使用SpringAOP實現動態數據源讀寫分離

由於項目越來越大,數據庫的壓力越來越大,之前只做了簡單的數據庫主從備份,沒有進行讀寫分離,現在主庫的壓力越來越明顯,除了需要優化SQL之外,減輕主庫壓力,讀寫分離是必不可少了,也是項目需要吧,在這裏記錄一下,也是供大家參考一下吧。

關鍵詞: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層面,對一些負責的業務,有可能會導致事務失效,很簡單一個例子,一個業務即涉及讀也涉及寫,過程中發生了異常,由於事務針對同一數據源,很有可能導致回滾失敗!!!所以之前想的方法雖然麻煩,但是在這種情況可還是可以避免掉事務失效的!!

兩種方法各有利弊吧,看各自具體項目實際需求取捨了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章