AOP动态数据源切换 实现主从读写分离

前言

MySQL 主从可以更好的减少数据库压力,今天搞了一个小demo,只读接口让他去从库查,用注解方式实现。代码上传至码云:
https://gitee.com/yunup/some-examples

环境信息

Java 版本 1.8
SpringBoot 版本 2.2.3.RELEASE
MySQL主库 test
           从库 test2

编写配置文件

spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/test\?useUnicode=true&characterEncoding=utf-8&useAffectedRows=true\&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai"
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 0
      maximum-pool-size: 20
      idle-timeout: 180000
      
dynamic-data-source:
  enabled: true
  slave:
    url: "jdbc:mysql://localhost:3306/test2\?useUnicode=true&characterEncoding=utf-8&useAffectedRows=true\&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai"
    username: root
    password: root

动态数据源类

创建 DynamicDataSource 类,继承自 AbstractRoutingDataSource ,用于判断使用哪个数据源

public class DynamicDataSource extends AbstractRoutingDataSource {

  private static final ThreadLocal<DataSourceType> dataSourceTypeThreadLocal = new ThreadLocal<>();

  public static void removeDataSourceType() {
    dataSourceTypeThreadLocal.remove();
  }

  public static void setDataSourceType(DataSourceType dataSourceType) {
    if (dataSourceTypeThreadLocal.get() == null) {
      dataSourceTypeThreadLocal.set(dataSourceType);
    }
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return dataSourceTypeThreadLocal.get() == null ? DataSourceType.Master : dataSourceTypeThreadLocal.get();
  }
}

多数据源配置

首先,配置多个数据源,配置文件中 dynamic-data-source的enabled为true,表示开启动态数据源,则注入主数据源和从数据源:

  @Bean({"masterDataSource"})
  @ConditionalOnProperty(name = {"dynamic-data-source.enabled"}, havingValue = "true")
  public DataSource masterDataSource() throws CloneNotSupportedException {
    this.log.info("init masterDataSource...");
    // 此处省略设置参数的代码
    return new HikariDataSource(properties);
  }

  @Bean({"slaverDataSource"})
  @ConditionalOnProperty(name = {"dynamic-data-source.enabled"}, havingValue = "true")
  public DataSource slaverDataSource() throws CloneNotSupportedException {
    this.log.info("init slaverDataSource...");
    // 此处省略设置参数的代码
    return new HikariDataSource(properties);
  }

  @Bean
  @Primary
  @DependsOn({"masterDataSource", "slaverDataSource"})
  @ConditionalOnProperty(name = {"dynamic-data-source.enabled"}, havingValue = "true")
  public DynamicDataSource dataSource(DataSource masterDataSource, DataSource slaverDataSource) {
    this.log.info("init dynamicDataSource...");
    DynamicDataSource source = new DynamicDataSource();
    Map<Object, Object> map = new HashMap<>();
    map.put(DataSourceType.Master, masterDataSource);
    map.put(DataSourceType.Slave, slaverDataSource);
    source.setTargetDataSources(map);
    source.setDefaultTargetDataSource(masterDataSource);
    return source;
  }

创建注解类

创建两个注解类,如果是查询接口,接口上就标注 DBReadOnly,让他去查询从库

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DBReadOnly {

}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DBWriteable {
  
}

编写切面代码

@Aspect
@Component
@ConditionalOnProperty(name = {"dynamic-data-source.enabled"}, havingValue = "true")
public class DataSourceAspect {

  @Around("@annotation(annotation)")
  public Object processDBReadOnly(ProceedingJoinPoint joinPoint, DBReadOnly annotation) {
    DynamicDataSource.setDataSourceType(DataSourceType.Slave);
    Object var3 = null;
    try {
      var3 = joinPoint.proceed();
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    } finally {
      DynamicDataSource.removeDataSourceType();
    }
    return var3;
  }

  @Around("@annotation(annotation)")
  public Object processDBWriteable(ProceedingJoinPoint joinPoint, DBWriteable annotation) {
    DynamicDataSource.setDataSourceType(DataSourceType.Master);
    Object var3 = null;
    try {
      var3 = joinPoint.proceed();
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    } finally {
      DynamicDataSource.removeDataSourceType();
    }
    return var3;
  }
}

测试效果

在控制器上对于两个请求进行标注,findAll 请求去查 test(主) 数据库,findAllSecond 去查 test2(从) 数据库。

  @GetMapping("/findAll")
  @DBWriteable
  public List<Users> findAll() {
    List<Users> users = usersService.findAll();
    return users;
  }

  @GetMapping("/findAllSecond")
  @DBReadOnly
  public List<Users> findAllSecond() {
    List<Users> users = usersService.findAll();
    return users;
  }

findAll 结果:[{“userId”:1,“userName”:“张三”},{“userId”:2,“userName”:“李四”},{“userId”:3,“userName”:“王五”}]
findAllSecond 结果: [{“userId”:1,“userName”:“Lucy”},{“userId”:2,“userName”:“Dick”},{“userId”:3,“userName”:“Angel”}]

编码中,遇到了循环依赖的问题,可见:https://blog.csdn.net/K_Tang/article/details/83117354

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值