AbstractRoutingDataSource的多數據源應用

AbstractRoutingDataSource:提供了動態切換數據源的功能。

public class DynamicDataSourceHolder extends AbstractRoutingDataSource {

    private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
    // 可選取slave的keys
    private List<String> slaveDataSourcesKeys;
    // 可選取master的keys
    private List<String> masterDataSourcesKeys;
    //從數據
    private Map<String, DataSource> slavetDataSources;
    //主數據
    private Map<String, DataSource> masterDataSources;
    /**
     * 重寫這個抽象類是爲了獲取數據源
     * protected DataSource determineTargetDataSource(){
     *     ......
     *        Object lookupKey = this.determineCurrentLookupKey();
     *        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
     *     .....
     * }
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceHolder.get();
    }

    /**
     * 將配置文件中的主數據源和從數據源加入到spring數據源中
     */
    @Override
    public void afterPropertiesSet(){
        // 數據檢驗和合並
        logger.debug("開始向spring routing datasource 提供數據源選取");
        Map<Object, Object> allDataSources = new HashMap<Object, Object>();
        allDataSources.putAll(masterDataSources);
        if (slavetDataSources != null) {
            allDataSources.putAll(slavetDataSources);
        }
        super.setTargetDataSources(allDataSources);
        super.afterPropertiesSet();
        logger.debug("已經完成向spring routing datasource 提供數據源選取");
    }

    /**
     * 在加載applicationContent-dataSource配置文件時,將從數據源加載到slavetDataSources中
     * @param slavetDataSources
     */
    public void setSlavetDataSources(Map<String, DataSource> slavetDataSources) {
        if (slavetDataSources == null || slavetDataSources.size() == 0) {
            return;
        }
        logger.debug("提供可選取slave數據源:{}", (Throwable) slavetDataSources.keySet());
        this.slavetDataSources = slavetDataSources;
        slaveDataSourcesKeys = new ArrayList<String>();
        for (Map.Entry<String, DataSource> entry : slavetDataSources.entrySet()) {
            slaveDataSourcesKeys.add(entry.getKey());
        }
    }
    /**
     * 在加載applicationContent-dataSource配置文件時,將從數據源加載到slavetDataSources中
     * @param masterDataSources
     */
    public void setMasterDataSources(Map<String, DataSource> masterDataSources) {
        if (masterDataSources == null) {
            throw new IllegalArgumentException("Property 'masterDataSources' is required");
        }
        logger.debug("提供可選取slave數據源:{}", (Throwable) masterDataSources.keySet());
        this.masterDataSources = masterDataSources;
        masterDataSourcesKeys = new ArrayList<String>();
        for (Map.Entry<String, DataSource> entry : masterDataSources.entrySet()) {
            masterDataSourcesKeys.add(entry.getKey());
        }
    }

    /**
     * 標記選取slave數據源
     */
    public void markSlave() {
        if (dataSourceHolder.get() != null) {
            // 從現在的策略來看,不允許標記兩次,直接拋異常,優於早發現問題
            throw new IllegalArgumentException("當前已有選取數據源,不允許覆蓋,已選數據源key:" + dataSourceHolder.get());
        }
        setDataSource(selectFromSlave());
    }

    /**
     * 標記選取master數據源
     */
    public void markMaster() {
        if (dataSourceHolder.get() != null) {
            // 從現在的策略來看,不允許標記兩次,直接拋異常,優於早發現問題
            throw new IllegalArgumentException("當前已有選取數據源,不允許覆蓋,已選數據源key:" + dataSourceHolder.get());
        }
        setDataSource(selectFromMaster());
    }

    /**
     * 刪除己標記選取的數據源
     */
    public void markRemove() {
        dataSourceHolder.remove();
    }

    private void setDataSource(String dataSourceKey) {
        logger.debug("dataSourceHolder set datasource keys:{}");
        dataSourceHolder.set(dataSourceKey);
    }
    /**
     * 是否已經綁定datasource 綁定:true 沒綁定:false
     */
    public boolean hasBindedDataSourse() {
        return dataSourceHolder.get() != null;
    }

    private String selectFromSlave() {
        if (slavetDataSources == null) {
            logger.debug("提供可選取slave數據源:{},將自動切換從主master選取數據源", (Throwable) slavetDataSources);
            return selectFromMaster();
        } else {
            return slaveDataSourcesKeys.get(RandomUtils.nextInt(slaveDataSourcesKeys.size()));
        }
    }

    private String selectFromMaster() {
        String dataSourceKey = masterDataSourcesKeys.get(RandomUtils.nextInt(masterDataSourcesKeys.size()));
        return dataSourceKey;
    }

}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

public class DynamicDataSourceAop {

    @Resource
    private DynamicDataSourceHolder dataSourceHolder;

    private String[] writeStartWiths;

