31 Mayıs 2023 Çarşamba

SpringData Multitenancy - Hibernate İle Separate Database

Giriş
Bu yöntem JPA sağlayıcısı olarak Hibernate kullanıyorsak işe yarar. 

Kısaca
Hibernate tarafından sağlanan AbstractMultiTenantConnectionProvider sınıfından kalıtan yeni bir ConnectionProvider yazılır. Bu sınıf belirtilen schema'ya geçiş yapar. Bu sınıfı kodlarken şu metodlar override edilir
protected ConnectionProvider getAnyConnectionProvider();
public Connection getConnection(String tenantIdentifier);
protected ConnectionProvider selectConnectionProvider(String tenantIdentifier);
AbstractMultiTenantConnectionProvider Sınıfı

Örnek
Şöyle yaparız
apublic class SchemaMultiTenantConnectionProvider 
  extends AbstractMultiTenantConnectionProvider {

  private static final String HIBERNATE_PROPERTIES_PATH = "/application.properties";
  private final Map<String, ConnectionProvider> connectionProviderMap;

  public SchemaMultiTenantConnectionProvider() {
    this.connectionProviderMap = new HashMap<String, 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;
  }
        
  @Override
  protected ConnectionProvider getAnyConnectionProvider() {
    return getConnectionProvider(TenantContext.DEFAULT_TENANT_ID);
  }
 
  @Override
  protected ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
    return getConnectionProvider(tenantIdentifier);
  }
        
  private ConnectionProvider getConnectionProvider(String tenantIdentifier) {
    return Optional.ofNullable(tenantIdentifier)
      .map(connectionProviderMap::get)
      .orElseGet(() -> createNewConnectionProvider(tenantIdentifier));
  }
 
   private ConnectionProvider createNewConnectionProvider(String tenantIdentifier) {
     return Optional.ofNullable(tenantIdentifier)
       .map(this::createConnectionProvider)
       .map(connectionProvider -> {
         connectionProviderMap.put(tenantIdentifier, connectionProvider);
         return connectionProvider;
       })
       .orElseThrow(() -> 
        new ConnectionProviderException(
String.format("Cannot create new connection provider for tenant: %s", tenantIdentifier)));
      }
        
    private ConnectionProvider createConnectionProvider(String tenantIdentifier) {
      return Optional.ofNullable(tenantIdentifier)
        .map(this::getHibernatePropertiesForTenantId)
        .map(this::initConnectionProvider)
        .orElse(null);
    }
        
  private Properties getHibernatePropertiesForTenantId(String tenantId) {
    try {
      Properties properties = new Properties();
      properties.load(getClass().getResourceAsStream(HIBERNATE_PROPERTIES_PATH));
      return properties;
    } catch (IOException e) {
      throw new RuntimeException(
   String.format("Cannot open hibernate properties: %s)", HIBERNATE_PROPERTIES_PATH));
    }
  }
 
  private ConnectionProvider initConnectionProvider(Properties hibernateProperties) {
    DriverManagerConnectionProviderImpl connectionProvider = 
      new DriverManagerConnectionProviderImpl();
    connectionProvider.configure(hibernateProperties);
    return connectionProvider;
  }      
}
Açıklaması şöyle
In this case, we have one method to overload: getConnection(). We call an SQL that changes the schema on the connection we’ve already created. In this implementation, we assumed that the tenantId is also the name of the schema. With tenantId mapping, the name of the schema can also be implemented from this side.

Hiç yorum yok:

Yorum Gönder