كيفية بناء تطبيق متعدد المستأجرين: برنامج تعليمي حول السبات

نشرت: 2022-03-11

عندما نتحدث عن التطبيقات السحابية حيث يكون لكل عميل بياناته المنفصلة ، نحتاج إلى التفكير في كيفية تخزين هذه البيانات ومعالجتها. حتى مع وجود جميع حلول NoSQL الرائعة المتوفرة ، في بعض الأحيان ما زلنا بحاجة إلى استخدام قاعدة البيانات الارتباطية القديمة الجيدة. الحل الأول الذي قد يتبادر إلى الذهن لفصل البيانات هو إضافة معرف في كل جدول ، بحيث يمكن التعامل معها بشكل فردي. يعمل هذا ، ولكن ماذا لو طلب العميل قاعدة بياناته؟ سيكون من المرهق للغاية استرداد كل تلك السجلات المخفية بين السجلات الأخرى.

تطبيق Java EE متعدد الوظائف مع وضع السبات

أصبح تعدد الوظائف في Java أسهل من أي وقت مضى مع Hibernate.
سقسقة

توصل فريق Hibernate إلى حل لهذه المشكلة منذ فترة. أنها توفر بعض نقاط الامتداد التي تمكن المرء من التحكم من حيث يجب استرداد البيانات. يحتوي هذا الحل على خيار التحكم في البيانات عبر عمود المعرف وقواعد البيانات المتعددة والمخططات المتعددة. ستغطي هذه المقالة حل المخططات المتعددة.

لذا ، فلنبدأ العمل!

ابدء

إذا كنت مطور Java أكثر خبرة وتعرف كيفية تكوين كل شيء ، أو إذا كان لديك بالفعل مشروع Java EE الخاص بك ، فيمكنك تخطي هذا القسم.

أولاً ، علينا إنشاء مشروع Java جديد. أنا أستخدم Eclipse و Gradle ، ولكن يمكنك استخدام IDE المفضل لديك وأدوات البناء ، مثل IntelliJ و Maven.

إذا كنت ترغب في استخدام نفس الأدوات مثلي ، يمكنك اتباع هذه الخطوات لإنشاء مشروعك:

  • قم بتثبيت برنامج Gradle الإضافي على Eclipse
  • انقر فوق ملف -> جديد -> أخرى ...
  • ابحث عن Gradle (STS) وانقر فوق التالي
  • أبلغ عن اسم واختر Java Quickstart لنموذج المشروع
  • انقر فوق "إنهاء"

رائعة! يجب أن يكون هذا هو الهيكل الأولي للملف:

 javaee-mt |- src/main/java |- src/main/resources |- src/test/java |- src/test/resources |- JRE System Library |- Gradle Dependencies |- build |- src |- build.gradle

يمكنك حذف جميع الملفات التي تأتي داخل المجلدات المصدر ، لأنها مجرد ملفات نموذجية.

لتشغيل المشروع ، أستخدم Wildfly ، وسأوضح كيفية تهيئته (مرة أخرى يمكنك استخدام أداتك المفضلة هنا):

  • تنزيل Wildfly: http://wildfly.org/downloads/ (أنا أستخدم الإصدار 10)
  • قم بفك ضغط الملف
  • قم بتثبيت البرنامج المساعد JBoss Tools على Eclipse
  • في علامة التبويب الخوادم ، انقر بزر الماوس الأيمن فوق أي منطقة فارغة واختر جديد -> خادم
  • اختر Wildfly 10.x (9.x يعمل أيضًا إذا لم يكن 10 متاحًا ، اعتمادًا على إصدار Eclipse الخاص بك)
  • انقر فوق التالي ، واختر إنشاء وقت تشغيل جديد (الصفحة التالية) وانقر فوق التالي مرة أخرى
  • اختر المجلد حيث قمت بفك ضغط Wildfly كدليل رئيسي
  • انقر فوق "إنهاء"

