جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


Multitenancy یا چند مستاجری به چندین کلاینت اجازه میدهد از یک منبع استفاده کنند و اطلاعات اشتراکی بین کلاینت ها بصورت ایزوله استفاده شود در این بخش خواهیم دید چگونه میتوان در Hibernate 5 دیتابیس را برای Multitenancy کانفیگ و استفاده کرد



ابتدا وابستگی ها را به Pom اضافه میکنیم :


<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-core</artifactId>
   <version>5.2.12.Final</version>
</dependency>

چون در این مثال از h2 استفاده میکنیم لازم است آن را هم به پروژه اضافه کنیم :

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <version>1.4.196</version>
</dependency>




مفهوم Multitenancy در Hibernate :


همانطور که در توضیحات Hibernate آمده است برای سه هدف از Multitenancy استفاده میشود :


- جداسازی Schema : یک schema به ازای یک مستاجر در یک دیتابیس فیزیکی

- جداسازی دیتابیس : یک دیتابیس فیزیکی جداگانه برای هر مستاجر

- داده های تقسیم شده یا Partitioned (Discriminator) Data : داده ها برای هر مستاجر تفکیک شده اند که هنوز توسط Hibernate پشتیبانی نمیشود 



هایبرنیت سعی دارد پیچیدگی ها را برای استفاده ساده کند برای همین دو اینترفیس را برای استفاده معرفی کرده است :


- MultiTenantConnectionProvider که برای هر مستاجر connection را فراهم میکند 

- CurrentTenantIdentifierResolver که برای هر مستاجر Id آنرا شناسایی میکند




MultiTenantConnectionProvider :


interface MultiTenantConnectionProvider extends Service, Wrapped {
    Connection getAnyConnection() throws SQLException;
 
    Connection getConnection(String tenantIdentifier) throws SQLException;
     // ...
}

* اگر هایبرنیت نتواند Connection ای با ID مستاجر پیدا کند از متد ()getAnyConnection استفاده میکند 


برای پیاده سازی هایبرنیت دو کلاس را ارائه داده است :


اگر از اینترفیس DataSource استفاده کنیم میتوانیم کلاس DataSourceBasedMultiTenantConnectionProviderImpl را بکار ببریم 

اگر از اینترفیس ConnectionProvider استفاده کنیم کلاس AbstractMultiTenantConnectionProvider برای پیاده سازی قابل استفاده است 







CurrentTenantIdentifierResolver :


راه های زیادی برای شناسایی کردن مستاجر وجود دارد مثلا میتواند با استفاده از یک فایل تنظیمات اطلاعات شناسایی استخراج شود و یا از پارامتر path استفاده کند 

ساختار این اینترفیس :

public interface CurrentTenantIdentifierResolver {
 
    String resolveCurrentTenantIdentifier();
 
    boolean validateExistingCurrentSessions();
}

هایبرنیت ابتدا متد resolveCurrentTenantIdentifier را فراخوانی میکند تا مستاجر را شناسایی کند و اگر بخواهیم تمامی مستاجر ها Validate شوند از متد validateExistingCurrentSessions با مقدار برگشتی true استفاده میکنیم





رویکرد Schema :


در این استراتژی ما از Schema یا نام کاربری متفاوت در یک دیتابیس فیزیکی استفاده میکنیم این رویکرد در شرایطی استفاده میشود که نیاز به بیشترین پرفرمانس داشته باشیم و بتوانیم از ویژگی ایجاد Backup دیتابیس به ازای هر مستاجر استفاده کنیم 


public abstract class MultitenancyIntegrationTest {
 
    @Mock
    private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
 
    private SessionFactory sessionFactory;
 
    @Before
    public void setup() throws IOException {
        MockitoAnnotations.initMocks(this);
 
        when(currentTenantIdentifierResolver.validateExistingCurrentSessions())
          .thenReturn(false);
 
        Properties properties = getHibernateProperties();
        properties.put(
          AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, 
          currentTenantIdentifierResolver);
 
        sessionFactory = buildSessionFactory(properties);
 
        initTenant(TenantIdNames.MYDB1);
        initTenant(TenantIdNames.MYDB2);
    }
 
    protected void initTenant(String tenantId) {
        when(currentTenantIdentifierResolver
         .resolveCurrentTenantIdentifier())
           .thenReturn(tenantId);
        createCarTable();
    }
}



پیاده سازی MultiTenantConnectionProvider  :


در این پیاده سازی هر زمانی که Connection نیاز بود ایجاد شود اسکیمای مورد نظر را ست میکند 

class SchemaMultiTenantConnectionProvider
  extends AbstractMultiTenantConnectionProvider {
 
    private ConnectionProvider connectionProvider;
 
    public SchemaMultiTenantConnectionProvider() throws IOException {
        this.connectionProvider = initConnectionProvider();
    }
 
    @Override
    protected ConnectionProvider getAnyConnectionProvider() {
        return connectionProvider;
    }
 
    @Override
    protected ConnectionProvider selectConnectionProvider(
      String tenantIdentifier) {
  
        return connectionProvider;
    }
 
    @Override
    public Connection getConnection(String tenantIdentifier)
      throws SQLException {
  
        Connection connection = super.getConnection(tenantIdentifier);
        connection.createStatement()
          .execute(String.format("SET SCHEMA %s;", tenantIdentifier));
        return connection;
    }
 
    private ConnectionProvider initConnectionProvider() throws IOException {
        Properties properties = new Properties();
        properties.load(getClass()
          .getResourceAsStream("/hibernate.properties"));
 
        DriverManagerConnectionProviderImpl connectionProvider 
          = new DriverManagerConnectionProviderImpl();
        connectionProvider.configure(properties);
        return connectionProvider;
    }
}


