Spring Boot Configure and Use Two DataSources

409,012

Solution 1

Here you go.

Add in your application.properties file:

#first db
spring.datasource.url = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
spring.datasource.driverClassName = oracle.jdbc.OracleDriver

#second db ...
spring.secondDatasource.url = [url]
spring.secondDatasource.username = [username]
spring.secondDatasource.password = [password]
spring.secondDatasource.driverClassName = oracle.jdbc.OracleDriver

Add in any class annotated with @Configuration the following methods:

@Bean
@Primary
@ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix="spring.secondDatasource")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

Solution 2

Update 2022-05-29 with Spring Boot 1.5.8.RELEASE which should work with Spring Boot 2.x

Most answers do not provide how to use them (as datasource itself and as transaction), only how to config them.

Moreover you should know how to commit/rollback transactions of both datasources at the same time.

You can see the runnable example and some explanation in https://github.com/surasint/surasint-examples/tree/master/spring-boot-jdbi/10_spring-boot-two-databases (see what you can try in README.txt)

I copied some code here.

First you have to set application.properties like this

#Database
database1.datasource.url=jdbc:mysql://localhost/testdb
database1.datasource.username=root
database1.datasource.password=root
database1.datasource.driver-class-name=com.mysql.jdbc.Driver

database2.datasource.url=jdbc:mysql://localhost/testdb2
database2.datasource.username=root
database2.datasource.password=root
database2.datasource.driver-class-name=com.mysql.jdbc.Driver

Then define them as providers (@Bean) like this:

@Bean(name = "datasource1")
@ConfigurationProperties("database1.datasource")
@Primary
public DataSource dataSource(){
    return DataSourceBuilder.create().build();
}

@Bean(name = "datasource2")
@ConfigurationProperties("database2.datasource")
public DataSource dataSource2(){
    return DataSourceBuilder.create().build();
}

Note that I have @Bean(name="datasource1") and @Bean(name="datasource2"), then you can use it when we need datasource as @Qualifier("datasource1") and @Qualifier("datasource2") , for example

@Qualifier("datasource1")
@Autowired
private DataSource dataSource;

If you do care about transaction, you have to define DataSourceTransactionManager for both of them, like this:

@Bean(name="tm1")
@Autowired
@Primary
DataSourceTransactionManager tm1(@Qualifier ("datasource1") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

@Bean(name="tm2")
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource2") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

Then you can use it like

@Transactional //this will use the first datasource because it is @primary

or

@Transactional("tm2")

The most important part, which you will hardly find an example in anywhere: if you want a method to commit/rollback transactions of both databases, you need ChainedTransactionManager for tm1 and tm2 , like this:

@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager getChainedTransactionManager(@Qualifier ("tm1") DataSourceTransactionManager tm1, @Qualifier ("tm2") DataSourceTransactionManager tm2){
    return new ChainedTransactionManager(tm1, tm2);
}

To use it, add this annotation in a method @Transactional(value="chainedTransactionManager") for example

@Transactional(value="chainedTransactionManager")
public void insertAll() {
    UserBean test = new UserBean();
    test.setUsername("username" + new Date().getTime());
    userDao.insert(test);

    userDao2.insert(test);
}

This should be enough. See example and detail in the link above.

Solution 3

Refer the official documentation


Creating more than one data source works same as creating the first one. You might want to mark one of them as @Primary if you are using the default auto-configuration for JDBC or JPA (then that one will be picked up by any @Autowired injections).

@Bean
@Primary
@ConfigurationProperties(prefix="datasource.primary")
public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

Solution 4

I also had to setup connection to 2 datasources from Spring Boot application, and it was not easy - the solution mentioned in the Spring Boot documentation didn't work. After a long digging through the internet I made it work and the main idea was taken from this article and bunch of other places.

The following solution is written in Kotlin and works with Spring Boot 2.1.3 and Hibernate Core 5.3.7. Main issue was that it was not enough just to setup different DataSource configs, but it was also necessary to configure EntityManagerFactory and TransactionManager for both databases.

Here is config for the first (Primary) database:

@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = "firstDbEntityManagerFactory",
    transactionManagerRef = "firstDbTransactionManager",
    basePackages = ["org.path.to.firstDb.domain"]
)
@EnableTransactionManagement
class FirstDbConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.firstDb")
    fun firstDbDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Primary
    @Bean(name = ["firstDbEntityManagerFactory"])
    fun firstDbEntityManagerFactory(
        builder: EntityManagerFactoryBuilder,
        @Qualifier("firstDbDataSource") dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean {
        return builder
            .dataSource(dataSource)
            .packages(SomeEntity::class.java)
            .persistenceUnit("firstDb")
            // Following is the optional configuration for naming strategy
            .properties(
                singletonMap(
                    "hibernate.naming.physical-strategy",
                    "org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl"
                )
            )
            .build()
    }

    @Primary
    @Bean(name = ["firstDbTransactionManager"])
    fun firstDbTransactionManager(
        @Qualifier("firstDbEntityManagerFactory") firstDbEntityManagerFactory: EntityManagerFactory
    ): PlatformTransactionManager {
        return JpaTransactionManager(firstDbEntityManagerFactory)
    }
}

