本篇文章将介绍如何使用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")
|