В этой статье я собираюсь объяснить, как ваше приложение Spring Boot может взаимодействовать с несколькими источниками данных и не обязательно одного и того же типа (Postgres для этой демонстрации), но его можно применять ко всем другим реляционным базам данных. Есть случаи, когда вам нужно иметь несколько источников данных от разных поставщиков, но общая концепция схожа, и этот пример может быть полезен с некоторыми изменениями в конфигурации проекта (application.yml).
Для этой демонстрации я выбрал PostgresSQL Data Replication, которая часто используется в высоконагруженной базе данных с большим трафиком в приложении.
Иногда даже при наличии лучшей базы данных (PostgresSQL, Oracle, MySQL, …) настройка не может быть настолько же полезной, как при разделении чтения базы данных и записи базы данных на уровне приложения.
Настройка Postgres
Для этой демонстрации вам понадобятся две отдельные базы данных Postgres, одна из которых является главной, а другая — репликой.
Я использовал две базы данных PostgresSQL, которые работают на моем локальном Docker на двух отдельных портах: 5432 и 5433.
Вы также можете прочитать:
Настройка нескольких источников данных с помощью Spring Boot и Spring Data в PCF
Для простоты, просто запустите:  docker-compose up --force-recreate.
Он  docker-compose.ymlуже находится в проекте, который содержит две базы данных PostgresSQL в двух разных портах с  demo базой данных.
Примечание : вы всегда можете удалить его как:  docker-compose down если вам нужно.
YAML
xxxxxxxxxx
1
version'3.1'
2
services
3
  db1
4
    imagepostgres
5
    container_namepostgres1
6
    volumes
7
./postgres-data1:/var/lib/postgresql/data
8
    ports
9
"5432:5432"
10
    environment
11
      POSTGRES_PASSWORDpostgres_user_for_db_write
12
      POSTGRES_USERpostgres
13
      POSTGRES_DBdemo
14
  db2
15
    imagepostgres
16
    container_namepostgres2
17
    volumes
18
./postgres-data2:/var/lib/postgresql/data
19
    ports
20
"5433:5432"
21
    environment
22
      POSTGRES_PASSWORDpostgres_user_for_db_read
23
      POSTGRES_USERpostgres
24
      POSTGRES_DBdemo
 Настройка Spring Boot
Из https://start.spring.io/ выберите web, data-jpa, lombok, postgresDriver.
После того, как вы сгенерируете и загрузите zip-файл, вы должны иметь такой же POM-файл, как:
XML
xxxxxxxxxx
1
 
2
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
    <modelVersion>4.0.0</modelVersion>
5
    <parent>
6
        <groupId>org.springframework.boot</groupId>
7
        <artifactId>spring-boot-starter-parent</artifactId>
8
        <version>2.2.2.RELEASE</version>
9
        <relativePath></relativePath> <!-- lookup parent from repository -->
10
    </parent>
11
    <groupId>com.example</groupId>
12
    <artifactId>demo</artifactId>
13
    <version>0.0.1-SNAPSHOT</version>
14
    <name>demo</name>
15
    <description>Demo project for Spring Boot</description>
16
    <properties>
18
        <java.version>1.8</java.version>
19
    </properties>
20
    <dependencies>
22
        <dependency>
23
            <groupId>org.springframework.boot</groupId>
24
            <artifactId>spring-boot-starter-web</artifactId>
25
        </dependency>
26
        <dependency>
27
            <groupId>org.springframework.boot</groupId>
28
            <artifactId>spring-boot-starter-data-jpa</artifactId>
29
        </dependency>
30
        <dependency>
31
            <groupId>org.springframework.boot</groupId>
32
            <artifactId>spring-boot-configuration-processor</artifactId>
33
        </dependency>
34
        <dependency>
35
            <groupId>org.projectlombok</groupId>
36
            <artifactId>lombok</artifactId>
37
            <optional>true</optional>
38
        </dependency>
39
        <dependency>
40
            <groupId>org.postgresql</groupId>
41
            <artifactId>postgresql</artifactId>
42
            <scope>runtime</scope>
43
        </dependency>
44
        <dependency>
45
            <groupId>org.springframework.boot</groupId>
46
            <artifactId>spring-boot-starter-test</artifactId>
47
            <scope>test</scope>
48
            <exclusions>
49
                <exclusion>
50
                    <groupId>org.junit.vintage</groupId>
51
                    <artifactId>junit-vintage-engine</artifactId>
52
                </exclusion>
53
            </exclusions>
54
        </dependency>
55
    </dependencies>
56
    <build>
57
        <plugins>
58
            <plugin>
59
                <groupId>org.springframework.boot</groupId>
60
                <artifactId>spring-boot-maven-plugin</artifactId>
61
            </plugin>
62
        </plugins>
63
    </build>
64
</project>
pom.xml
Для этой демонстрации я использовал HikariDataSource в качестве библиотеки пулов соединений по умолчанию в Spring Boot 2.2.2.
Нам нужно иметь два отдельных источника данных и EntityManager, один для записи (Master / Primary) и один для чтения (Slave / Secondary).
application.yml
 
