最近在做保證金余額查詢優化,在項目啟動時候需要把余額全量加載到本地緩存,因為需要全量查詢所有騎手的保證金余額,為了不影響主數據庫的性能,考慮把這個查詢走從庫。所以涉及到需要在一個項目中配置多數據源,并且能夠動態切換。經過一番摸索,完美實現動態切換,記錄一下配置方法供大家參考。
設計總體思路
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