關鍵詞:AOP、AbstractRoutingDataSource、動態切換數據源
廢話不多說,首先在XML中進行讀、寫數據源的配置
<!--讀數據源配置-->
<bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"destroy-method="close">
//配置省略
</bean>
<!--寫數據源配置-->
<bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"destroy-method="close">
//配置省略
</bean>
<!-- 動態數據源 -->
<bean id = "dataSource" class="com.xxx.xxx.DynamicDataSource" >
<!-- 已配置的數據源 -->
<property name="targetDataSources">
<map>
<entry key="READ" value-ref="readDataSource"/>
<entry key="WRITE" value-ref="writeDataSource"/>
</map>
</property>
<!-- 默認的數據源 -->
<property name="defaultTargetDataSource" ref="writeDataSource"/>
</bean>
具體的配置省略,建議加載資源文件,注意的點是,如何進行數據源的切換?通過動態數據源的的配置可以看出,其key就是負責切換數據源的,最最最重要一點:動態數據源DynamicDataSource這個類一定要繼承自:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,重寫其determineCurrentLookupKey()方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getType();
}
}
還需要一個存放key的地方--->>DataSourceContextHolder工具類;爲保證切換時線程安全我們使用ThreadLocal來保存我們的key。
public class DataSourceContextHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
public static final String DATA_SOURCE_WRITE = "WRITE";
public static final String DATA_SOURCE_READ = "READ";
// 線程本地環境
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
// 設置數據源類型
public static void setType(String type) {
if(LOGGER.isDebugEnabled())
LOGGER.debug("========>>>>>>>切換數據源,類型:"+type+"<<<<<<<<========");
contextHolder.set(type);
}
// 獲取數據源類型
public static String getType() {
return (contextHolder.get());
}
// 清除數據源類型
public static void clearType() {
contextHolder.remove();
}
}
到這裏其實現數據源的切換的核心部分已經完成,接下來,就需要實現怎麼在特定的場景下去觸發數據源切換,那麼就交給Spring AOP來完成了。
@Component
@Aspect
public class DynamicDataSourceAspect {
@Pointcut("execution (* com.x.mapper.*.select*(..)) || execution (* com.x.mapper.*.count*(..)) ")
public void readMethodPointcut() {}
@Pointcut("execution(* com.mapper.*.insert*(..))||execution(* com.mapper.*.update*(..))||execution(* com.mapper.*.delete*(..))")
public void writeMethodPointcut() {}
@Before("readMethodPointcut()")
public void switchReadDataSource(){
//設置讀數據源
DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_READ);
}
@Before("writeMethodPointcut()")
public void switchWriteDataSource(){
//設置寫數據源
DataSourceContextHolder.setType(DataSourceContextHolder.DATA_SOURCE_WRITE);
}
}
PS:多謝一位朋友提醒,本來我考慮的是切到業務層,自己寫個註解來切換,直接切到逆向工程的mapper更省事!
按照之前我的想法,會麻煩一點,思路大體就是,寫一個註解@ReadSource,上面的切面切到業務層(或者是直接調用DAO層的邏輯層),對所有讀數據的方法加上@ReadSource,切面裏面獲取方法的註解是否有@ReadSource,如果有則設置讀數據源。想想都覺得後怕。。。。項目裏幾百上千個方法總是有的。。。。!!!
另外,很重要的一點!!!
另外,很重要的一點!!!
另外,很重要的一點!!!
直接切到mapper層面,對一些負責的業務,有可能會導致事務失效,很簡單一個例子,一個業務即涉及讀也涉及寫,過程中發生了異常,由於事務針對同一數據源,很有可能導致回滾失敗!!!所以之前想的方法雖然麻煩,但是在這種情況可還是可以避免掉事務失效的!!
兩種方法各有利弊吧,看各自具體項目實際需求取捨了。