Wiring Multiple Datasources in a Spring Boot and Spring Data JPA Application

FifthElement

Having to wire a Spring Boot application to talk to multiple datasources is a requirement you come along every once awhile. The good news is this can be done reasonably easily within Spring Boot and in this article we will walk through how to accomplish this.

Wiring Multiple Datasources Step-by-Step

Pre-Requisites

To run the demo application the following tools are needed:

  • Docker
  • Java

There are also some shell scripts available for building, running, testing, and tearing down the application for *nix OS users.

Application Structure

The example application used in this article has two domain models, Doctor and Clinic, each of which are persisted to their own separate datastores. This application overall is a very standard implementation of Spring Boot and Spring Data. Because there is already a lot of great documentation on how to implement such an application, those steps will be skipped. However for clarity, here is what the overall structure of the application looks like:

com.developer.ibm.multidatasource\
   clinic\
      Clinic
      ClinicsController
      ClinicsDatasourceConfiguration
      ClinicsRepo
   doctor\
      Doctor
      DoctorsController
      DoctorsDatasourceConfiguration
      DoctorsRepo
   MultiDatasourceApplication

The full application can be seen here: https://github.com/wkorando/multi-datasources-spring-boot

Configuring Spring Data

The first step would be to define the @Configuration classes that add the DataSource, PlatformTransactionManager, and LocalContainerEntityManagerFactoryBean to the application context which will be used by Spring Data when communicating with the databases. Both of the configuration classes look essentially identical, with one exception which will be covered in detail below. Let’s step through some of the key elements in these configuration classes:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(entityManagerFactoryRef = "clinicEntityManagerFactory", transactionManagerRef = "clinicTransactionManager")
public class ClinicsDatasourceConfiguration {

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

    @Bean
    PlatformTransactionManager clinicTransactionManager(
            @Qualifier("clinicEntityManagerFactory") LocalContainerEntityManagerFactoryBean clinicEntityManagerFactory) {
        return new JpaTransactionManager(clinicEntityManagerFactory.getObject());
    }

    @Bean
    LocalContainerEntityManagerFactoryBean clinicEntityManagerFactory(
            @Qualifier("clinicsDataSource") DataSource clinicsDatasource, EntityManagerFactoryBuilder builder) {
        return builder.dataSource(clinicsDatasource).packages(Clinic.class).build();
    }
}

@Primary

Outside of name differences, the @Primary added to clinicsDataSource is the only functional difference between ClinicsDatasourceConfiguration and DoctorsDatasourceConfiguration. Adding @Primary to clinicsDataSource is necessary as some of the autoconfiguring behavior within Spring Data depends upon a DataSource being available in the application context. However in our situation there will be two DataSources, available in the application context so adding @Primary to one gives Spring the information it needs on which bean to choose. For this applications purposes, making clinicsDataSource the primary DataSource was an arbitrary decision. However deciding which DataSource should be the primary one might be something worth thinking about depending on the requirements and behavior of your application.

@ConfigurationProperties

Automatically maps the jdbc-url, password, and username properties prefixed with clinics.datasource available in the environment, in this case defined in application.properties (source), and maps them to the DataSource being created in clinicsDataSource. If this feels too “magical” DataSourceBuilder (javadoc) also has standard builder methods; url(String), password(String), username(String) available among others.

Using @ConfigurationProperties helps to keep the behavior, from a developer’s perspective, more consistent with how a Spring Boot application would work if it had only a single DataSource. @ConfigurationProperties could also be useful in other scenarios. Here is an example of using @ConfigurationProperties to map to fields within a configuration class from an earlier version of the example project.

@Qualifier

The arguments for the @Bean methods of clinicTransactionManager and clinicEntityManagerFactory are annotated with @Qualifier. Like with @Primary, @Qualifier tells the Spring which instance of class to use when there are multiple available in the application context. By default the name of a bean is the same name as the method that created the bean.

Defining the Properties

Next we need to provide Spring with the values to connect to both our databases. In this example we are connecting to containerized instances of MySQL and Postgres. We will go into a little more detail on this properties file below:

clinics.datasource.jdbc-url=jdbc:mysql://localhost:3306/clinics-db
clinics.datasource.username=root
clinics.datasource.password=secret

doctors.datasource.jdbc-url=jdbc:postgresql://localhost:5432/doctors-db
doctors.datasource.username=postgres
doctors.datasource.password=secret

spring.jpa.open-in-view=false

jdbc-url

When defining a datasource using the spring.datasource properties the property of url would be used. However with Spring Boot 2, HirkariDataSource became the standard DataSource implementation, so to use @ConfigurationProperties like in the @Configuration classes above, the property needs to be jdbc-url.

More info can be found here, as well as a workaround if you’d prefer to keep using url.

(I plan on going into more depth about this in a future article, as this change caused me a lot of pain while putting together the example application)

spring.jpa.open-in-view

This is ultimately an optional addition. By default Spring Boot sets this property to true which is arguably an anti-pattern, which you can read more about here. Why this is relevant in an article about configuring multiple datasources is that when spring.jpa.open-in-view is set to true, Spring MVC will look for an instance of PlatformTransactionManager and LocalContainerEntityManagerFactoryBean in the application context.

This could had been alternatively resolved by adding @Primary to one of the @Bean definitions of both PlatformTransactionManager and LocalContainerEntityManagerFactoryBean as was done with clinicsDataSource, however disabling spring.jpa.open-in-view should generally be done anyways, so that is a better resolution.

Running the Application

There are several scripts available for running the demo application, added for convenience and experimentation.

  • build.sh – builds the docker images and Java artifact
  • run.sh – starts up the Docker containers and Spring Boot application (note: there is a 15 second sleep between starting the containers and starting the app, to give the containers time to startup)
  • requests.sh – curl commands for POSTing and GETting to the Spring Boot application
  • kill.sh – stops and removes the Docker containers

The application by default runs at http://localhost:8080 with GET/POST endpoints residing at /clinics and /doctors.

Conclusion

With a little work a Spring Boot application can be setup to handle multiple datasources and still have an overall pretty familiar look and feel to it.

The code used in this article can be found here: https://github.com/wkorando/multi-datasources-spring-boot

2 thoughts on “Wiring Multiple Datasources in a Spring Boot and Spring Data JPA Application

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s