SpringBoot AOP方式實現(xiàn)多數(shù)據(jù)源切換的方法
最近在做保證金余額查詢優(yōu)化,在項目啟動時候需要把余額全量加載到本地緩存,因為需要全量查詢所有騎手的保證金余額,為了不影響主數(shù)據(jù)庫的性能,考慮把這個查詢走從庫。所以涉及到需要在一個項目中配置多數(shù)據(jù)源,并且能夠動態(tài)切換。經(jīng)過一番摸索,完美實現(xiàn)動態(tài)切換,記錄一下配置方法供大家參考。
設計總體思路
Spring-Boot+AOP方式實現(xiàn)多數(shù)據(jù)源切換,繼承AbstractRoutingDataSource實現(xiàn)數(shù)據(jù)源動態(tài)的獲取,在service層使用注解指定數(shù)據(jù)源。
步驟
一、多數(shù)據(jù)源配置
在application.properties中,我們的配置是這樣的
#主數(shù)據(jù)源 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 #從數(shù)據(jù)源 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
讀取配置
<!-- master數(shù)據(jù)源 -->
<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數(shù)據(jù)源 -->
<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>
<!-- 動態(tài)數(shù)據(jù)源,根據(jù)service接口上的注解來決定取哪個數(shù)據(jù)源 -->
<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>
二、動態(tài)數(shù)據(jù)源
spring為我們提供了AbstractRoutingDataSource,即帶路由的數(shù)據(jù)源。繼承后我們需要實現(xiàn)它的determineCurrentLookupKey(),該方法用于自定義實際數(shù)據(jù)源名稱的路由選擇方法,由于我們將信息保存到了ThreadLocal中,所以只需要從中拿出來即可。
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected Object determineCurrentLookupKey() {
String dataSource = JdbcContextHolder.getDataSource();
logger.info("數(shù)據(jù)源為{}",dataSource);
return dataSource;
}
}
三. 數(shù)據(jù)源動態(tài)切換類
動態(tài)數(shù)據(jù)源切換是基于AOP的,所以我們需要聲明一個AOP切面,并在切面前做數(shù)據(jù)源切換,切面完成后移除數(shù)據(jù)源名稱。
@Aspect
@Order(1) //設置AOP執(zhí)行順序(需要在事務之前,否則事務只發(fā)生在默認庫中)
@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());// 數(shù)據(jù)源放到當前線程中
}
} catch (Exception e) {
logger.error("get datasource error ",e);
//默認選擇master
JdbcContextHolder.putDataSource(DataSourceType.Master.getName());// 數(shù)據(jù)源放到當前線程中
}
}
@AfterReturning("aspect()")
public void after(JoinPoint point) {
JdbcContextHolder.clearDataSource();
}
}
四、數(shù)據(jù)源管理類
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();
}
}
五、數(shù)據(jù)源注解和枚舉
我們切換數(shù)據(jù)源時,一般都是在調(diào)用具體接口的方法前實現(xiàn),所以我們定義一個方法注解,當AOP檢測到方法上有該注解時,根據(jù)注解中value對應的名稱進行切換。
@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;
}
}
六、切點注解
由于我們的動態(tài)數(shù)據(jù)源配置了默認庫,所以如果方法是操作默認庫的可以不需要注解,如果要操作非默認數(shù)據(jù)源,我們需要在方法上添加@MyDataSource("數(shù)據(jù)源名稱")注解,這樣就可以利用AOP實現(xiàn)動態(tài)切換了
@Component
public class xxxServiceImpl {
@Resource
private XxxMapperExt xxxMapperExt;
@MyDataSource(value= DataSourceType.Slave)
public List<Object> getAll(){
return xxxMapperExt.getAll();
}
}
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
springboot接口接收數(shù)組及多個參數(shù)的問題及解決
這篇文章主要介紹了springboot接口接收數(shù)組及多個參數(shù)的問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
深入學習Java單元測試(Junit+Mock+代碼覆蓋率)
在做單元測試時,代碼覆蓋率常常被拿來作為衡量測試好壞的指標,甚至,用代碼覆蓋率來考核測試任務完成情況,比如,代碼覆蓋率必須達到80%或 90%。下面我們就來詳細學習下java單元測試吧2019-06-06
解決MultipartFile.transferTo(dest) 報FileNotFoundExcep的問題
這篇文章主要介紹了解決MultipartFile.transferTo(dest) 報FileNotFoundExcep的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
解決struts2 攔截器修改request的parameters參數(shù)失敗的問題
這篇文章主要介紹了解決struts2 攔截器修改request的parameters參數(shù)失敗的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

