Spring Boot 2 with Multiple DataSource
Sometimes, even with the best database (PostgreSQL, Oracle, Sql, etc.), tuning cannot separate read and write like application level.
Spring Boot 2.2.2 with multiple data sources
Postgres Setup
For this demo, you need 2 separate Postgres databases, one of which is the Master and the other is the copy.
For simplicity, just run:
docker-composition --force-recreate
Docker-compose.yml has demo database in 2 Postgresql projects with 2 different ports
Note: you can always uninstall it as: docker-compose down if you needed to.
you can clone this project from my github on https://github.com/ehsaniara/spring-boot-multi-data-source
Spring Boot Setup
From https://start.spring.io/ select web, data-jpa, lombok, postgresDriver
After generating and downloading the zip file, you should have a POM file similar to the following:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
For this demo, I use HikariDataSource as the default connection pool library for Spring Boot 2.2.2. We need to have 2 separate DataSource and EntityManager, one for Writes (master server / master server) and the other for Reads (slave server / Second server).
spring:
datasource-write:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:5432/demo
username: 'postgres_user_for_db_write'
password: 'you_password'
platform: postgresql
hikari:
idle-timeout: 10000
maximum-pool-size: 10
minimum-idle: 5
pool-name: WriteHikariPool
datasource-read:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:5433/demo
username: 'postgres_user_for_db_read'
password: 'you_password'
platform: postgresql
hikari:
idle-timeout: 10000
maximum-pool-size: 10
minimum-idle: 5
pool-name: ReadHikariPool
As you can see, I have 2 data sources: datasource-write and datasource-read with their own credentials.
Data source configuration of WriteDB:
@Configuration
@ConfigurationProperties("spring.datasource-write")
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryWrite",
transactionManagerRef = "transactionManagerWrite",
basePackages = {"com.ehsaniara.multidatasource.repository.writeRepository"}
)
public class DataSourceConfigWrite extends HikariConfig {
public final static String PERSISTENCE_UNIT_NAME = "write";
@Bean
public HikariDataSource dataSourceWrite() {
return new HikariDataSource(this);
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryWrite(
final HikariDataSource dataSourceWrite) {
return new LocalContainerEntityManagerFactoryBean() {{
setDataSource(dataSourceWrite);
setPersistenceProviderClass(HibernatePersistenceProvider.class);
setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
setPackagesToScan(MODEL_PACKAGE);
setJpaProperties(JPA_PROPERTIES);
}};
}
@Bean
public PlatformTransactionManager transactionManagerWrite(EntityManagerFactory entityManagerFactoryWrite) {
return new JpaTransactionManager(entityManagerFactoryWrite);
}
}
Data source configuration of ReadDB:
@Configuration
@ConfigurationProperties("spring.datasource-read")
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryRead",
transactionManagerRef = "transactionManagerRead",
basePackages = {"com.ehsaniara.multidatasource.repository.readRepository"}
)
public class DataSourceConfigRead extends HikariConfig {
public final static String PERSISTENCE_UNIT_NAME = "read";
@Bean
public HikariDataSource dataSourceRead() {
return new HikariDataSource(this);
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryRead(
final HikariDataSource dataSourceRead) {
return new LocalContainerEntityManagerFactoryBean() {{
setDataSource(dataSourceRead);
setPersistenceProviderClass(HibernatePersistenceProvider.class);
setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
setPackagesToScan(MODEL_PACKAGE);
setJpaProperties(JPA_PROPERTIES);
}};
}
@Bean
public PlatformTransactionManager transactionManagerRead(EntityManagerFactory entityManagerFactoryRead) {
return new JpaTransactionManager(entityManagerFactoryRead);
}
}
The read-write repository should be placed in a separate package:
写:com.ehsaniara.multidatasource.repository.writeRepository
读:com.ehsaniara.multidatasource.repository.readRepository
You also need to set:
public final static String MODEL_PACKAGE = "com.ehsaniara.multidatasource.model";
public final static Properties JPA_PROPERTIES = new Properties() {{
put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
put("hibernate.hbm2ddl.auto", "update");
put("hibernate.ddl-auto", "update");
put("show-sql", "true");
}};
And the actual logic in the service layer:
@Service
public class CustomerServiceImpl implements CustomerService {
private final CustomerReadRepository customerReadRepository;
private final CustomerWriteRepository customerWriteRepository;
public CustomerServiceImpl(CustomerReadRepository customerReadRepository, CustomerWriteRepository customerWriteRepository) {
this.customerReadRepository = customerReadRepository;
this.customerWriteRepository = customerWriteRepository;
}
public Optional<Customer> getCustomer(Long id) {
return customerReadRepository.findById(id);
}
public Customer createCustomer(Customer customer) {
Assert.notNull(customer, "Invalid customer");
Assert.isNull(customer.getId(), "customer id should be null");
Assert.notNull(customer.getName(), "Invalid customer name");
return customerWriteRepository.save(customer);
}
public Customer updateCustomer(Customer customer) {
Assert.notNull(customer, "Invalid customer");
Assert.notNull(customer.getId(), "Invalid customer id");
return customerWriteRepository.save(customer);
}
}
Now, if you run this line, you will create a customer in DB1:
curl -H "Content-Type: application/json" --request POST --data '{"name":"Jay"}' http://localhost:8080/customer
Or
curl -H "Content-Type: application/json" --request PUT --data '{"id":1 , "name":"Jay ehsaniara"}' http://localhost:8080/customer
However, if you run this line, you can get data from DB2:
curl --request GET http://localhost:8080/customer/1
Note: You need to manually insert customers in DB2, because it has no previous customers. And we have not set up Postgres Replication
from: https://dev.to//ehsaniara/spring-boot-2-with-multiple-datasources-1n9e