در اینجا دو مستاجر داریم که برای هر یک اسکیمای جداگانه ای در نظر گرفته شده 


تنظیمات hibernate.properties برای استفاده از schema در حالت چند مستاجری و اینترفیس MultiTenantConnectionProvider  :


hibernate.connection.url=jdbc:h2:mem:mydb1;DB_CLOSE_DELAY=-1;\
  INIT=CREATE SCHEMA IF NOT EXISTS MYDB1\\;CREATE SCHEMA IF NOT EXISTS MYDB2\\;
hibernate.multiTenancy=SCHEMA
hibernate.multi_tenant_connection_provider=\
  com.baeldung.hibernate.multitenancy.schema.SchemaMultiTenantConnectionProvider


ما برای شرایط تست در hibernate.connection.url دو اسکیما را کانفیگ کردیم که در برنامه واقعی لازم نیست و باید اسکیما ها از قبل آماده استفاده باشند


تست :


یک داده از نوع Car را برای مستاجر اول مربوط به اسکیمای myDb1 ذخیره کردیم و سپس آنرا از دیتابیس فراخوانی کردیم و بعد آنرا از myDb2 که مربوط به مستاجر دوم است فراخوانی میکنیم ولی دیتایی موجود نیست پس هر مستاجر دیتای مربوط به خود را بصورت ایزوله خواهد داشت :

@Test
void whenAddingEntries_thenOnlyAddedToConcreteDatabase() {
    whenCurrentTenantIs(TenantIdNames.MYDB1);
    whenAddCar("myCar");
    thenCarFound("myCar");
    whenCurrentTenantIs(TenantIdNames.MYDB2);
    thenCarNotFound("myCar");
}





رویکرد Database :


رویکرد چند مستاجری Database برای فراهم کردن دیتابیس فیزیکی مجزا به ازای هر مستاجر است و از آنجا که دیتابیس هر مستاجر کاملا ایزوله است باید زمانی این استراتژی انتخاب شود که ویژگی های خاصی نظیر Backup گیری دیتابیس به ازای هر مستاجر مورد نیاز باشد


برای مثال، مانند قبل از کلاس MultitenancyIntegrationTest و اینترفیس CurrentTenantIdentifierResolver استفاده میکنیم و برای اینترفیس MultiTenantConnectionProvider ما از یک Map استفاده کردیم که ConnectionProvider های شناسایی شده هر مستاجر در آن نگهداری میشود :


class MapMultiTenantConnectionProvider
  extends AbstractMultiTenantConnectionProvider {
 
    private Map<String, ConnectionProvider> connectionProviderMap
     = new HashMap<>();
 
    public MapMultiTenantConnectionProvider() throws IOException {
        initConnectionProviderForTenant(TenantIdNames.MYDB1);
        initConnectionProviderForTenant(TenantIdNames.MYDB2);
    }
 
    @Override
    protected ConnectionProvider getAnyConnectionProvider() {
        return connectionProviderMap.values()
          .iterator()
          .next();
    }
 
    @Override
    protected ConnectionProvider selectConnectionProvider(
      String tenantIdentifier) {
  
        return connectionProviderMap.get(tenantIdentifier);
    }
 
    private void initConnectionProviderForTenant(String tenantId)
     throws IOException {
        Properties properties = new Properties();
        properties.load(getClass().getResourceAsStream(
          String.format("/hibernate-database-%s.properties", tenantId)));
        DriverManagerConnectionProviderImpl connectionProvider 
          = new DriverManagerConnectionProviderImpl();
        connectionProvider.configure(properties);
        this.connectionProviderMap.put(tenantId, connectionProvider);
    }
}



ست کردن تنظیمات دیتابیس به ازای هر مستاجر :


هر ConnectionProvider مربوط به هر مستاجر از طریق فایل جداگانه با الگوی hibernate-database-<tenant identifier>.properties کانفیگ میشود 

hibernate.connection.driver_class=org.h2.Driver
hibernate.connection.url=jdbc:h2:mem:<Tenant Identifier>;DB_CLOSE_DELAY=-1
hibernate.connection.username=sa
hibernate.dialect=org.hibernate.dialect.H2Dialect


و در hibernate.properties حالت چند مستاجری database را فعال میکنیم و پیاده ساز اینترفیس MultiTenantConnectionProvider را نیز معرفی میکنیم :

hibernate.multiTenancy=DATABASE
hibernate.multi_tenant_connection_provider=\
  com.baeldung.hibernate.multitenancy.database.MapMultiTenantConnectionProvider


تست آن مانند حالت قبل قابل انجام است 









نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی