Spring Boot offers an opinionated way to bootstrap applications by favoring auto-configuration and making reasonable assumptions about how your application operates.
To configure a data source using Spring Data, you can add the following properties to application.properties
:
spring.datasource.url=jdbc:mysql://localhost:3306/my_database
spring.datasource.username=sqluser
spring.datasource.password=p@55w0rd
Under the hood, Spring Boot will instantiate and wire up all of the classes needed to connect to your database.
But what happens if you need to connect to multiple databases? In this post, I'll show you how to configure Spring Boot to work with two entities on two separate databases.
Create a New Spring Boot Project
The Spring Initializr can be used to make a new Spring Boot project.
You can also access the Initializr with curl:
curl https://start.spring.io/starter.zip -d dependencies=data-jpa,h2 \
-d language=kotlin \
-d packageName=com.smcrow.demo \
-d artifactId=spring-boot-multiple-datasources \
-d groupId=com.smcrow.demo \
-d baseDir=spring-boot-multiple-datasource | tar -xzvf -
Create the Entities
For this example, you're going to create two entities:
-
com.smcrow.demo.farm.Animal
will be used to access information from theAnimal
table of theFarm
database. -
com.smcrow.demo.factory.Worker
will be used to access information from theWorker
table of theFactory
database.
Create the Animal Entity
First, create a new class to represent an Animal. This class will be created in the com.smcrow.demo.farm
package:
package com.smcrow.demo.farm
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
class Animal(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val name: String
)
@Repository
interface AnimalRepository : JpaRepository<Animal, Long>
I like to put my JpaRepository
interfaces inside of the same file as the class.
Create the Worker Entity
Now, create a new class to represent a Worker. This class will be created in the com.smcrow.demo.factory
package. Separating the Worker
and Animal
classes into different packages will help later with configuration:
package com.smcrow.demo.factory
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
@Entity
class Worker(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
val name: String
)
@Repository
interface WorkerRepository : JpaRepository<Worker, Long>
Add Database Configuration to application.properties
Since the worker and the animal tables exist in different databases, two configuration blocks are necessary.
You can define multiple data sources inside of application.properties
:
# Farm DB Connection
spring.datasource-farm.jdbcUrl=jdbc:h2:mem:farm
spring.datasource-farm.username=sa
spring.datasource-farm.password=
# Factory DB Connection
spring.datasource-factory.jdbcUrl=jdbc:h2:mem:factory
spring.datasource-factory.username=sa
spring.datasource-factory.password=
Register the Database Connections
Normally, Spring Boot expects to find database credentials as part of the spring.datasource.*
configuration prefix. Instead of using the automatic configuration, the data sources must be defined manually.
Create the Farm Data Source Configuration
Create a new class inside of the com.smcrow.demo.farm
package called FarmDataSourceConfiguration
:
package com.smcrow.demo.farm
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.orm.jpa.JpaTransactionManager
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import javax.sql.DataSource
@Configuration
@EnableJpaRepositories(
basePackages = ["com.smcrow.demo.farm"],
entityManagerFactoryRef = "farmEntityManager",
transactionManagerRef = "farmTransactionManager"
)
class FarmDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource-farm")
fun farmDataSource(): DataSource = DataSourceBuilder.create().build()
@Bean
fun farmEntityManager(): LocalContainerEntityManagerFactoryBean =
(LocalContainerEntityManagerFactoryBean()).apply {
dataSource = farmDataSource()
setPackagesToScan("com.smcrow.demo.farm")
jpaVendorAdapter = HibernateJpaVendorAdapter()
}
@Bean
fun farmTransactionManager() = JpaTransactionManager(farmEntityManager().`object`!!)
}
Create the Factory Data Source Configuration
Create a new class inside of the com.smcrow.demo.factory
package called FactoryDataSourceConfiguration
:
package com.smcrow.demo.factory
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.orm.jpa.JpaTransactionManager
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import javax.sql.DataSource
@Configuration
@EnableJpaRepositories(
basePackages = ["com.smcrow.demo.factory"],
entityManagerFactoryRef = "factoryEntityManager",
transactionManagerRef = "factoryTransactionManager"
)
class FactoryDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource-factory")
fun factoryDataSource(): DataSource = DataSourceBuilder.create().build()
@Bean
fun factoryEntityManager(): LocalContainerEntityManagerFactoryBean =
(LocalContainerEntityManagerFactoryBean()).apply {
dataSource = factoryDataSource()
setPackagesToScan("com.smcrow.demo.factory")
jpaVendorAdapter = HibernateJpaVendorAdapter()
}
@Bean
fun factoryTransactionManager() = JpaTransactionManager(factoryEntityManager().`object`!!)
}
More Detail
Now let's break down the configuration classes.
Each configuration class has an @EnableJpaRepositories
annotation. This enables package scanning for interfaces annotated with @Repository
.
The basePackages
is an array of packages that contain the @Repositories
that will be associated with the specific data source being configured.
There are three beans that are created in each configuration class.
The first is the data source which represents a connection to a physical data source. The @ConfigurationProperties
annotation allows the properties from spring.datasource-factory
to be loaded into the DataSource
.
@Bean
@ConfigurationProperties(prefix = "spring.datasource-factory")
fun factoryDataSource(): DataSource = DataSourceBuilder.create().build()
The next bean is the LocalContainerEntityManagerFactoryBean
which is used to create the EntityManagerFactory
@Bean
fun factoryEntityManager(): LocalContainerEntityManagerFactoryBean =
(LocalContainerEntityManagerFactoryBean()).apply {
dataSource = factoryDataSource()
setPackagesToScan("com.smcrow.demo.factory")
jpaVendorAdapter = HibernateJpaVendorAdapter()
}
And the third is the JpaTransactionManager
:
@Bean
fun factoryTransactionManager() = JpaTransactionManager(factoryEntityManager().`object`!!)
Summary
I hope that this helps serve as a quick reference guide for setting up multiple data sources in Spring Boot and Kotlin.
The full code repository can be found on GitHub: cr0wst/spring-boot-multiple-datasource
I've found a few other resources for configuring this in Java, but none of them were written with Kotlin in mind. They can be found here:
Top comments (2)
Thanks! This setup worked for me except that I needed to install jakarta persistence package as I was facing issue with unmet dependency for javax.persistence.
thanks!