الآن ، لنقم بتهيئة Wildfly لمعرفة قاعدة البيانات:

  • انتقل إلى مجلد bin داخل مجلد Wildfly الخاص بك
  • قم بتنفيذ add-user.bat أو add-user.sh (حسب نظام التشغيل الخاص بك)
  • اتبع الخطوات لإنشاء المستخدم الخاص بك كمدير
  • في Eclipse ، انتقل إلى علامة التبويب Servers مرة أخرى ، وانقر بزر الماوس الأيمن على الخادم الذي قمت بإنشائه وحدد Start
  • على المستعرض الخاص بك ، قم بالوصول إلى http: // localhost: 9990 ، وهي واجهة الإدارة
  • أدخل بيانات اعتماد المستخدم الذي أنشأته للتو
  • انشر برنامج التشغيل لقاعدة البيانات الخاصة بك:
    1. انتقل إلى علامة التبويب "النشر" وانقر فوق إضافة
    2. انقر فوق التالي ، اختر ملف جرة السائق الخاص بك
    3. انقر فوق "التالي" و "إنهاء"
  • انتقل إلى علامة التبويب "التكوين"
  • اختر الأنظمة الفرعية -> مصادر البيانات -> غير XA
  • انقر فوق إضافة ، وحدد قاعدة البيانات الخاصة بك وانقر فوق التالي
  • قم بتسمية مصدر البيانات الخاص بك وانقر فوق التالي
  • حدد علامة التبويب Detect Driver واختر برنامج التشغيل الذي قمت بنشره للتو
  • أدخل معلومات قاعدة البيانات الخاصة بك وانقر فوق "التالي"
  • انقر فوق اختبار الاتصال إذا كنت تريد التأكد من صحة معلومات الخطوة السابقة
  • انقر فوق "إنهاء"
  • ارجع إلى Eclipse وأوقف الخادم قيد التشغيل
  • انقر بزر الماوس الأيمن فوقه وحدد "إضافة وإزالة"
  • أضف مشروعك إلى اليمين
  • انقر فوق "إنهاء"

حسنًا ، لقد تم تكوين Eclipse و Wildfly معًا!

هذه هي جميع التكوينات المطلوبة خارج المشروع. دعنا ننتقل إلى تكوين المشروع.

مشروع التمهيد

الآن بعد أن تم تكوين Eclipse و Wildfly وإنشاء مشروعنا ، نحتاج إلى تكوين مشروعنا.

أول شيء سنفعله هو تعديل build.gradle. هكذا المفروض ان يظهر:

 apply plugin: 'java' apply plugin: 'war' apply plugin: 'eclipse' apply plugin: 'eclipse-wtp' sourceCompatibility = '1.8' compileJava.options.encoding = 'UTF-8' compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' repositories { jcenter() } eclipse { wtp { } } dependencies { providedCompile 'org.hibernate:hibernate-entitymanager:5.0.7.Final' providedCompile 'org.jboss.resteasy:resteasy-jaxrs:3.0.14.Final' providedCompile 'javax:javaee-api:7.0' }

يتم إعلان جميع التبعيات كـ "providedCompile" ، لأن هذا الأمر لا يضيف التبعية في ملف الحرب النهائي. تمتلك Wildfly بالفعل هذه التبعيات ، وستتسبب في حدوث تعارضات مع تلك الخاصة بالتطبيق.

في هذه المرحلة ، يمكنك النقر بزر الماوس الأيمن فوق مشروعك ، وتحديد Gradle (STS) -> Refresh All لاستيراد التبعيات التي أعلنا عنها للتو.

حان الوقت لإنشاء ملف "persistent.xml" وتكوينه ، وهو الملف الذي يحتوي على المعلومات التي يحتاجها Hibernate:

  • في المجلد src / main / source source ، قم بإنشاء مجلد يسمى META-INF
  • داخل هذا المجلد ، قم بإنشاء ملف باسم persistent.xml

