MySQL 读写分离后的 Spring 设置
在 Spring Boot 中适配主从复制的 MySQL 数据库,可以通过以下步骤实现读写分离。主要思路是使用不同的数据源来分别处理读和写操作。
1. 配置数据源
你需要配置两个数据源:一个用于主库(写操作),一个用于从库(读操作)。
# application.yml
spring:
datasource:
master:
url: jdbc:mysql://master-db-url:3306/dbname
username: master_user
password: master_password
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://slave-db-url:3306/dbname
username: slave_user
password: slave_password
driver-class-name: com.mysql.cj.jdbc.Driver
2. 创建数据源配置类
使用 @Configuration 注解创建数据源配置类,定义主从数据源的 Bean。
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "masterDataSourceProperties")
@ConfigurationProperties("spring.datasource.master")
public DataSourceProperties masterDataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "slaveDataSourceProperties")
@ConfigurationProperties("spring.datasource.slave")
public DataSourceProperties slaveDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean(name = "masterDataSource")
public DataSource masterDataSource() {
return masterDataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean(name = "slaveDataSource")
public DataSource slaveDataSource() {
return slaveDataSourceProperties().initializeDataSourceBuilder().build();
}
@Bean
public DataSource routingDataSource() {
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType(); // 自定义上下文获取数据源
}
};
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
3. 实现数据源上下文
创建一个上下文来存储当前使用的数据源类型。
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clear() {
contextHolder.remove();
}
}
4. AOP 切面配置
使用 AOP 切面来在方法执行前设置数据源类型,根据读写操作选择主库或从库。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(ReadOnly)") // 自定义注解标记读取方法
public void setReadDataSource() {
DataSourceContextHolder.setDataSourceType("slave");
}
@Before("@annotation(Write)") // 自定义注解标记写入方法
public void setWriteDataSource() {
DataSourceContextHolder.setDataSourceType("master");
}
}
5. 自定义注解
定义读写操作的注解,用于 AOP 切面识别。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Write {}
6. 在服务中使用注解
在服务层的方法上使用自定义注解,标识该方法是读操作还是写操作。
@Service
public class UserService {
@ReadOnly
public User getUserById(Long id) {
// 查询逻辑
}
@Write
public void createUser(User user) {
// 写入逻辑
}
}
License:
CC BY 4.0