基于注解的多数据源动态切换

  • 最近项目中使用多数据源,架构给的多数据源方案是在注册数据源时区分mapper包的位置,在开发过程中将mapper写到对应的包下。这种方式可能更加清晰,但是在开发过程中个人觉得有点麻烦,参考别人的博客,结合自己的理解写了这篇文章,希望对大家有帮助。

  • 实现思路:

  1. yml 配置数据源基本信息
  2. 配置类注册数据源信息
  3. 继承 AbstractRoutingDataSource类动态切换数据源
  4. 声明注解
  5. 编织切面

1. yml 配置数据源基本信息

spring:
  datasource:
    USER: #用户库
      type: com.alibaba.druid.pool.DruidDataSource
      jdbc-url: jdbc:mysql://localhost:3306/test
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver
    ORDER: #订单库
      type: com.alibaba.druid.pool.DruidDataSource
      jdbc-url: jdbc:mysql://localhost:3306/order
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver

2. 配置类注册数据源信息

/**
 * @Description: 注入数据源
 */
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.user")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.order")
    public DataSource orderDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource userDataSource, DataSource orderDataSource) {
        Map targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.USER.name(), userDataSource);
        targetDataSources.put(DataSourceType.ORDER.name(), orderDataSource);
        return new DynamicDataSource(userDataSource, targetDataSources);
    }
}

3. 继承 AbstractRoutingDataSource类动态切换数据源

/**
 * @Description:
 * 动态切换数据源主要依靠 AbstractRoutingDataSource。
 * 创建一个 AbstractRoutingDataSource 的子类,重写 determineCurrentLookupKey 方法,
 * 用于决定使用哪一个数据源。这里主要用到 AbstractRoutingDataSource 的两个属
 * 性 defaultTargetDataSource和targetDataSources。defaultTargetDataSource 默认目标数据源,
 * targetDataSources(map类型)存放用来切换的数据源。
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

4. 声明注解

/**
 * @Description:
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.USER;
}

5. 编织切面

/**
 * @Description: 通过拦截 @DataSource 注解,在其执行之前处理设置当前执行SQL的数据源的信息,
 * CONTEXT_HOLDER.set(dataSourceType)这里的数据源信息从我们设置的注解上面获取信息,
 * 如果没有设置就是用默认的数据源的信息。
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    @Pointcut("@annotation(com.haowq.anno.DataSource)")
    public void dsPointCut() {

    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            //通过注解值设置数据源
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}

6. 动态数据源上下文

/**
 * @Description: 动态数据源切换
 */
public class DynamicDataSourceContextHolder {
    /***
     * @description: 使用ThreadLocal维护变量,TThreadLocal为每个使用该变量的县城提供独立的变量副本,
     * 所以每一个线程都可以独立改变自己的副本,而不会影响其他线程所对应的副本
     */
    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();


    /*** 
     * @description: 设置数据源变量
     */
    public static void setDataSourceType(String dataSourceType) {
        System.out.println("切换到 "+ dataSourceType+"数据源");
        CONTEXT_HOLDER.set(dataSourceType);
    }

    /*** 
     * @description: 获取数据源变量
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

你可能感兴趣的:(基于注解的多数据源动态切换)