Spring Boot 2 with multiple data sources

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

Published 0 original articles · liked 0 · visits 417

Guess you like

Origin blog.csdn.net/cunxiedian8614/article/details/105690009