java如何使用Spring在readonly和readwrite中进行数据库路由
我在春季研究事务路由,但我的应用程序存在运行时问题
我有两个MySQL数据库,一个用于读取,一个用于读取/写入,但我的路由配置不起作用,当我应用只读配置时,我没有成功
这是我的配置:
pom。xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
</parent>
<groupId>br.com.multidatasources</groupId>
<artifactId>multidatasources</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>multidatasources</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
应用程序。属性
# Database master
master.datasource.url=jdbc:mysql://localhost:3306/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
master.datasource.username=root
master.datasource.password=root
# Database slave
slave.datasource.url=jdbc:mysql://localhost:3307/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
slave.datasource.username=root
slave.datasource.password=root
# Database driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA property settings
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
数据源类型。java
public enum DataSourceType {
READ_ONLY,
READ_WRITE
}
TransactionRoutingDataSource。java
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
}
}
路由配置。java
@Configuration
@EnableTransactionManagement
public class RoutingConfiguration {
private final Environment environment;
public RoutingConfiguration(Environment environment) {
this.environment = environment;
}
@Bean
public JpaTransactionManager transactionManager(@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("routingDataSource") DataSource routingDataSource) {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(routingDataSource);
bean.setPackagesToScan(Billionaires.class.getPackageName());
bean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
bean.setJpaProperties(additionalProperties());
return bean;
}
@Bean
public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
@Bean
public TransactionRoutingDataSource routingDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource
) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
@Bean
public DataSource masterDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("master.datasource.url"));
dataSource.setUsername(environment.getProperty("master.datasource.username"));
dataSource.setPassword(environment.getProperty("master.datasource.password"));
return dataSource;
}
@Bean
public DataSource slaveDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("slave.datasource.url"));
dataSource.setUsername(environment.getProperty("slave.datasource.username"));
dataSource.setPassword(environment.getProperty("slave.datasource.password"));
return dataSource;
}
private Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
return properties;
}
}
亿万富翁。java
@Entity
@Table(name = "billionaires")
public class Billionaires {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
private String career;
public Billionaires() { }
public Billionaires(Long id, String firstName, String lastName, String career) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.career = career;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getCareer() {
return career;
}
public void setCareer(String career) {
this.career = career;
}
}
亿万富翁储蓄。java
@Repository
public interface BillionairesRepository extends JpaRepository<Billionaires, Long> {
}
亿万航空服务。java
@Service
public class BillionairesService {
private final BillionairesRepository billionairesRepository;
public BillionairesService(BillionairesRepository billionairesRepository) {
this.billionairesRepository = billionairesRepository;
}
@Transactional(readOnly = true) // Should be used the READ_ONLY (This point not working)
public List<Billionaires> findAll() {
return billionairesRepository.findAll();
}
@Transactional // Should be used the READ_WRITE
public Billionaires save(Billionaires billionaires) {
return billionairesRepository.save(billionaires);
}
}
在亿万富翁服务类中,我在findAll
方法上应用了@Transactional(readOnly = true)
来使用READ_ONLY
数据源,但这并没有发生
findAll
方法应用于READ_ONLY
数据源,而save
方法应用于READ_WRITE
数据源
有人能帮我解决这个问题吗
# 1 楼答案
我通过更改
RoutingConfiguration.java
类的实现解决了这个问题我将数据源配置为使用
setAutoCommit(false)
配置,并添加了值为true
的属性hibernate.connection.provider_disables_autocommit
hibernate.connection.provider_disables_autocommit
允许在调用determineCurrentLookupKey
方法之前获取连接# 2 楼答案
我强烈建议您尽可能多地使用自动配置,这样会使事情变得更简单。主键设置为延迟连接的获取,并为当前事务准备连接
这可以通过两种不同的方式实现
将
JpaDialect
的prepareConnection
属性设置为false
。如果您不这样做,JpaTransactionManager
将急切地获取Connection
,并为事务做好准备。这甚至是在它有时间将事务的当前状态设置到TransactionSynchronizationManager
之前。这将使对TransactionSynchronizationManager.isCurrentTransactionReadOnly
的调用始终返回false
(因为它是在JpaTransactionManager
中的doBegin
方法末尾设置的)将
hibernate.connection.handling_mode
设置为DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
。这将延迟连接的获取,并在事务完成后关闭连接。如果没有Spring,这也是Hibernate 5.2+的默认设置(请参见Hibernate User Guide),但出于传统原因,Spring将其切换为DELAYED_ACQUISITION_AND_HOLD
当连接准备延迟时,这些解决方案中的任何一个都将起作用,
JpaTransactionManager
因此有时间同步TransactionSynchronizationManager
中的状态但是,将此属性添加到
application.properties
也可以:使用这些解决方案中的任何一个,您现在都可以放弃事务配置、jpa等。还有一种更简单的方法来配置多个数据源。它在Spring Boot Reference Guide中描述,它将尽可能多地重用Spring自动配置
首先确保以下内容在
application.properties
中注意:删除了JDBC的驱动程序(不需要)仅设置
spring.jpa.database-platform
您可以设置database
或database-platform
而不是同时设置现在有了这个类和下面的
@Configuration
类,您将有2个数据源,路由一个和上面提到的BeanPostProcessor
(如果您选择使用该属性,您可以删除所述的BeanPostProcessor
)这将设置您工作所需的一切,并且仍然能够使用尽可能多的自动配置和检测。有了它,您需要做的唯一配置就是
DataSource
设置。没有JPA,交易管理等,因为这将自动完成最后,这里有一个测试来测试这一点(您可以测试这两个场景)。只读模式将失败,因为那里没有模式,保存将成功,因为在事物的读写端有一个模式