Here is my solution (it might be outdated as it was created in 2016th):
DbConfig (It does not really needed, I just added for completeness config)
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.jta.JtaTransactionManager; import javax.sql.DataSource; @Configuration public class DBConfig extends HibernateJpaAutoConfiguration { @Value("${spring.jpa.orm}") private String orm; // this is need for my entities declared in orm.xml located in resources directory @SuppressWarnings("SpringJavaAutowiringInspection") public DBConfig(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) { super(dataSource, jpaProperties, jtaTransactionManagerProvider); } @Override @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( EntityManagerFactoryBuilder factoryBuilder) { final LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = super.entityManagerFactory(factoryBuilder); entityManagerFactoryBean.setMappingResources(orm); return entityManagerFactoryBean; } }
DataSourceConfig
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Scope; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; @Configuration public class DataSourceConfig { @Bean @Qualifier("default") @ConfigurationProperties(prefix = "spring.datasource") protected DataSource defaultDataSource(){ return DataSourceBuilder .create() .build(); } @Bean @Primary @Scope("singleton") public AbstractRoutingDataSource routingDataSource(@Autowired @Qualifier("default") DataSource defaultDataSource){ RoutingDataSource routingDataSource = new RoutingDataSource(); routingDataSource.addDataSource(RoutingDataSource.DEFAULT,defaultDataSource); routingDataSource.setDefaultTargetDataSource(defaultDataSource); return routingDataSource; } }
My extension of RoutingDataSource:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; public class RoutingDataSource extends AbstractRoutingDataSource { static final int DEFAULT = 0; static final int NEW = 1; private volatile int key = DEFAULT; void setKey(int key){ this.key = key; } private Map<Object,Object> dataSources = new HashMap(); RoutingDataSource() { setTargetDataSources(dataSources); } void addDataSource(int key, DataSource dataSource){ dataSources.put(new Integer(key),dataSource); } @Override protected Object determineCurrentLookupKey() { return new Integer(key); } @Override protected DataSource determineTargetDataSource() { return (DataSource) dataSources.get(key); } }
And here it's special spring component to swith datasource in runtime:
import org.hibernate.boot.Metadata; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.tool.hbm2ddl.SchemaUpdate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.stereotype.Component; import javax.sql.DataSource; @Component public class DBSettingsSwitcher { @Autowired private AbstractRoutingDataSource routingDataSource; @Value("${spring.jpa.orm}") private String ormMapping; public void applySettings(DBSettings dbSettings){ if (routingDataSource instanceof RoutingDataSource){ // by default Spring uses DataSource from apache tomcat DataSource dataSource = DataSourceBuilder .create() .username(dbSettings.getUserName()) .password(dbSettings.getPassword()) .url(dbSettings.JDBConnectionURL()) .driverClassName(dbSettings.driverClassName()) .build(); RoutingDataSource rds = (RoutingDataSource)routingDataSource; rds.addDataSource(RoutingDataSource.NEW,dataSource); rds.setKey(RoutingDataSource.NEW); updateDDL(dbSettings); } } private void updateDDL(DBSettings dbSettings){ /** worked on hibernate 5*/ StandardServiceRegistry registry = new StandardServiceRegistryBuilder() .applySetting("hibernate.connection.url", dbSettings.JDBConnectionURL()) .applySetting("hibernate.connection.username", dbSettings.getUserName()) .applySetting("hibernate.connection.password", dbSettings.getPassword()) .applySetting("hibernate.connection.driver_class", dbSettings.driverClassName()) .applySetting("hibernate.dialect", dbSettings.dialect()) .applySetting("show.sql", "false") .build(); Metadata metadata = new MetadataSources() .addResource(ormMapping) .addPackage("specify_here_your_package_with_entities") .getMetadataBuilder(registry) .build(); new SchemaUpdate((MetadataImplementor) metadata).execute(false,true); } }
Where DB settings is just an interface (you should implement it according to your needs):
public interface DBSettings { int getPort(); String getServer(); String getSelectedDataBaseName(); String getPassword(); String getUserName(); String dbmsType(); String JDBConnectionURL(); String driverClassName(); String dialect(); }
Having your own implementation of DBSettings and builded DBSettingsSwitcher in your Spring context, now you can just call DBSettingsSwitcher.applySettings(dbSettingsIml) and your data requests will be routed to new data source.