Search code examples
hibernatespring-bootspring-orm

Dynamic datasource in Spring boot JPA


I have an application that need to connect to few different schema's but everything of same type (ORACLE). The decision of which schema comes from UI.

if User selects schema1, then entity should persist in Schema1, if selects other, then it should be in the selected other schema.

Am using Spring boot + Hibernate with the dependency "spring-boot-starter-data-jpa"

I have created a datasource class like below so that i can change the "schemaName" in the datasource object everytime before invoking the data layer.

@Component
public class SchemaDatasource extends AbstractDataSource {

    private String schemaName;

    @Autowired
    private DSManager dsm;

    public void setSchemaName(String schemaName) {
        this.schemaName = schemaName;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (schemaName!= null)
            return dsm.getConnection(schemaName);
        else
            return null;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        if (schemaName!= null)
            return dsm.getConnection(schemaName);
        else
            return null;
    }

}

My problem is during the startup, the "HibernateJpaAutoConfiguration" tries creating sessionfactory.During creation it tries to check connection with the datasource But as schemaName is null in the startup, my SchemaDatasource returns null connection with which application bootstrap is failed.

is there a way to handle this. Am expecting similar to SessionFactory withnooptions in hibernate.

In case of RoutingDatasource also, i have to set defaultDatasource.

Spring boot version: 1.5.9.RELEASE

Solution

  • Here is my implementation of DataSource

    public class DataSourceManager implements DataSource {
    
        private Map<String, DataSource> dataSources = new HashMap<>();
        private DataSource dataSource;
    
        public DataSourceManager() {
        }
    
        public DataSourceManager(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public void add(String name, DataSource dataSource) {
            dataSources.put(name, dataSource);
        }
    
        public void switchDataSource(String name) {
            dataSource = dataSources.get(name);
        }
    
        @Override
        public PrintWriter getLogWriter() throws SQLException {
            return dataSource.getLogWriter();
        }
    
        @Override
        public void setLogWriter(PrintWriter out) throws SQLException {
            dataSource.setLogWriter(out);
        }
    
        @Override
        public void setLoginTimeout(int seconds) throws SQLException {
            dataSource.setLoginTimeout(seconds);
        }
    
        @Override
        public int getLoginTimeout() throws SQLException {
            return dataSource.getLoginTimeout();
        }
    
        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return dataSource.getParentLogger();
        }
    
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return dataSource.unwrap(iface);
        }
    
        @Override
        public boolean isWrapperFor(Class<?> iface) throws SQLException {
            return dataSource.isWrapperFor(iface);
        }
    
        @Override
        public Connection getConnection() throws SQLException {
            return dataSource.getConnection();
        }
    
        @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return dataSource.getConnection(username, password);
        }
    }
    

    Here is my configuration

    @Configuration
    public class DataSourceConfig {
    
        @Autowired
        private Environment env;
    
        public DataSource makeDataSource(String name) {
            return DataSourceBuilder.create()
                    .driverClassName(env.getProperty("spring.datasource." + name + ".driver-class-name"))
                    .url(env.getProperty("spring.datasource." + name + ".url")).build();
        }
    
        @Bean
        public DataSource dataSource() {
            DataSourceManager dataSourceManager = new DataSourceManager();
            dataSourceManager.add("test1", makeDataSource("test1"));
            dataSourceManager.add("test2", makeDataSource("test2"));
            dataSourceManager.switchDataSource("test1");
            return dataSourceManager;
        }
    
    }
    

    Here is application.yml

    spring:
      jpa:
        hibernate:
          ddl-auto: create
        properties:
          hibernate:
            dialect: org.hibernate.dialect.H2Dialect
      datasource:
        test1:
          name: test2
          url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
          driver-class-name: org.h2.Driver
          username: h2
          password: h2
        test2:
          name: test1
          url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
          driver-class-name: org.h2.Driver
          username: h2
          password: h2