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
Great article
LikeLike
Thanks!
LikeLike
Thanks ! Exactly what I was looking for.
LikeLike