@Auto-Annotation自定义注解——动态数据源篇

@Auto-Annotation自定义注解——动态数据源篇

自定义通用注解连更系列—连载中…

首页介绍:点这里

前言

​ 通常一个系统只需要连接一个数据库就可以了。但是在企业应用的开发中往往会和其他子系统交互,特别是对于一些数据实时性要求比较高的数据,我们就需要做实时连接查询,而不是做同步。这个时候就需要用到多数据源。

​ 举个例子,在主从数据库的业务场景中,一个库用来读,一个库用来写,那么在进行数据库读写操作时就需要进行数据库的切换。

所需依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.10</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>5.3.4</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.2.16</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

动态数据源注解@DynamicDb

定义一个动态数据源注解做为标识符

/** 动态数据源注解
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DynamicDb {
    /**
     * 切换数据库源
     */
    DbType value() default DbType.PRIMARY_DB;
}

定义主从库枚举

定义主从库枚举类,可增加多个,则对应配置多个数据源

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Getter
@AllArgsConstructor
public enum DbType {

    /**
     * 主库
     */
    PRIMARY_DB("PRIMARY_DB","主库"),
    /**
     * 从库
     */
    SECOND_DB("SECOND_DB","从库"),
    ;

    private final String value;
    private final String desc;

}

动态数据源配置类

程序运行时初始化数据源配置,默认加载主数据源,按条件加载从数据源,动态添加数据源到这个spring底层的AbstractRoutingDataSource数据源切换路由中,从而实现数据源的切换。

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Configuration
public class DruidConfig {

    /**
     * 加载主数据源
     *
     * @return 主数据源
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 加载从数据源(按条件加载)
     *
     * @return 从数据源
     */
    @Bean("slaveDataSource")
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 配置动态数据源
     *
     * @param masterDataSource 主数据源
     * @return 动态数据源切换配置类
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSourceHandler dataSource(DataSource masterDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DbType.PRIMARY_DB, masterDataSource);
        try {
            Object slaveDataSource = SpringUtil.getBean("slaveDataSource");
            targetDataSources.put(DbType.SECOND_DB, slaveDataSource);
        } catch (Exception e) {
            //未开启从库则会找不到该对象,非异常,这里进行空捕获
        }
        return new DynamicDataSourceHandler(masterDataSource, targetDataSources);
    }
}

动态数据源切换配置

具体实现原理:

  • 1、获取数据库连接getConnection()方法时,调用的是determineTargetDataSource()方法,来创建连接,而determineTargetDataSource()方法是决定spring容器连接哪个数据源。
  • 2、哪个数据源又是由determineCurrentLookupKey()方法来决定的,此方法是抽象方法,需要我们继承AbstractRoutingDataSource抽象类来重写此方法。
  • 3、该方法返回一个key,该key是数据源对象中的beanName,并赋值给lookupKey,由此key可以通过resolvedDataSources属性的键来获取对应的DataSource值,从而达到数据源切换的功能
/** 动态数据源切换配置
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
public class DynamicDataSourceHandler extends AbstractRoutingDataSource {

    public DynamicDataSourceHandler(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 操作数据库前会先获取本地线程的主从库枚举,再根据主从库枚举获取指定数据源
     * @return 主从库枚举
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DbThreadLocal.getType();
    }
}

本地数据源线程

用于存储每个线程需切换的数据源

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
public class DbThreadLocal {

    /**
     * 本地数据源线程,每个线程单独拥有自己的数据源配置(默认为主数据源)
     */
    private static ThreadLocal<DbType> THREAD_LOCAL = new ThreadLocal<>();

    public static void setType(DbType dbType){
        THREAD_LOCAL.set(dbType);
    }

    public static DbType getType(){
        return THREAD_LOCAL.get() == null ? DbType.PRIMARY_DB: THREAD_LOCAL.get();
    }

    public static void cleanType(){
        THREAD_LOCAL.remove();
    }

}

定义数据源切面类

在接口执行前切换指定数据源,存入本地线程中,在创建数据库连接时会执行DynamicDataSourceHandler类中的determineCurrentLookupKey方法,获取本地线程的主从库枚举,再根据主从库枚举获取指定数据源。

/**
 * @Author: 清峰
 * @Description: May there be no bug in the world!
 */
@Slf4j
@Aspect
@Component
public class DynamicDataSourceAop {


    @Around(value = "@annotation(dynamicDb)")
    public Object around(ProceedingJoinPoint jp, DynamicDb dynamicDb) throws ServerException {
        try {
            MethodSignature signature = (MethodSignature) jp.getSignature();
            Method method = signature.getMethod();
            DynamicDb annotation = method.getAnnotation(DynamicDb.class);
            DbType dbType = annotation.value();
            DbThreadLocal.setType(dbType);

            return jp.proceed();
        } catch (Throwable e) {
            log.error("处理异常", e);
            throw new ServerException("程序运行异常", e);
        } finally {
            //清理本地线程数据源,避免上下文数据源逻辑混乱
            DbThreadLocal.cleanType();
        }

    }
}

数据源配置

# 数据源配置
spring:
  datasource:
    type:
    driverClassName:
    druid:
      # 主库数据源
      master:
        url:
        username:
        password:
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled:
        url:
        username:
        password:

标记动态数据源接口

    @DynamicDb(DbType.SECOND_DB)
    @PostMapping("saveUser")
    private void saveUser(User user){
        System.out.println("保存用户信息逻辑...");
    }

总结

​ 使用自定义注解来切换动态数据源非常方便,只需添加依赖,并在yaml中配置数据源的名称和地址,并在接口上使用注解来指定实现切换即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值