1. 程式人生 > >多數據源動態配置及事務控制

多數據源動態配置及事務控制

業務 data 實例 每一個 RM join 數據 個數 lis

1、動態數據源切換時,如何保證事務

  目前事務最靈活的方式,是使用spring的聲明式事務,本質是利用了spring的aop,在執行數據庫操作前後,加上事務處理。

  spring的事務管理,是基於數據源的,所以如果要實現動態數據源切換,而且在同一個數據源中保證事務是起作用的話,就需要註意二者的順序問題,即:在事務起作用之前就要把數據源切換回來。

  舉一個例子:web開發常見是三層結構:controller、service、dao。一般事務都會在service層添加,如果使用spring的聲明式事務管理,在調用service層代碼之前,spring會通過aop的方式動態添加事務控制代碼,所以如果要想保證事務是有效的,那麽就必須在spring添加事務之前把數據源動態切換過來,也就是動態切換數據源的aop要至少在service上添加,而且要在spring聲明式事務aop之前添加.根據上面分析:

  • 最簡單的方式是把動態切換數據源的aop加到controller層,這樣在controller層裏面就可以確定下來數據源了。不過,這樣有一個缺點就是,每一個controller綁定了一個數據源,不靈活。對於這種:一個請求,需要使用兩個以上數據源中的數據完成的業務時,就無法實現了。
  • 針對上面的這種問題,可以考慮把動態切換數據源的aop放到service層,但要註意一定要在事務aop之前來完成。這樣,對於一個需要多個數據源數據的請求,我們只需要在controller裏面註入多個service實現即可。但這種做法的問題在於,controller層裏面會涉及到一些不必要的業務代碼,例如:合並兩個數據源中的list…
  • 此外,針對上面的問題,還可以再考慮一種方案,就是把事務控制到dao層,然後在service層裏面動態切換數據源。

2、實例

本例子中,對不同數據源分包(package)管理,同一包下的代碼使用了同一數據源。
1、寫一個DynamicDataSource類繼承AbstractRoutingDataSource,並實現determineCurrentLookupKey方法

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends
AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatabaseContextHolder.getCustomerType();
  }
}

2、利用ThreadLocal解決線程安全問題:

public class DatabaseContextHolder {
    public static final String DATA_SOURCE_A = "dataSource";
    public static final String DATA_SOURCE_B = "dataSource2";
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static void setCustomerType(String customerType) {
        contextHolder.set(customerType);
    }
    public static String getCustomerType() {
        return contextHolder.get();
    }
    public static void clearCustomerType() {
        contextHolder.remove();
    }
}

3、定義一個數據源切面類,通過aop來控制數據源的切換:

import org.aspectj.lang.JoinPoint;

public class DataSourceInterceptor {

    public void setdataSourceMysql(JoinPoint jp) {
        DatabaseContextHolder.setCustomerType("dataSourceMySql");
    }
    
    public void setdataSourceOracle(JoinPoint jp) {
        DatabaseContextHolder.setCustomerType("dataSourceOracle");
    }
}

4、在spring的application.xml中配置多個dataSource:

<!-- 數據源1 -->
<bean id="dataSourceMysql" class="org.apache.commons.dbcp.BasicDataSource">
           <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"></property>
        <property name="url" value="jdbc:jtds:sqlserver://1.1.1.1:3306;databaseName=standards"></property>
        <property name="username" value="admin"></property>
        <property name="password" value="admin"></property>
</bean>
<!-- 數據源2 -->
<bean id="dataSourceOracle" class="org.apache.commons.dbcp.BasicDataSource">
           <property name="driverClassName" value="net.sourceforge.jtds.jdbc.Driver"></property>
        <property name="url" value="jdbc:jtds:sqlserver://2.2.2.2:1433;databaseName=standards"></property>
        <property name="username" value="admin"></property>
        <property name="password" value="admin"></property>
</bean>

<bean id="dataSource" class="com.core.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="dataSourceMySql" value-ref="dataSourceMySql" />
            <entry key="dataSourceOracle" value-ref="dataSourceOracle" />
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="dataSourceMySql" /> <!-- 默認使用的數據源 -->
</bean>

<!-- 動態數據源切換aop 先與事務的aop  -->
<bean id="dataSourceInterceptor" class="com.core.DataSourceInterceptor" />
<aop:config>
    <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
        <aop:pointcut id="dsMysql" expression="execution(* com.service.mysql..*.*(..))" />
        <aop:pointcut id="dsOracle" expression="execution(* com.service.oracle..*.*(..))" />
        <aop:before method="setdataSourceMysql" pointcut-ref="dsMysql"/>
        <aop:before method="setdataSourceOracle" pointcut-ref="dsOracle"/>
    </aop:aspect>
</aop:config>

<!-- 事務管理器, Jdbc單數據源事務 -->
<bean id="transactionManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 使用annotation定義事務 proxy-target-class="true"-->
<tx:annotation-driven transaction-manager="transactionManager"/>

3、DynamicDataSource講解

  再來說下DynamicDataSource的實現原理,DynamicDataSource實現AbstractRoutingDataSource抽象類,然後實現了determineCurrentLookupKey方法,這個方法用於選擇具體使用targetDataSources中的哪一個數據源
<bean id="dataSource" class="com.core.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="dataSourceMySql" value-ref="dataSourceMySql" />
            <entry key="dataSourceOracle" value-ref="dataSourceOracle" />
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="dataSourceMySql" /> <!-- 默認使用的數據源 -->
</bean>

  可以看到Spring配置中DynamicDataSource設置了兩個屬性defaultTargetDataSource和targetDataSources,這兩個屬性定義在AbstractRoutingDataSource,當MyBatis執行查詢時會先選擇數據源,選擇順序時現根據determineCurrentLookupKey方法返回的值到targetDataSources中去找,若能找到怎返回對應的數據源,若找不到返回默認的數據源defaultTargetDataSource,具體參考AbstractRoutingDataSource的源碼

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    private Map<Object, Object> targetDataSources;

    private Object defaultTargetDataSource;

    
    /**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

    /**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */protected abstract Object determineCurrentLookupKey();
  
  .............

}

多數據源動態配置及事務控制