يجب أن يكون محتوى الملف على النحو التالي ، مع تغيير jta-data-source ليطابق مصدر البيانات الذي أنشأته في Wildfly package com.toptal.andrehil.mt.hibernate إلى الملف الذي ستنشئه في اليوم التالي القسم (إلا إذا اخترت نفس اسم الحزمة):

 <?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="pu"> <jta-data-source>java:/JavaEEMTDS</jta-data-source> <properties> <property name="hibernate.multiTenancy" value="SCHEMA"/> <property name="hibernate.tenant_identifier_resolver" value="com.toptal.andrehil.mt.hibernate.SchemaResolver"/> <property name="hibernate.multi_tenant_connection_provider" value="com.toptal.andrehil.mt.hibernate.MultiTenantProvider"/> </properties> </persistence-unit> </persistence>

فصول السبات

تشير التكوينات المضافة إلى persistence.xml إلى فئتين مخصصتين MultiTenantProvider و SchemaResolver. الدرجة الأولى مسؤولة عن توفير اتصالات تم تكوينها باستخدام المخطط الصحيح. الفئة الثانية مسؤولة عن تحديد اسم المخطط المراد استخدامه.

هنا تنفيذ الفئتين:

 public class MultiTenantProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService { private static final long serialVersionUID = 1L; private DataSource dataSource; @Override public boolean supportsAggressiveRelease() { return false; } @Override public void injectServices(ServiceRegistryImplementor serviceRegistry) { try { final Context init = new InitialContext(); dataSource = (DataSource) init.lookup("java:/JavaEEMTDS"); // Change to your datasource name } catch (final NamingException e) { throw new RuntimeException(e); } } @SuppressWarnings("rawtypes") @Override public boolean isUnwrappableAs(Class clazz) { return false; } @Override public <T> T unwrap(Class<T> clazz) { return null; } @Override public Connection getAnyConnection() throws SQLException { final Connection connection = dataSource.getConnection(); return connection; } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { final Connection connection = getAnyConnection(); try { connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'"); } catch (final SQLException e) { throw new HibernateException("Error trying to alter schema [" + tenantIdentifier + "]", e); } return connection; } @Override public void releaseAnyConnection(Connection connection) throws SQLException { try { connection.createStatement().execute("SET SCHEMA 'public'"); } catch (final SQLException e) { throw new HibernateException("Error trying to alter schema [public]", e); } connection.close(); } @Override public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { releaseAnyConnection(connection); } }

تعمل البنية المستخدمة في العبارات أعلاه مع PostgreSQL وبعض قواعد البيانات الأخرى ، ويجب تغيير هذا في حالة وجود صيغة مختلفة لقاعدة البيانات لتغيير المخطط الحالي.

 public class SchemaResolver implements CurrentTenantIdentifierResolver { private String tenantIdentifier = "public"; @Override public String resolveCurrentTenantIdentifier() { return tenantIdentifier; } @Override public boolean validateExistingCurrentSessions() { return false; } public void setTenantIdentifier(String tenantIdentifier) { this.tenantIdentifier = tenantIdentifier; } }

في هذه المرحلة ، من الممكن بالفعل اختبار التطبيق. في الوقت الحالي ، يشير المحلل الخاص بنا مباشرة إلى مخطط عام مشفر بشكل صارم ، ولكن تم استدعاؤه بالفعل. للقيام بذلك ، قم بإيقاف الخادم الخاص بك إذا كان قيد التشغيل وابدأ تشغيله مرة أخرى. يمكنك محاولة تشغيله في وضع التصحيح ووضع نقطة توقف في أي نقطة من الفئات أعلاه للتحقق مما إذا كان يعمل.

الاستخدام العملي للمحلل

لذا ، كيف يمكن لوحدة الحل أن تحتوي بالفعل على الاسم الصحيح للمخطط؟

تتمثل إحدى طرق تحقيق ذلك في الاحتفاظ بمعرف في رأس جميع الطلبات ثم إنشاء عامل تصفية لإدخال اسم المخطط.

دعنا ننفذ فئة مرشح لتوضيح الاستخدام. يمكن الوصول إلى المحلل من خلال Hibernate's SessionFactory ، لذلك سوف نستفيد من ذلك للحصول عليه وحقن اسم المخطط الصحيح.

 @Provider public class AuthRequestFilter implements ContainerRequestFilter { @PersistenceUnit(unitName = "pu") private EntityManagerFactory entityManagerFactory; @Override public void filter(ContainerRequestContext containerRequestContext) throws IOException { final SessionFactoryImplementor sessionFactory = ((EntityManagerFactoryImpl) entityManagerFactory).getSessionFactory(); final SchemaResolver schemaResolver = (SchemaResolver) sessionFactory.getCurrentTenantIdentifierResolver(); final String username = containerRequestContext.getHeaderString("username"); schemaResolver.setTenantIdentifier(username); } }

الآن ، عندما تحصل أي فئة على EntityManager للوصول إلى قاعدة البيانات ، فسيتم تكوينها بالفعل باستخدام المخطط الصحيح.

من أجل البساطة ، فإن التنفيذ الموضح هنا هو الحصول على المعرف مباشرة من سلسلة في الرأس ، ولكن من الجيد استخدام رمز المصادقة وتخزين المعرف في الرمز المميز. إذا كنت مهتمًا بمعرفة المزيد عن هذا الموضوع ، أقترح إلقاء نظرة على JSON Web Tokens (JWT). JWT هي مكتبة لطيفة وبسيطة لمعالجة الرمز المميز.

كيفية استخدام كل هذا

مع تكوين كل شيء ، ليس هناك ما يلزم القيام به في الكيانات و / أو الفئات التي تتفاعل مع EntityManager . سيتم توجيه أي شيء تقوم بتشغيله من EntityManager إلى المخطط الذي تم حله بواسطة عامل التصفية الذي تم إنشاؤه.

الآن ، كل ما عليك فعله هو اعتراض الطلبات من جانب العميل وإدخال المعرف / الرمز المميز في الرأس ليتم إرساله إلى جانب الخادم.

في التطبيق الحقيقي ، سيكون لديك وسيلة أفضل للمصادقة. ومع ذلك ، فإن الفكرة العامة لشركات التعددية ستبقى كما هي.

يشير الرابط الموجود في نهاية المقالة إلى المشروع المستخدم لكتابة هذه المقالة. يستخدم Flyway لإنشاء مخططين ويحتويان على فئة كيان تسمى Car وفئة خدمة أخرى تسمى CarService يمكن استخدامها لاختبار المشروع. يمكنك اتباع جميع الخطوات أدناه ، ولكن بدلاً من إنشاء مشروعك الخاص ، يمكنك استنساخه واستخدامه. بعد ذلك ، عند التشغيل ، يمكنك استخدام عميل HTTP بسيط (مثل ملحق Postman لمتصفح Chrome) وتقديم طلب GET إلى http: // localhost: 8080 / javaee-mt / rest / cars باستخدام مفتاح الرؤوس: القيمة:

  • اسم المستخدم: جو ؛ أو
  • اسم المستخدم: فريد.

من خلال القيام بذلك ، ستعيد الطلبات قيمًا مختلفة ، والتي توجد في مخططات مختلفة ، أحدهما يسمى joe والآخر يسمى "fred".

الكلمات الأخيرة

ليس هذا هو الحل الوحيد لإنشاء تطبيقات متعددة الشركات في عالم Java ، ولكنه طريقة بسيطة لتحقيق ذلك.

شيء واحد يجب مراعاته هو أن Hibernate لا تنشئ DDL عند استخدام التكوين متعدد الاستئجار. اقتراحي هو إلقاء نظرة على Flyway أو Liquibase ، وهما مكتبات رائعة للتحكم في إنشاء قاعدة البيانات. يعد هذا أمرًا رائعًا حتى إذا كنت لن تستخدم الوظائف المتعددة ، كما ينصح فريق Hibernate بعدم استخدام إنشاء قاعدة بيانات السيارات الخاصة بهم في الإنتاج.

يمكن العثور على الكود المصدري المستخدم لإنشاء هذه المقالة وتهيئة البيئة على github.com/andrehil/JavaEEMT

الموضوعات ذات الصلة: دروس متقدمة في Java Class: دليل لإعادة تحميل الفصل