Expand Authors Top

If you have a few years of experience in the Java ecosystem and you’d like to share that with the community, have a look at our Contribution Guidelines.

November Discount Launch 2022 – Top
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

November Discount Launch 2022 – TEMP TOP (NPI)
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Expanded Audience – Frontegg – Security (partner)
announcement - icon User management is very complex, when implemented properly. No surprise here.

Not having to roll all of that out manually, but instead integrating a mature, fully-fledged solution - yeah, that makes a lot of sense.
That's basically what Frontegg is - User Management for your application. It's focused on making your app scalable, secure and enjoyable for your users.
From signup to authentication, it supports simple scenarios all the way to complex and custom application logic.

Have a look:

>> Elegant User Management, Tailor-made for B2B SaaS

1. Overview

The typical scenario for a Spring Boot application is to store data in a single relational database. But we sometimes need to access multiple databases.

In this tutorial, we'll learn how to configure and use multiple data sources with Spring Boot.

To find out how to deal with a single data source, check out our introduction to Spring Data JPA.

2. Default Behavior

Let's remember what declaring a data source in Spring Boot looks like in application.yml:

spring:
  datasource:
    url: ...
    username: ...
    password: ...
    driverClassname: ...

Internally, Spring maps these settings to an instance of org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.

Let's take a look into the implementation:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

    // ...

    /**
     * Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
     */
    private String driverClassName;

    /**
     * JDBC URL of the database.
     */
    private String url;

    /**
     * Login username of the database.
     */
    private String username;

    /**
     * Login password of the database.
     */
    private String password;

    // ...

}

We should point out the @ConfigurationProperties annotation that maps the properties of the configuration to the Java object automatically.

3. Extending the Defaults

So, to use multiple data sources, we need to declare multiple beans with different mappings within Spring's application context.

We can do this by using a configuration class:

@Configuration
public class TodoDatasourceConfiguration {

    @Bean
    @ConfigurationProperties("spring.datasource.todos")
    public DataSourceProperties todosDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.topics")
    public DataSourceProperties topicsDataSourceProperties() {
        return new DataSourceProperties();
    }

}

The configuration for the data sources must look like this:

spring:
  datasource:
    todos:
      url: ...
      username: ...
      password: ...
      driverClassName: ...
    topics:
      url: ...
      username: ...
      password: ...
      driverClassName: ...

Then we can create the data sources by using the DataSourceProperties objects:

@Bean
public DataSource todosDataSource() {
    return todosDataSourceProperties()
      .initializeDataSourceBuilder()
      .build();
}

@Bean
public DataSource topicsDataSource() {
    return topicsDataSourceProperties()
      .initializeDataSourceBuilder()
      .build();
}

4. Spring Data JDBC

When using Spring Data JDBC, we also need to configure one instance of JdbcTemplate for each DataSource:

@Bean
public JdbcTemplate todosJdbcTemplate(@Qualifier("todosDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

@Bean
public JdbcTemplate topicsJdbcTemplate(@Qualifier("topicsDataSource") DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

We can then use them also by specifying a @Qualifier:

@Autowired
@Qualifier("topicsJdbcTemplate")
JdbcTemplate jdbcTemplate;

5. Spring Data JPA

When using Spring Data JPA, we want to use repositories like the following, where Todo is the entity:

public interface TodoRepository extends JpaRepository<Todo, Long> {}

So, we need to declare EntityManager factories for each data source:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
  basePackageClasses = Todo.class,
  entityManagerFactoryRef = "todosEntityManagerFactory",
  transactionManagerRef = "todosTransactionManager"
)
public class TodoJpaConfiguration {

    @Bean
    public LocalContainerEntityManagerFactoryBean todosEntityManagerFactory(
      Qualifier("todosDataSource") DataSource dataSource,
      EntityManagerFactoryBuilder builder) {
        return builder
          .dataSource(todosDataSource())
          .packages(Todo.class)
          .build();
    }

    @Bean
    public PlatformTransactionManager todosTransactionManager(
      @Qualifier("todosEntityManagerFactory") LocalContainerEntityManagerFactoryBean todosEntityManagerFactory) {
        return new JpaTransactionManager(Objects.requireNonNull(todosEntityManagerFactory.getObject()));
    }

}

Let's look at a few restrictions that we should be aware of.

We need to split the packages to allow one @EnableJpaRepositories for each data source.

Unfortunately, to get EntityManagerFactoryBuilder injected, we need to declare one of the data sources as @Primary.

This is because EntityManagerFactoryBuilder is declared in org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration, and this class needs a single data source injected. Usually, some parts of the framework might not expect multiple data sources configured.

6. Configure Hikari Connection Pool

If we want to configure Hikari, we just need to add an @ConfigurationProperties to the data source definition:

@Bean
@ConfigurationProperties("spring.datasource.todos.hikari")
public DataSource todosDataSource() {
    return todosDataSourceProperties()
      .initializeDataSourceBuilder()
      .build();
}

Then we can insert the following lines into the application.properties file:

spring.datasource.todos.hikari.connectionTimeout=30000 
spring.datasource.todos.hikari.idleTimeout=600000 
spring.datasource.todos.hikari.maxLifetime=1800000 

7. Conclusion

In this article, we learned how to configure multiple data sources with Spring Boot.

We saw that we need some configuration and that there might be pitfalls when deviating from the standard but that it is possible in the end.

As always, all the code is available over on GitHub.

November Discount Launch 2022 – Bottom
We’re finally running a Black Friday launch. All Courses are 30% off until next Friday:

>> GET ACCESS NOW

Persistence footer banner
4 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!