本篇文章将介绍如何使用AOP和注解来实现动态数据源.

使用ThreadLocal存储当前线程使用的数据源的key

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 用于保存当前线程使用的数据源名称的工具类,多数据源动态切换的工具类
 * @author lishouyu
 * @version 1.0
 * @since 1.0
 */
public class DataSourceContextHolder {

  /**
   * 用于日志记录的对象
   */
  public static final Logger logger = LoggerFactory.getLogger(DataSourceContextHolder.class);

  /**
   * 进程内数据存储
   */
  private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

  /**
   * 默认的数据源的名称
   */
  public static final String DEFAULT_DATDASOURCE_NAME = "master";

  /**
   * 设置数据源名
   * @param datasourceName 数据源的名字
   */
  public static void setDatasourceName(String datasourceName){
    logger.info("切换到{}数据源",datasourceName);
    contextHolder.set(datasourceName);
  }

  /**
   * 获取数据源名
   * @return
   */
  public static String getDatdasourceName(){
    return contextHolder.get();
  }

  /**
   * 清除数据源名称
   */
  public static void clearDatasourceName(){
    contextHolder.remove();
  }
}

创建动态数据源类

继承spring框架为我提供的数据源路由抽象类AbstractRoutingDataSource,创建我们自己的动态数据源类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源类继承自AbstractRoutingDataSource
 * @author lishouyu
 * @version 1.0
 * @since 1.0
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
  /**
   * 记录日志
   */
  private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

  @Override
  public Object determineCurrentLookupKey() {
    logger.debug("数据源{}",DataSourceContextHolder.getDatdasourceName());
    return DataSourceContextHolder.getDatdasourceName();
  }
}

在spring中配置刚才创建的动态数据源的bean

示例代码中实现了如果只有单一数据源的时候,动态数据源将只会注入一个master数据源

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import com.micro.fast.common.dao.DynamicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


/**
 * 动态数据源配置类
 * @author lishouyu
 * @version 1.0
 * @since 1.0
 */
@Configuration
@AutoConfigureBefore({SqlSessionFactoryConfig.class})
public class DynamicDataSourceConfig {
  /**
   * 注入主数据源
   */
  @Autowired
  @Qualifier("masterDataSource")
  private DataSource masterDataSource;

  /**
   * 从数据源
   */
  @Autowired(required = false)
  @Qualifier("slaveDataSource")
  private DataSource slaveDataSource;

  @Bean(name = "dynamicDataSource")
  @Primary
  public DataSource dynamicDataSource(){
    //动态数据源对象
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    //设置默认数据源
    dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
    //配置多数据源
    Map<Object,Object> dataSourceMap = new HashMap<>();
    //填入主数据源
    dataSourceMap.put("master",masterDataSource);
    //如果从数据源存在,填入从数据源
    if (slaveDataSource!=null){
      dataSourceMap.put("slave",slaveDataSource);
    }
    dynamicDataSource.setTargetDataSources(dataSourceMap);
    return dynamicDataSource;
  }
}

自定义注解指明查询方法使用的数据源

自定义注解用于标注在mybatis的dao层接口的方法上,或者是hibernate的dao层方法上,指明使用哪个数据源.默认不带注解的方法使用master数据源,从数据源使用@SwitchDataSource("salve")放在方法上.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import java.lang.annotation.*;

/**
 * 用于指明mybatis Dao层接口使用哪个数据源
 * @author lishouyu
 * @version 1.0
 * @since 1.0
 */
@Retention(RetentionPolicy.RUNTIME)//运行时保留
@Documented//生成到文档中
@Target(ElementType.METHOD)//作用范围是方法
public @interface SwitchDataSource {
  /**
   * value 使用的数据源的名称,默认为master
   * @return
   */
  String value() default "master";
}

使用AOP对自定义的注解进行拦截

根据注解情况修改当前线程使用的数据源的key

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import com.micro.fast.common.annotation.SwitchDataSource;
import com.micro.fast.common.dao.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 动态切换数据源的切面,根据注解的内容来切换数据源
 * @author lishouyu
 * @version 1.0
 * @since 1.0
 */
@Aspect
@Component
public class DynamicDataSourceAspect {

  @Pointcut("@annotation(com.micro.fast.common.annotation.SwitchDataSource)")
  public void mybatisExecutionSqlPointcut(){

  }
  /**
   * 在SwitchDataSource注解的方法之前执行
   * @see com.micro.fast.common.annotation.SwitchDataSource
   */
  @Before("mybatisExecutionSqlPointcut()")
  public void beforeMybatisExecutionSql(JoinPoint joinPoint){
    //获取当前访问的class
    Class<?> aClass = joinPoint.getTarget().getClass();
    //获取方法的签名
    MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
    //获取访问的方法名字
    String methodName = methodSignature.getName();
    //得到当前方法的参数类型
    Class[] argsClass = methodSignature.getParameterTypes();
    String dataSourceName = DataSourceContextHolder.DEFAULT_DATDASOURCE_NAME;
    try {
      Method method = aClass.getMethod(methodName, argsClass);
      //判断是否存在数据源切换注解
      if (method.isAnnotationPresent(SwitchDataSource.class)) {
        SwitchDataSource annotation = method.getAnnotation(SwitchDataSource.class);
        //赋值数据源的名称
        dataSourceName = annotation.value();
      }
    } catch (NoSuchMethodException e) {
      e.printStackTrace();
    }
    //切换数据源
    DataSourceContextHolder.setDatasourceName(dataSourceName);
  }

  /**
   * 在SwitchDataSource注解的方法之后执行
   * @see com.micro.fast.common.annotation.SwitchDataSource
   */
  @After("mybatisExecutionSqlPointcut()")
  public void afterMybatisExecutionSql(){
    //清除进程中数据源的名字
    DataSourceContextHolder.clearDatasourceName();
  }
}

使用的时候要取消默认的数据源自动装配

1
2
3
4
5
6
7
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class PerformanceAppraisalApplication {

	public static void main(String[] args) {
		SpringApplication.run(PerformanceAppraisalApplication.class, args);
	}
}

使用示例

1
@SwitchDataSource("slave")