<font size="1">版權聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。https://blog.csdn.net/qq_36523638/article/details/84571304</font>
Spring+mybatis動態(tài)切換數(shù)據(jù)源
這是基于mybatis的動態(tài)切換數(shù)據(jù)源
數(shù)據(jù)源連接池使用的為Druid
bean工廠使用的是mybatis的SqlSessionFactoryBean
1、配置第一個數(shù)據(jù)源:
#配置第一個數(shù)據(jù)源(具體配置視情況而定):
<bean id="dataSourceA" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--<property name="filters" value="stat,log4j"/>-->
<property name="maxActive" value="20"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="60000"/>
<property name="minIdle" value="1"/>
<property name="timeBetweenEvictionRunsMillis" value="3000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x' FROM DUAL"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
</bean>
2、配置第二個數(shù)據(jù)源:
#配置第二個數(shù)據(jù)源(具體配置視情況而定):
<bean id="dataSourceB" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
#配置第三個數(shù)據(jù)源
#配置第四個數(shù)據(jù)源
...
...
#可以配置多個數(shù)據(jù)源,只要不超出連接池的限制就可以
3、配置動態(tài)的數(shù)據(jù)源(關鍵)這里的開關:MultipleDataSource在第5步說明
<bean id="multipleDataSource" class="包名.MultipleDataSource">
<!-- 這里可以設置一個默認的數(shù)據(jù)源 >
<property name="defaultTargetDataSource" ref="dataSource" />
<property name="targetDataSources">
<map>
<entry key="dataSourceA" value-ref="dataSourceA" />
<entry key="dataSourceB" value-ref="dataSourceB" />
</map>
</property>
</bean>
4、然后在配置sqlSessionFactory的配置文件中添加屬性:
<property name="dataSource" ref="multipleDataSource"/>
添加完后是這樣:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource"/>
<一些插件的配置>
...
</>
</bean>
5、包名.MultipleDataSource(動態(tài)切換開關):
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
public static void removeDataSourceKey() {
dataSourceKey.remove();
}
}
6、最后自己寫一個切面AOP來做數(shù)據(jù)源的切換:
/**
* 數(shù)據(jù)源切換AOP
*/
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE-1)
@Component
public class MultipleDataSourceInterceptor {
/**
* 攔截器對所有的業(yè)務實現(xiàn)類請求之前進行數(shù)據(jù)源切換 特別注意,由于用到了多數(shù)據(jù)源,Mapper的調(diào)用最好只在*ServiceImpl,不然調(diào)用到非默認數(shù)據(jù)源的表時,會報表不存在的異常
*
* @throws
*/
@Pointcut("execution(* com..*Impl.*(..)) || execution(* com..*Mapper.*(..))")
public void log(){
}
@Before("log()")
public void setDataSoruce(JoinPoint joinPoint)
throws Throwable {
//int i=8;
//int j=i/0;
Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
if (ClassUtils.isAssignable(clazz, Proxy.class)) {
className = joinPoint.getSignature().getDeclaringTypeName();
}
// 對包名含有serverwpms的設置為wpms數(shù)據(jù)源,否則默認為后臺的數(shù)據(jù)源
if (className.contains(".serverwpms.")) {
MultipleDataSource.setDataSourceKey("dataSourceA");
}else if (className.contains(".server.") || className.contains(".mapper.")) {
MultipleDataSource.setDataSourceKey("dataSourceB");
}
}
/**
* 當操作完成時,釋放當前的數(shù)據(jù)源 如果不釋放,頻繁點擊時會發(fā)生數(shù)據(jù)源沖突,本是另一個數(shù)據(jù)源的表,結(jié)果跑到另外一個數(shù)據(jù)源去,報表不存在
*
* @param joinPoint
* @throws Throwable
*/
@After("log()")
public void removeDataSoruce(JoinPoint joinPoint)
throws Throwable {
MultipleDataSource.removeDataSourceKey();
}
}
aop在springmvc的配置文件中定義下:
<!-- 定義aspect類 -->
<bean name="multipleDataSourceInterceptor" class="包名.MultipleDataSourceInterceptor"/>
<font size="4" color="red"> ----到這,系統(tǒng)中如果沒有用到事務,多數(shù)據(jù)源的配置就完成了,aop會根據(jù)你想要的需求去動態(tài)切換數(shù)據(jù)源,如果用到事務的話,僅僅到這是不夠的</font>
查閱資料說事務管理器是從connection級別來處理的,尤其是非常方便的DataSourceTransactionManager管理器,方法上用一個@Transactional注解就可以完成事務管理。
DataSourceTransactionManager管理器會對connection進行緩存(大概是這個意思),導致我們的aop MultipleDataSourceInterceptor雖然將數(shù)據(jù)源A的字符串切換為了數(shù)據(jù)源B,但是根本上的connection是并沒有切換回來的,這時候如果本該對數(shù)據(jù)源A操作的sql去操作數(shù)據(jù)源B了,會報表不存在的錯誤。
<font size = "4" color="blue">這時候應該怎么解決呢,其實主要是事務管理器執(zhí)行的優(yōu)先級比我們的aop優(yōu)先級高罷了,每次都是事務管理器先執(zhí)行,然后aop才去切換數(shù)據(jù)源,發(fā)現(xiàn)并沒有切換過來,手動設置下aop的優(yōu)先級高于事務管理器就好了,而事務管理器的優(yōu)先級默認為Order(1),也就是我之前所見過的最高的優(yōu)先級了</font>
<font size="5" color="green"></font>
上面<font size="5" color="red">@Order(Ordered.LOWEST_PRECEDENCE-1)</font>為關鍵,意思是給aop定一個優(yōu)先級,這個優(yōu)先級為多少呢, Ordered.LOWEST_PRECEDENCE為最低的order,大家都知道,Order越低,優(yōu)先級越高,事務管理器默認優(yōu)先級為1,這個aop的order比最低的Order還要小一個單位,代表優(yōu)先級是最高的。
注:事務管理器我是這么配置的:
<!-- 配置事務管理器,注意這里的dataSource和SqlSessionFactoryBean的dataSource要一致,不然事務就沒有作用了 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"/>
</bean>
<!-- 用注解來實現(xiàn)事務管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>