And this is config for second database:

@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = "secondDbEntityManagerFactory",
    transactionManagerRef = "secondDbTransactionManager",
    basePackages = ["org.path.to.secondDb.domain"]
)
@EnableTransactionManagement
class SecondDbConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.secondDb")
    fun secondDbDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Bean(name = ["secondDbEntityManagerFactory"])
    fun secondDbEntityManagerFactory(
        builder: EntityManagerFactoryBuilder,
        @Qualifier("secondDbDataSource") dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean {
        return builder
            .dataSource(dataSource)
            .packages(EntityFromSecondDb::class.java)
            .persistenceUnit("secondDb")
            .build()
    }

    @Bean(name = ["secondDbTransactionManager"])
    fun secondDbTransactionManager(
        @Qualifier("secondDbEntityManagerFactory") secondDbEntityManagerFactory: EntityManagerFactory
    ): PlatformTransactionManager {
        return JpaTransactionManager(secondDbEntityManagerFactory)
    }
}

The properties for datasources are like this:

spring.datasource.firstDb.jdbc-url=
spring.datasource.firstDb.username=
spring.datasource.firstDb.password=

spring.datasource.secondDb.jdbc-url=
spring.datasource.secondDb.username=
spring.datasource.secondDb.password=

Issue with properties was that I had to define jdbc-url instead of url because otherwise I had an exception.

p.s. Also you might have different naming schemes in your databases, which was the case for me. Since Hibernate 5 does not support all previous naming schemes, I had to use solution from this answer - maybe it will also help someone as well.

Solution 5

Here is the Complete solution

#First Datasource (DB1)
db1.datasource.url: url
db1.datasource.username:user
db1.datasource.password:password

#Second Datasource (DB2)
db2.datasource.url:url
db2.datasource.username:user
db2.datasource.password:password

Since we are going to get access two different databases (db1, db2), we need to configure each data source configuration separately like:

public class DB1_DataSource {
@Autowired
private Environment env;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean db1EntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(db1Datasource());
    em.setPersistenceUnitName("db1EntityManager");
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<string, object=""> properties = new HashMap<>();
    properties.put("hibernate.dialect",
            env.getProperty("hibernate.dialect"));
    properties.put("hibernate.show-sql",
            env.getProperty("jdbc.show-sql"));
    em.setJpaPropertyMap(properties);
    return em;
}

@Primary
@Bean
public DataSource db1Datasource() {

    DriverManagerDataSource dataSource
            = new DriverManagerDataSource();
    dataSource.setDriverClassName(
            env.getProperty("jdbc.driver-class-name"));
    dataSource.setUrl(env.getProperty("db1.datasource.url"));
    dataSource.setUsername(env.getProperty("db1.datasource.username"));
    dataSource.setPassword(env.getProperty("db1.datasource.password"));

    return dataSource;
}

@Primary
@Bean
public PlatformTransactionManager db1TransactionManager() {

    JpaTransactionManager transactionManager
            = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(
            db1EntityManager().getObject());
    return transactionManager;
}
}

Second Datasource :

public class DB2_DataSource {

@Autowired
private Environment env;

@Bean
public LocalContainerEntityManagerFactoryBean db2EntityManager() {
    LocalContainerEntityManagerFactoryBean em
            = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(db2Datasource());
    em.setPersistenceUnitName("db2EntityManager");
    HibernateJpaVendorAdapter vendorAdapter
            = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<string, object=""> properties = new HashMap<>();
    properties.put("hibernate.dialect",
            env.getProperty("hibernate.dialect"));
    properties.put("hibernate.show-sql",
            env.getProperty("jdbc.show-sql"));
    em.setJpaPropertyMap(properties);
    return em;
}

@Bean
public DataSource db2Datasource() {
    DriverManagerDataSource dataSource
            = new DriverManagerDataSource();
    dataSource.setDriverClassName(
            env.getProperty("jdbc.driver-class-name"));
    dataSource.setUrl(env.getProperty("db2.datasource.url"));
    dataSource.setUsername(env.getProperty("db2.datasource.username"));
    dataSource.setPassword(env.getProperty("db2.datasource.password"));

    return dataSource;
}

@Bean
public PlatformTransactionManager db2TransactionManager() {
    JpaTransactionManager transactionManager
            = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(
            db2EntityManager().getObject());
    return transactionManager;
}
}