YAML
xxxxxxxxxx
1
spring
2
  datasource-write
3
    driver-class-nameorg.postgresql.Driver
4
    jdbc-urljdbcpostgresql//localhost5432/demo
5
    username'postgres'
6
    password'postgres_pass_for_db_write'
7
    platformpostgresql
8
    hikari
9
      idle-timeout10000
10
      maximum-pool-size10
11
      minimum-idle5
12
      pool-nameWriteHikariPool
13
  datasource-read
15
    driver-class-nameorg.postgresql.Driver
16
    jdbc-urljdbcpostgresql//localhost5433/demo
17
    username'postgres'
18
    password'postgres_pass_for_db_read'
19
    platformpostgresql
20
    hikari
21
      idle-timeout10000
22
      maximum-pool-size10
23
      minimum-idle5
24
      pool-nameReadHikariPool
Поскольку и DataSourceConfigWrite, и DataSourceConfigRead получают свои конфиги от «spring.datasource-write» и «spring.datasource-read», entityManagerFactory для каждого источника данных не может получить конфигурации JPA из application.yml . Вот почему конфигурации JPA добавляются позже из статического свойства « JPA_PROPERTIES ». Вы также можете добавить независимые @ConfigurationProperties («spring.jpa»), чтобы предоставить свои конфигурации JPA на основе вашего весеннего профиля .
Как видите, у меня есть два источника данных: datasource-write и datasource-read со своими учетными данными.
Конфигурации источника данных для WriteDB:
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource.configurations;
2
import com.zaxxer.hikari.HikariConfig;
4
import com.zaxxer.hikari.HikariDataSource;
5
import org.hibernate.jpa.HibernatePersistenceProvider;
6
import org.springframework.boot.context.properties.ConfigurationProperties;
7
import org.springframework.context.annotation.Bean;
8
import org.springframework.context.annotation.Configuration;
9
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10
import org.springframework.orm.jpa.JpaTransactionManager;
11
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12
import org.springframework.transaction.PlatformTransactionManager;
13
import org.springframework.transaction.annotation.EnableTransactionManagement;
14
import javax.persistence.EntityManagerFactory;
16
import static com.ehsaniara.multidatasource.DemoApplication.JPA_PROPERTIES;
18
import static com.ehsaniara.multidatasource.DemoApplication.MODEL_PACKAGE;
19
/**
21
 * @author Jay Ehsaniara, Dec 30 2019
22
 */
23
24
("spring.datasource-write")
25
26
(
27
        entityManagerFactoryRef = "entityManagerFactoryWrite",
28
        transactionManagerRef = "transactionManagerWrite",
29
        basePackages = {"com.ehsaniara.multidatasource.repository.writeRepository"}
30
)
31
public class DataSourceConfigWrite extends HikariConfig {
32
    public final static String PERSISTENCE_UNIT_NAME = "write";
34
    
36
    public HikariDataSource dataSourceWrite() {
37
        return new HikariDataSource(this);
38
    }
39
    
41
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryWrite(
42
            final HikariDataSource dataSourceWrite) {
43
        return new LocalContainerEntityManagerFactoryBean() {{
45
            setDataSource(dataSourceWrite);
46
            setPersistenceProviderClass(HibernatePersistenceProvider.class);
47
            setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
48
            setPackagesToScan(MODEL_PACKAGE);
49
            setJpaProperties(JPA_PROPERTIES);
50
        }};
51
    }
52
    
54
    public PlatformTransactionManager transactionManagerWrite(EntityManagerFactory entityManagerFactoryWrite) {
55
        return new JpaTransactionManager(entityManagerFactoryWrite);
56
    }
57
}
Конфигурации источника данных для ReadDB:
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource.configurations;
2
import com.zaxxer.hikari.HikariConfig;
4
import com.zaxxer.hikari.HikariDataSource;
5
import org.hibernate.jpa.HibernatePersistenceProvider;
6
import org.springframework.boot.context.properties.ConfigurationProperties;
7
import org.springframework.context.annotation.Bean;
8
import org.springframework.context.annotation.Configuration;
9
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10
import org.springframework.orm.jpa.JpaTransactionManager;
11
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
12
import org.springframework.transaction.PlatformTransactionManager;
13
import org.springframework.transaction.annotation.EnableTransactionManagement;
14
import javax.persistence.EntityManagerFactory;
16
import static com.ehsaniara.multidatasource.DemoApplication.JPA_PROPERTIES;
18
import static com.ehsaniara.multidatasource.DemoApplication.MODEL_PACKAGE;
19
/**
21
 * @author Jay Ehsaniara, Dec 30 2019
22
 */