    public Object doAroundMethod(ProceedingJoinPoint pjp) throws Throwable {
        Object response = null;
        String method = pjp.getSignature().getName();
        boolean hasBinded = false;
        try {
            hasBinded = dataSourceHolder.hasBindedDataSourse();
            if (!hasBinded) {
                boolean isWrite = false;
                for (String writeStartWith : writeStartWiths) {
                    if (method.startsWith(writeStartWith)) {
                        isWrite = true;
                        break;
                    }
                }
                if (isWrite) {
                    dataSourceHolder.markMaster();
                } else {
                    dataSourceHolder.markSlave();
                }
            }
            response = pjp.proceed();
        } finally {
            if (!hasBinded) {
                dataSourceHolder.markRemove();
            }
        }
        return response;
    }

    public void setWriteStartWith(String[] writeStartWith) {
        this.writeStartWiths = writeStartWith;
    }


}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

applicationContent-dataSource.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
   http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
    
    <context:component-scan base-package="com.tangCL.core" />

    <bean id="master" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <!-- 數據源驅動類可不寫,Druid默認會自動根據URL識別DriverClass -->
        <property name="url" value="${master.jdbc.url}"/>
        <property name="username" value="${master.jdbc.username}"/>
        <property name="password" value="${master.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="3"/>
        <property name="minIdle" value="3"/>
        <property name="maxActive" value="60"/>
        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打開removeAbandoned功能 -->
        <property name="removeAbandoned" value="true"/>
        <!-- 1800秒,也就是30分鐘 -->
        <property name="removeAbandonedTimeout" value="180"/>
        <!-- 關閉abanded連接時輸出錯誤日誌 -->
        <property name="logAbandoned" value="true"/>
        <property name="proxyFilters">
            <list>
                <ref bean="slf4j-filter"/>
                <ref bean="stat-filter"/>
            </list>
        </property>
        <property name="filters" value="stat,config,wall"/>
        <property name="connectionProperties"
                  value="config.decrypt=true;druid.log.conn=false;druid.log.stmt=false;druid.log.rs=false;druid.log.stmt.executableSql=true;"/>

    </bean>

    <bean id="slave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
          destroy-method="close">
        <!-- 數據源驅動類可不寫,Druid默認會自動根據URL識別DriverClass -->
        <property name="url" value="${slave.jdbc.url}"/>
        <property name="username" value="${slave.jdbc.username}"/>
        <property name="password" value="${slave.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="3"/>
        <property name="minIdle" value="3"/>
        <property name="maxActive" value="100"/>
        <!-- 配置獲取連接等待超時的時間 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="validationQuery" value="SELECT 'x'"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打開removeAbandoned功能 -->
        <property name="removeAbandoned" value="true"/>
        <!-- 1800秒,也就是30分鐘 -->
        <property name="removeAbandonedTimeout" value="180"/>
        <!-- 關閉abanded連接時輸出錯誤日誌 -->
        <property name="logAbandoned" value="true"/>
        <property name="proxyFilters">
            <list>
                <ref bean="slf4j-filter"/>
                <ref bean="stat-filter"/>
            </list>
        </property>
        <property name="filters" value="stat,config,wall"/>
        <property name="connectionProperties"
                  value="config.decrypt=true;druid.log.conn=false;druid.log.stmt=false;druid.log.rs=false;druid.log.stmt.executableSql=true;"/>
    </bean>

    <!-- 自定義數據源 -->
    <bean id="dynamicDataSourceHolder" class="com.tangCL.utils.jdbc.DynamicDataSourceHolder">
        <property name="slavetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="slave" key="slave"></entry>
            </map>
        </property>
        <property name="masterDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="master" key="master"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="master"/>
    </bean>

    <bean id="slf4j-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter">
    </bean>

    <!-- 事務管理器 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSourceHolder"/>
    </bean>

    <bean name="dynamicDataSourceAop" class="com.tangCL.utils.jdbc.DynamicDataSourceAop"
          scope="singleton">
        <property name="writeStartWith">
            <array>
                <value>insert</value>
                <value>update</value>
                <value>add</value>
                <value>save</value>
                <value>modify</value>
                <value>delete</value>
                <value>call</value>
                <value>change</value>
                <value>end</value>
                <value>cancel</value>
            </array>
        </property>
    </bean>

    <aop:config expose-proxy="true">
        <aop:pointcut id="rwPointcut"
                      expression="execution(表達式) or
                      execution(表達式) or
                      execution(表達式)"/>
        <!-- 通過AOP切面實現讀/寫庫選擇 -->
        <aop:aspect ref="dynamicDataSourceAop">
            <aop:around pointcut-ref="rwPointcut" method="doAroundMethod"/>
        </aop:aspect>
    </aop:config>

    <!-- 開啓註解事務機制 配置基於註解方式的事務 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <!--Spring 聲明式配置 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="load*" read-only="true"/>
            <tx:method name="count*" read-only="true"/>
            <tx:method name="add*" rollback-for="Exception" propagation="REQUIRED"/>
            <tx:method name="call*" rollback-for="Exception" propagation="REQUIRED"/>
            <tx:method name="save*" rollback-for="Exception" propagation="REQUIRED"/>
            <tx:method name="update*" rollback-for="Exception" propagation="REQUIRED"/>
            <tx:method name="insert*" rollback-for="Exception,RuntimeException" propagation="REQUIRED"/>
            <tx:method name="delete*" rollback-for="Exception" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(表達式) or
                      execution(表達式)"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

</beans>

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