Multiple Datasources with Spring Boot and Hibernate
Adding two database connections to a Spring Boot application is quite straightforward unless you are using JpaRepositories
. Simple task of giving JdbcTemplate beans different identifier names becomes a bit confusing.
Starting with the configuration file the only thing needed is having different initials like
app.datasource.backup.url=jdbc:mysql://backup_db
app.datasource.backup.username=user
app.datasource.backup.password=password
app.datasource.backup.maximum-pool-size=10
app.datasource.main.url=jdbc:mysql://main_db
app.datasource.main.username=user
app.datasource.main.password=password
app.datasource.main.maximum-pool-size=10
These will be used for datasource creation and after that identifiers will be enough to separate JDBC beans.
@Bean
@Primary
@ConfigurationProperties("app.datasource.main")
public DataSourceProperties mainDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "dbMain")
@Primary
public DataSource mainDataSource() {
return mainDataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean(name = "jdbcMain")
@Autowired
public JdbcTemplate mainJdbcTemplate(@Qualifier("dbMain") DataSource dsMain) {
return new JdbcTemplate(dsMain);
}
@Bean(name = "jdbcBackup")
@Autowired
public JdbcTemplate slaveJdbcTemplate(@Qualifier("dbBackup") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}
@Bean
@ConfigurationProperties("app.datasource.backup")
public DataSourceProperties backupDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "dbBackup")
public DataSource backupDataSource() {
return backupDataSourceProperties().initializeDataSourceBuilder().build();
}
In a configuration file or in the SpringBoot main class define these datasource beans.
- Read configuration information from resources
- Create a datasource with a name from that configuration
- Set the datasource created to a specific JdbcTemplate. The name given here will be used for accessing to the different JdbcTemplate beans as
@Qualifier("jdbcMain") JdbcTemplate jdbcTemplate
While defining a jdbc repository give the identifier for accessing to that database.
@Repository
public class GameRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public GameRepository(@Qualifier("jdbcMain") JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
...
What if we want to use JPA and define our objects with Entity
. Datasource definitions and initialization will not change. However different EntityManagers
and TransactionManagers
must be defined.
Annoying part is having auto mapping repositories to these classes is done by scanning packages. Which package to scan must be defined clearly otherwise it will fail while creating database connection. Another point is different entity managers must have different packages as well. It is not possible to use a com.test.repositories
package and make both scan in there.
Datasource configuration is almost the same as the previous one but this time let’s use configuration annotation. DataSourceConfiguration
file will be defined as:
@Configuration
public class DataSourceConfig {
// first database
@Primary
@Bean(name = "blog")
@ConfigurationProperties("app.datasource.blog")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean(name = "dataSourceBlogDb")
public HikariDataSource firstDataSource(@Qualifier("blog") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
// second database
@Bean(name = "log")
@ConfigurationProperties("app.datasource.log")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "dataSourceLogDb")
public HikariDataSource secondDataSource(@Qualifier("log") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}
Next step is defining an EntityManager. Be aware of the base package io.msdalp.dsone
to be scanned. First entity manager will deal with datasource one and everything related to it will be stored in io.msdalp.dsone
. Also do not forget to update Hibernate Dialect if you are using a different database.
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "ds1EntityManagerFactory",
transactionManagerRef = "ds1TransactionManager",
basePackages = "io.msdalp.dsone"
)
@EnableTransactionManagement
public class FirstEntityManagerFactory {
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean ds1EntityManagerFactory(
EntityManagerFactoryBuilder builder, @Qualifier("dataSourceBlogDb") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("io.msdalp.dsone")
.persistenceUnit("ds1-pu")
.properties(hibernateProperties())
.build();
}
@Bean
@Primary
public PlatformTransactionManager ds1TransactionManager(
@Qualifier("ds1EntityManagerFactory") EntityManagerFactory ds1EntityManagerFactory) {
return new JpaTransactionManager(ds1EntityManagerFactory);
}
protected Map<String, String> hibernateProperties() {
return new HashMap<String, String>() {
{
put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
put("hibernate.hbm2ddl.auto", "create");
}
};
}
}
Second entity manager will be exactly the same with this one but only different identifiers. It is using the second datasource and scanning io.msdalp.dstwo
.
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "ds2EntityManagerFactory",
transactionManagerRef = "ds2TransactionManager",
basePackages = "io.msdalp.dstwo"
)
@EnableTransactionManagement
public class SecondEntityManagerFactory {
@Bean
public LocalContainerEntityManagerFactoryBean ds2EntityManagerFactory(
EntityManagerFactoryBuilder builder, @Qualifier("dataSourceLogDb") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("io.msdalp.dstwo")
.persistenceUnit("ds2-pu")
.properties(hibernateProperties())
.build();
}
@Bean
public PlatformTransactionManager ds2TransactionManager(
@Qualifier("ds2EntityManagerFactory") EntityManagerFactory secondEntityManagerFactory) {
return new JpaTransactionManager(secondEntityManagerFactory);
}
protected Map<String, String> hibernateProperties() {
return new HashMap<String, String>() {
{
put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
put("hibernate.hbm2ddl.auto", "create");
}
};
}
}
With these configurations any repository or entity defined under package io.msdalp.dsone
will work with Blog
database and io.msdalp.dstwo
with Log
database.
You can check the sample project at https://github.com/msdalp/spring-multi-datasource.