多數據源動態配置及事務控制
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 extendsAbstractRoutingDataSource { @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(); ............. }
多數據源動態配置及事務控制