spring+atomikos+mybatis 多数据源事务(动态切换)
注:自动切换,是为不同的数据源,却要对应相同的dao层;
1.与无事务版的一样,创建DynamicDataSource类,继承AbstractRoutingDataSource
package com.test.main.dataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSource(); } }
创建辅助类DynamicDataSourceHolder,主要用于保存当前线程所需的datasource的key值
package com.test.main.dataSource; public class DynamicDataSourceHolder { private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
创建dao层切面,注解选择数据源DataSourceAspect类:
package com.test.main.dataSource; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import com.test.main.annotations.DataSource; @Component @Aspect public class DataSourceAspect { @Before("execution(* com.test.model.dao.*.*.*(..))") public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } private void resolveDataSource(Class<?> clazz, Method method) { String sourceName = null; try { Class<?>[] types = method.getParameterTypes(); if (clazz.isAnnotationPresent((Class<? extends Annotation>) DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); sourceName = source.value(); DynamicDataSourceHolder.setDataSource(sourceName); } Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); sourceName = source.value(); DynamicDataSourceHolder.setDataSource(sourceName); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } }
2.spring-db.xml 配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"> <util:properties id="jdbc" location="classpath:etc/mybatis/db.properties" /> <!-- 连接池配置开始 --> <bean id="base" class="com.alibaba.druid.pool.xa.DruidXADataSource" destroy-method="close" lazy-init="true"> <property name="initialSize" value="5" /> <property name="minIdle" value="5" /> <property name="maxActive" value="20" /> <property name="validationQuery" value="SELECT 1" /> <property name="timeBetweenEvictionRunsMillis" value="2800000" /> <property name="minEvictableIdleTimeMillis" value="600000" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="filters" value="stat" /> <property name="logAbandoned" value="true" /> </bean> <!-- Druid连接池 --> <bean id="jpcar" parent="base"> <property name="driverClassName" value="#{jdbc.driverClassName}" /> <property name="url" value="#{jdbc.jpcar_url}" /> <property name="username" value="#{jdbc.username}" /> <property name="password" value="#{jdbc.password}" /> </bean> <bean id="jpcarAtom" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql/jpcar" /> <property name="xaDataSource" ref="jpcar" /> <property name="maintenanceInterval" value="28000" /> <property name="testQuery" value="SELECT 1" /> </bean> <bean id="jpcarData" parent="base"> <property name="driverClassName" value="#{jdbc.driverClassName}" /> <property name="url" value="#{jdbc.jpcarData_url}" /> <property name="username" value="#{jdbc.username}" /> <property name="password" value="#{jdbc.password}" /> </bean> <bean id="jpcarDataAtom" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql/jpcarData" /> <property name="xaDataSource" ref="jpcarData" /> <property name="maintenanceInterval" value="28000" /> <property name="testQuery" value="SELECT 1" /> </bean> <bean id="test_jpcar" parent="base"> <property name="driverClassName" value="#{jdbc.driverClassName}" /> <property name="url" value="#{jdbc.test_jpcar_url}" /> <property name="username" value="#{jdbc.test_username}" /> <property name="password" value="#{jdbc.test_password}" /> </bean> <bean id="test_jpcarAtom" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql/test_jpcar" /> <property name="xaDataSource" ref="test_jpcar" /> <property name="maintenanceInterval" value="28000" /> <property name="testQuery" value="SELECT 1" /> </bean> <bean id="test_jpcarData" parent="base"> <property name="driverClassName" value="#{jdbc.driverClassName}" /> <property name="url" value="#{jdbc.test_jpcarData_url}" /> <property name="username" value="#{jdbc.test_username}" /> <property name="password" value="#{jdbc.test_password}" /> </bean> <bean id="test_jpcarDataAtom" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="mysql/test_jpcarData" /> <property name="xaDataSource" ref="test_jpcarData" /> <property name="maintenanceInterval" value="28000" /> <property name="testQuery" value="SELECT 1" /> </bean> <!-- 连接池配置结束 --> <!-- MyBatis整合开始 --> <bean id="jpcarDataDynamicDataSource" class="com.jpcar.main.dataSource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="jpcar" value-ref="jpcarAtom"></entry> <entry key="jpcarData" value-ref="jpcarDataAtom"></entry> <entry key="test_jpcarData" value-ref="test_jpcarDataAtom"></entry> <entry key="test_jpcar" value-ref="test_jpcarData"></entry> </map> </property> </bean> <bean id="jpcarDataFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="jpcarDataDynamicDataSource" /> <property name="configLocation" value="classpath:etc/mybatis/mybatis-config.xml" /> <property name="mapperLocations" value="classpath:com/**/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.jpcar" /> <property name="annotationClass" value="com.jpcar.main.annotations.MyBatisRepository" /> <property name="sqlSessionFactory" ref="jpcarDataFactory"></property> <property name="sqlSessionFactoryBeanName" value="jpcarDataFactory"></property> </bean> <!-- MyBatis整合结束 --> <!-- 配置数据库事务开始 --> <!-- atomikos事务管理器 --> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <description>UserTransactionManager</description> <property name="forceShutdown"> <value>true</value> </property> </bean> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="90000" /> </bean> <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"> <ref bean="atomikosTransactionManager" /> </property> <property name="userTransaction"> <ref bean="atomikosUserTransaction" /> </property> <property name="allowCustomIsolationLevels" value="true" /> </bean> <tx:annotation-driven transaction-manager="springTransactionManager" /> <!-- 配置数据库事务结束 --> </beans>
2.1在同一个事务中,不同的数据源需要不同 SqlSessionFactoryBean,注意配置时,需要配置 MapperScannerConfigurer的:
<property name="sqlSessionFactoryBeanName" value="cFactory"></property>
value值为:SqlSessionFactoryBean 的 bean id;
2.2同一事务中的SqlSessionFactoryBean,对应MapperScannerConfigurer 中的 basePackage 不能范围重合,不然在同一事务时,spring不会切换数据源,而是取先前与之重合的SqlSessionFactoryBean的数据源