最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库。所以涉及到需要在一个项目中配置多数据源,并且能够动态切换。经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考。
设计总体思路
Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源。
步骤
一、多数据源配置
在application.properties中,我们的配置是这样的
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#主数据源
druid.master.url=jdbc: mysql://url/masterdb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
druid.master.username=xxx
druid.master.password=123
druid.master.driver-class-name=com.mysql.jdbc.Driver
druid.master.max-wait=5000
druid.master.max-active=100
druid.master.test-on-borrow=true
druid.master.validation-query=SELECT 1
#从数据源
druid.slave.url=jdbc: mysql://url/slavedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
druid.slave.username=xxx
druid.slave.password=123
druid.slave.driver-class-name=com.mysql.jdbc.Driver
druid.slave.max-wait=5000
druid.slave.max-active=100
druid.slave.test-on-borrow=true
druid.slave.validation-query=SELECT 1
|
读取配置
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
67
68
69
|
<!-- master数据源 -->
< bean primary = "true" id = "masterdb" class = "com.alibaba.druid.pool.DruidDataSource" init-method = "init" destroy-method = "close" >
<!-- 基本属性 url、user、password -->
< property name = "driverClassName" value = "com.mysql.jdbc.Driver" />
< property name = "url" value = "${druid.master.url}" />
< property name = "username" value = "${druid.master.username}" />
< property name = "password" value = "${druid.master.password}" />
<!-- 配置初始化最大 -->
< property name = "maxActive" value = "${druid.master.max-active}" />
<!-- 配置获取连接等待超时的时间 -->
< property name = "maxWait" value = "${druid.master.max-wait}" />
< property name = "validationQuery" value = "${druid.master.validation-query}" />
< property name = "testOnBorrow" value = "${druid.master.test-on-borrow}" />
</ bean >
<!-- slave数据源 -->
< bean primary = "true" id = "slavedb" class = "com.alibaba.druid.pool.DruidDataSource" init-method = "init" destroy-method = "close" >
<!-- 基本属性 url、user、password -->
< property name = "driverClassName" value = "com.mysql.jdbc.Driver" />
< property name = "url" value = "${druid.slave.url}" />
< property name = "username" value = "${druid.slave.username}" />
< property name = "password" value = "${druid.slave.password}" />
<!-- 配置初始化大小、最小、最大 -->
< property name = "maxActive" value = "${druid.slave.max-active}" />
<!-- 配置获取连接等待超时的时间 -->
< property name = "maxWait" value = "${druid.slave.max-wait}" />
< property name = "validationQuery" value = "${druid.slave.validation-query}" />
< property name = "testOnBorrow" value = "${druid.slave.test-on-borrow}" />
</ bean >
<!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 -->
< bean id = "dataSource" class = "datasource.DynamicDataSource" >
< property name = "targetDataSources" >
< map key-type = "java.lang.String" >
< entry key = "slave" value-ref = "slavedb" />
< entry key = "master" value-ref = "masterdb" />
</ map >
</ property >
< property name = "defaultTargetDataSource" ref = "masterdb" />
</ bean >
<!-- Spring JdbcTemplate -->
< bean id = "jdbcTemplate" class = "org.springframework.jdbc.core.JdbcTemplate" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
<!-- Spring事务管理器 -->
< bean id = "transactionManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name = "dataSource" ref = "dataSource" />
</ bean >
< bean id = "transactionTemplate" class = "org.springframework.transaction.support.TransactionTemplate" >
< property name = "transactionManager" ref = "transactionManager" />
</ bean >
< tx:annotation-driven transaction-manager = "transactionManager" proxy-target-class = "true" order = "2" />
<!-- depositdbSqlSessionFactory -->
< bean id = "sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean" >
< property name = "dataSource" ref = "dataSource" />
< property name = "mapperLocations" value = "classpath*:mapper-xxdb/*Mapper*.xml" />
</ bean >
< bean class = "org.mybatis.spring.mapper.MapperScannerConfigurer" >
< property name = "basePackage" value = "xxdb.mapper" />
< property name = "sqlSessionFactoryBeanName" value = "sqlSessionFactory" />
</ bean >
|
二、动态数据源
spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。
1
|
2
3
4
5
6
7
8
9
10
|
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger( this .getClass());
@Override
protected Object determineCurrentLookupKey() {
String dataSource = JdbcContextHolder.getDataSource();
logger.info( "数据源为{}" ,dataSource);
return dataSource;
}
}
|
三. 数据源动态切换类
动态数据源切换是基于AOP的,所以我们需要声明一个AOP切面,并在切面前做数据源切换,切面完成后移除数据源名称。
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
|
@Aspect
@Order ( 1 ) //设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中)
@Component
public class DataSourceAspect {
private Logger logger = LoggerFactory.getLogger( this .getClass());
//切点
@Pointcut ( "execution(* com.xxx.service.*.*(..))" )
public void aspect() { }
@Before ( "aspect()" )
private void before(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?> classz = target.getClass(); // 获取目标类
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz.getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(MyDataSource. class )) {
MyDataSource data = m.getAnnotation(MyDataSource. class );
logger.info( "method :{},datasource:{}" ,m.getName() ,data.value().getName());
JdbcContextHolder.putDataSource(data.value().getName()); // 数据源放到当前线程中
}
} catch (Exception e) {
logger.error( "get datasource error " ,e);
//默认选择master
JdbcContextHolder.putDataSource(DataSourceType.Master.getName()); // 数据源放到当前线程中
}
}
@AfterReturning ( "aspect()" )
public void after(JoinPoint point) {
JdbcContextHolder.clearDataSource();
}
}
|
四、数据源管理类
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class JdbcContextHolder {
private final static ThreadLocal<String> local = new ThreadLocal<>();
public static void putDataSource(String name) {
local.set(name);
}
public static String getDataSource() {
return local.get();
}
public static void clearDataSource() {
local.remove();
}
}
|
五、数据源注解和枚举
我们切换数据源时,一般都是在调用具体接口的方法前实现,所以我们定义一个方法注解,当AOP检测到方法上有该注解时,根据注解中value对应的名称进行切换。
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
|
@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
public @interface MyDataSource {
DataSourceType value();
}
public enum DataSourceType {
// 主表
Master( "master" ),
// 从表
Slave( "slave" );
private String name;
private DataSourceType(String name) {
this .name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
}
|
六、切点注解
由于我们的动态数据源配置了默认库,所以如果方法是操作默认库的可以不需要注解,如果要操作非默认数据源,我们需要在方法上添加@MyDataSource("数据源名称")注解,这样就可以利用AOP实现动态切换了
1
|
2
3
4
5
6
7
8
9
10
|
@Component
public class xxxServiceImpl {
@Resource
private XxxMapperExt xxxMapperExt;
@MyDataSource (value= DataSourceType.Slave)
public List<Object> getAll(){
return xxxMapperExt.getAll();
}
}
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://tech.dianwoda.com/2018/03/28/spring-boot-aopfang-shi-shi-xian-duo-shu-ju-yuan-qie-huan