" />
文章

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