23
24
("spring.datasource-read")
25
26
(
27
        entityManagerFactoryRef = "entityManagerFactoryRead",
28
        transactionManagerRef = "transactionManagerRead",
29
        basePackages = {"com.ehsaniara.multidatasource.repository.readRepository"}
30
)
31
public class DataSourceConfigRead extends HikariConfig {
32
    public final static String PERSISTENCE_UNIT_NAME = "read";
34
    
37
    public HikariDataSource dataSourceRead() {
38
        return new HikariDataSource(this);
39
    }
40
    
42
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryRead(
43
            final HikariDataSource dataSourceRead) {
44
        return new LocalContainerEntityManagerFactoryBean() {{
46
            setDataSource(dataSourceRead);
47
            setPersistenceProviderClass(HibernatePersistenceProvider.class);
48
            setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
49
            setPackagesToScan(MODEL_PACKAGE);
50
            setJpaProperties(JPA_PROPERTIES);
51
        }};
52
    }
53
    
55
    public PlatformTransactionManager transactionManagerRead(EntityManagerFactory entityManagerFactoryRead) {
56
        return new JpaTransactionManager(entityManagerFactoryRead);
57
    }
58
}
Репозитории Read и Write должны быть в отдельном пакете:
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource.repository.writeRepository;
2
import com.ehsaniara.multidatasource.model.Customer;
4
import org.springframework.data.repository.CrudRepository;
5
/**
7
 * @author Jay Ehsaniara, Dec 30 2019
8
 */
9
public interface CustomerWriteRepository extends CrudRepository<Customer, Long> {
10
}
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource.repository.readRepository;
2
import com.ehsaniara.multidatasource.model.Customer;
4
import org.springframework.data.repository.CrudRepository;
5
/**
7
 * @author Jay Ehsaniara, Dec 30 2019
8
 */
9
public interface CustomerReadRepository extends CrudRepository<Customer, Long> {
10
}
Вам также необходимо установить:
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource;
2
import org.springframework.boot.SpringApplication;
4
import org.springframework.boot.autoconfigure.SpringBootApplication;
5
import java.util.Properties;
7
9
public class DemoApplication {
10
    public final static String MODEL_PACKAGE = "com.ehsaniara.multidatasource.model";
12
    public final static Properties JPA_PROPERTIES = new Properties() {{
14
        put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL10Dialect");
15
        put("hibernate.hbm2ddl.auto", "update");
16
        put("hibernate.ddl-auto", "update");
17
        put("show-sql", "true");
18
    }};
19
    public static void main(String[] args) {
22
        SpringApplication.run(DemoApplication.class, args);
23
    }
24
}
И фактическая логика находится на уровне сервиса:
Джава
xxxxxxxxxx
1
package com.ehsaniara.multidatasource.service;
2
import com.ehsaniara.multidatasource.model.Customer;
4
import com.ehsaniara.multidatasource.repository.readRepository.CustomerReadRepository;
5
import com.ehsaniara.multidatasource.repository.writeRepository.CustomerWriteRepository;
6
import org.springframework.stereotype.Service;
7
import org.springframework.util.Assert;
8
import java.util.Optional;
10
/**
12
 * @author Jay Ehsaniara, Dec 30 2019
13
 */
14
15
public class CustomerServiceImpl implements CustomerService {
16
    private final CustomerReadRepository customerReadRepository;
18
    private final CustomerWriteRepository customerWriteRepository;
19
    public CustomerServiceImpl(CustomerReadRepository customerReadRepository, CustomerWriteRepository customerWriteRepository) {
21
        this.customerReadRepository = customerReadRepository;
22
        this.customerWriteRepository = customerWriteRepository;
23
    }
24
    public Optional<Customer> getCustomer(Long id) {
26
        return customerReadRepository.findById(id);
27
    }
28
    public Customer createCustomer(Customer customer) {
30
        Assert.notNull(customer, "Invalid customer");
32
        Assert.isNull(customer.getId(), "customer id should be null");
33
        Assert.notNull(customer.getName(), "Invalid customer name");
34
        return customerWriteRepository.save(customer);
36
    }
37
    public Customer updateCustomer(Customer customer) {
39
        Assert.notNull(customer, "Invalid customer");
41
        Assert.notNull(customer.getId(), "Invalid customer id");
42
        return customerWriteRepository.save(customer);
44
    }
45
}
Когда вы запускаете приложение, вы можете видеть два постоянных блока как «Чтение» и «Запись».
Теперь, если выполняется, в этой строке вы создаете клиента в DB1, чтобы создать зарегистрированного клиента (запись в DB1):
Оболочка
xxxxxxxxxx
1
curl -H "Content-Type: application/json" --request POST --data '{"name":"Jay"}'   http://localhost:8080/customer
ИЛИ ЖЕ
обновить клиента по записи (запись в DB1):
Оболочка
xxxxxxxxxx
1
curl -H "Content-Type: application/json" --request PUT --data '{"id":1 , "name":"Jay ehsaniara"}'   http://localhost:8080/customer
Но если вы запустите эту строку, вы получите данные из DB2 для чтения из DB2:
Оболочка
xxxxxxxxxx
1
 curl --request GET  http://localhost:8080/customer/1
Примечание: Вам нужно вставить клиента в DB2 вручную, так как у него нет предварительного клиента. И мы еще не настроили репликацию Postgres.
Я добавил исходный код проекта в GitHub .
Удачи!
Дальнейшее чтение
 Несколько баз данных с классами общих сущностей в Spring Boot и Java