Home > other >  Schema Based - Multitenant - Spring Boot
Schema Based - Multitenant - Spring Boot

Time:01-14

I am trying to create minimalistic Schema Based - Multitenant - Spring Boot but I am getting following error. Application can be downloaded from GitHub https://github.com/ivoronline/spring_boot_db_multitenant

Error

Unable to instantiate specified multi-tenant connection provider [com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider]

application.properties

#spring.jpa.properties.hibernate.multiTenancy = SCHEMA
spring.jpa.properties.hibernate.multiTenancy                     = SCHEMA
spring.jpa.properties.hibernate.multi_tenant_connection_provider = com.ivoronline.spring_boot_db_multitenant.config.TenantConnectionProvider
spring.jpa.properties.hibernate.tenant_identifier_resolver       = com.ivoronline.spring_boot_db_multitenant.config.TenantIdentifierResolver

# JPA / HIBERNATE
spring.jpa.hibernate.ddl-auto = create

DataSourceConfig.java

@Configuration
public class DataSourceConfig {

      @Bean
      public DataSource dataSource()   {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
                          dataSourceBuilder.url("jdbc:postgresql://localhost:5432/multitenant");
                          dataSourceBuilder.username("postgres");
                          dataSourceBuilder.password("letmein");
                          dataSourceBuilder.driverClassName("org.postgresql.Driver");
        return            dataSourceBuilder.build();
      }
    
    }

TenantIdentifierResolver.java

@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {

  static final String DEFAULT_TENANT = "public";

  @Override
  public String resolveCurrentTenantIdentifier() {
    return "public";
  }

  @Override
  public boolean validateExistingCurrentSessions() {
    return true;  
  }

}

TenantConnectionProvider.java

@Component
public class TenantConnectionProvider implements MultiTenantConnectionProvider {

  private DataSource datasource;

  public TenantConnectionProvider(DataSource dataSource) {
    this.datasource = dataSource;
  }

  @Override
  public Connection getAnyConnection() throws SQLException {
    return datasource.getConnection();
  }

  @Override
  public void releaseAnyConnection(Connection connection) throws SQLException {
    connection.close();
  }

  @Override
  public Connection getConnection(String tenantIdentifier) throws SQLException {
    final Connection connection = getAnyConnection();
    //connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", tenantIdentifier));
    connection.createStatement().execute("SET Schema 'public'");
    return connection;
  }

  @Override
  public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
    //connection.createStatement().execute(String.format("SET SCHEMA \"%s\";", TenantIdentifierResolver.DEFAULT_TENANT));
    connection.createStatement().execute("SET Schema 'public'");
    releaseAnyConnection(connection);
  }

  @Override
  public boolean supportsAggressiveRelease() {
    return false;
  }

  @Override
  public boolean isUnwrappableAs(Class unwrapType) {
    return false;
  }

  @Override
  public <T> T unwrap(Class<T> unwrapType) {
    return null;
  }

}

CodePudding user response:

Hibernate expects that property hibernate.multi_tenant_connection_provider defines either already initialized object or class name which Hibernate can instantiate. In your case TenantConnectionProvider class has no default constructor, thus Hibernate fails (moreover, you expect that TenantConnectionProvider is a spring component). There are typically two options to configure such Hibernate SPIs:

  1. Hibernate way - interact with spring context using ManagedBeanRegistry
public class TenantConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {

    private ServiceRegistryImplementor serviceRegistry;
    
    // default constructor
    public TenantConnectionProvider() {
        super();
    }

    @Override
    public void injectServices(ServiceRegistryImplementor serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }
    
    protected DataSource getDataSource() {
        return serviceRegistry.getService(ManagedBeanRegistry.class)
                .getBean(DataSource.class)
                .getBeanInstance();
    }
    
    ...

}
  1. spring-boot way - configure JPA properties in Java code:
@Bean
public HibernatePropertiesCustomizer tenantConnectionProviderCustomizer(TenantConnectionProvider tenantConnectionProvider) {
    return hibernateProperties -> {
        hibernateProperties.put(AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, tenantConnectionProvider);
    };
}
  • Related