В этой статье я собираюсь объяснить, как ваше приложение 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
image postgres
5
container_name postgres1
6
volumes
7
./postgres-data1:/var/lib/postgresql/data
8
ports
9
"5432:5432"
10
environment
11
POSTGRES_PASSWORD postgres_user_for_db_write
12
POSTGRES_USER postgres
13
POSTGRES_DB demo
14
db2
15
image postgres
16
container_name postgres2
17
volumes
18
./postgres-data2:/var/lib/postgresql/data
19
ports
20
"5433:5432"
21
environment
22
POSTGRES_PASSWORD postgres_user_for_db_read
23
POSTGRES_USER postgres
24
POSTGRES_DB demo
Из 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-name org.postgresql.Driver
4
jdbc-url jdbc postgresql //localhost 5432/demo
5
username'postgres'
6
password'postgres_pass_for_db_write'
7
platform postgresql
8
hikari
9
idle-timeout10000
10
maximum-pool-size10
11
minimum-idle5
12
pool-name WriteHikariPool
13
datasource-read
15
driver-class-name org.postgresql.Driver
16
jdbc-url jdbc postgresql //localhost 5433/demo
17
username'postgres'
18
password'postgres_pass_for_db_read'
19
platform postgresql
20
hikari
21
idle-timeout10000
22
maximum-pool-size10
23
minimum-idle5
24
pool-name ReadHikariPool
Поскольку и 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
}
Я добавил исходный код проекта в GitHub .
Удачи!
Дальнейшее чтение
Несколько баз данных с классами общих сущностей в Spring Boot и Java