Here you can find the complete Example on my blog : Spring Boot with Multiple DataSource Configuration

Share:
409,012

Related videos on Youtube

juventus
Author by

juventus

Updated on November 06, 2021

Comments

  • juventus
    juventus over 2 years

    How can I configure and use two data sources?

    For example here is what I have for the first data source:

    application.properties

    #first db
    spring.datasource.url = [url]
    spring.datasource.username = [username]
    spring.datasource.password = [password]
    spring.datasource.driverClassName = oracle.jdbc.OracleDriver
    
    #second db ...
    

    Application class

    @SpringBootApplication
    public class SampleApplication
    {
        public static void main(String[] args) {
            SpringApplication.run(SampleApplication.class, args);
        }
    }
    

    How do I modify application.properties to add another data source? How do I autowire it to be used by a different repository?

  • K. Siva Prasad Reddy
    K. Siva Prasad Reddy almost 9 years
    Take a look at baeldung.com/spring-data-jpa-multiple-databases which describes the same what you are looking for.
  • Dai Kaixian
    Dai Kaixian over 7 years
    Sometimes you may need to assign datasource, transactionManager, and SqlSessionFactory as primary all.
  • Matley
    Matley about 5 years
    @K. Siva Prasad Reddy OK but I have 2 different JPARepositories - how does Spring Boot know which DataSource to use? Every JPARepository shoudl use different database
  • K. Siva Prasad Reddy
    K. Siva Prasad Reddy about 5 years
    @Matley This blog post javadevjournal.com/spring-boot/… might be what you are looking for.
  • Krish
    Krish almost 5 years
    @K.SivaPrasadReddy to configure multiple datasources all databases should be in same server ?
  • K. Siva Prasad Reddy
    K. Siva Prasad Reddy almost 5 years
    @Krish, It can be in different server.
  • Krish
    Krish almost 5 years
    @K.SivaPrasadReddy I am unable to use in different server. I have created one question stackoverflow.com/questions/56412404/…
  • Codex
    Codex over 4 years
    Will it behave the same for application.yml file? I am trying the same but Datasource is empty without any impact of parameters
  • rvit34
    rvit34 over 4 years
    I had problems with entity and table naming. And it's helped me along with your answer: mapOf("hibernate.physical_naming_strategy" to "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNa‌​mingStrategy", "hibernate.implicit_naming_strategy" to "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNa‌​mingStrategy" )
  • Arun Sudhakaran
    Arun Sudhakaran almost 4 years
    Hi @Surasin Tancharoen, we are trying to keep two data sources with same data so that if one fails, the application will run on the other data source. Will be above approach be fine?
  • Rohit Chaurasiya
    Rohit Chaurasiya almost 4 years
    this code will not run how 2 @primary annotation work together in same db.
  • PAA
    PAA over 3 years
    @K.SivaPrasadReddy - How can we customize datasources to use connection pool and etc properties?
  • im_infamous
    im_infamous over 3 years
    indeed, but this code mentions important part with manual properties setting instead of plain and simple return DataSourceBuilder.create().build(); which is apparently not working, hence my upvote goes here
  • Amir Keshavarz
    Amir Keshavarz almost 3 years
    Not working for me. And saying this: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceI‌​nitializationConfigu‌​ration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.
  • Raja Anbazhagan
    Raja Anbazhagan almost 3 years
    @ArunSudhakaran No. This solution will not work as a backup. If you are looking for high availability, then most databases already have configurations to run multiple databases with a single virtual IP. try that instead.
  • Manoj Shrestha
    Manoj Shrestha over 2 years
    Thank you for the link to the official documentation on this.
  • Avec
    Avec over 2 years
  • Admin
    Admin over 2 years
    @AmirKeshavarz my answer might be a little ... to late, but you have to modify the datasource.url to datasource.jdbc-url and it will work
  • Galley
    Galley over 2 years
    I replace url with jdbcUrl, it works, thank you
  • Andrey
    Andrey about 2 years
    If there are some connection pool specific settings (e.g. HikariCP), for that configuration to be picked up, another bean with @ConfigurationProperties(prefix="spring.datasource.hikari") is required: stackoverflow.com/a/71474522/854386
  • Luis Mauricio
    Luis Mauricio about 2 years
    This fails for me with spring boot spring-boot-starter-parent 1.5.4.RELEASE and tomcat properties, any other configuration I should be aware of?