背景:
项目中业务分库需要使用到动态数据源,本来使用的Spring的AbstractRoutingDataSourced来实现,但是测试时发现其无法控制跨库的事务,就改为使用JTA-Atomikos来做丰台数据源了。JTA集成动态数据源比较简单,快捷。并不需要修改多少代码,只需更改下我们的配置文件就ok了。
JTA的定义:(引用自百度)
Java事务API(Java Transaction API,简称JTA ) 是一个Java企业版 的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务.
一个分布式的事务涉及一个事务管理器和一个或者多个资源管理器。一个资源管理器是任何类型的持久性的数据存储。事务管理器负责协调所有事务参与者之间的通信。常见的JTA实现有以下几种:
1.J2EE容器所提供的JTA实现(JBoss中jndi容器事物配置)
2.独立的JTA实现:如JOTM,Atomikos等开源事物管理器
这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事事务保证。如Tomcat,Jetty以及普通的java应用。本篇中我们使用的是Atomikos。
1.Atomikos的maven依赖配置如下:
<!-- atomikos 依赖--> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jdbc</artifactId> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>atomikos-util</artifactId> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions</artifactId> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jta</artifactId> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-api</artifactId> </dependency> <!-- jta 事务 --> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </dependency>
2.为每个数据库配置数据源,这里注意mysql和Oracle数据库的xaDataSourceClassName 配置是不一样的
oracle数据库:oracle.jdbc.xa.client.OracleXADataSource; myslq数据库:com.mysql.jdbc.jdbc2.optional.MysqlXADataSource.
<aop:aspectj-autoproxy /> <!-- 读取数据库配置文件 --> <context:property-placeholder location="classpath:jdbc.properties" ignore-unresolvable="true"/> <!-- 两个数据源的功用配置,方便下面直接引用 --> <bean id="abstractXADataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close" abstract="true"> <property name="poolSize" value="10" /> <property name="minPoolSize" value="10"/> <property name="maxPoolSize" value="30"/> <property name="borrowConnectionTimeout" value="60"/> <!--获取连接失败重新获等待最大时间,在这个时间内如果有可用连接,将返回--> <property name="reapTimeout" value="20"/> <!--最大获取数据时间,如果不设置这个值,Atomikos使用默认的5分钟,那么在处理大批量数据读取的时候,一旦超过5分钟,就会抛出类似 Resultset is close 的错误.--> <property name="maxIdleTime" value="60"/> <!--最大闲置时间,超过最小连接池连接的连接将将关闭--> <property name="maintenanceInterval" value="60" /> <!--连接回收时间--> <property name="loginTimeout" value="60" /> <!--java数据库连接池,最大可等待获取datasouce的时间--> <property name="logWriter" value="60"/> <property name="testQuery"> <value>select 1</value> </property> </bean> <!-- 配置第一个数据源 --> <bean id="busDataSource" parent="abstractXADataSource"> <!-- value只要两个数据源不同就行,随便取名 --> <property name="uniqueResourceName" value="busDataSource" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc.busUrl}</prop> <prop key="user">${jdbc.username}</prop> <prop key="password">${jdbc.password}</prop> </props> </property> </bean> <!-- 配置第二个数据源--> <bean id="dspDataSource" parent="abstractXADataSource"> <!-- value只要两个数据源不同就行,随便取名 --> <property name="uniqueResourceName" value="dspDataSource" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="xaProperties"> <props> <prop key="URL">${jdbc.dspUrl}</prop> <prop key="user">${jdbc.username}</prop> <prop key="password">${jdbc.password}</prop> </props> </property> </bean>
3.再为每一个数据源对应的dao层,也就是mapper层,创建其对应的SqlSessionFactory,并建立其与dao层包名的对应关系。
<bean id="busSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="busDataSource" /><!-- bus dataSource--> <property name="mapperLocations" value="classpath*:cn/hccake/test/bus/model/mappers/**/*.xml" /> </bean> <bean id="dspSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dspDataSource" /><!-- dsp dataSource--> <property name="mapperLocations" value="classpath*:cn/hccake/test/dsp/model/mappers/**/*.xml" /> </bean> <!--scan for mappers and let them be autowired--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.moppo.domino.bus.dao"/> <property name="sqlSessionFactoryBeanName" value="busSqlSessionFactory" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.moppo.domino.dsp.dao"/> <property name="sqlSessionFactoryBeanName" value="dspSqlSessionFactory" /> </bean>
4.最后 配置对应的事物管理器:atomikosTransactionManager;
atomikosUserTransaction将其注入到spring管理的JTA事物管理器中 org.springframework.transaction.jta.JtaTransactionManager<!-- 开始事物注解扫描 --> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <!-- 事务管理 --> <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <description>UserTransactionImp</description> <property name="transactionTimeout" value="300"/> </bean> <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="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <description>JtaTransactionManager</description> <property name="transactionManager"> <ref bean="atomikosTransactionManager" /> </property> <property name="userTransaction"> <ref bean="atomikosUserTransaction" /> </property> <property name="allowCustomIsolationLevels" value="true"/> <!-- 必须设置,否则程序出现异常 JtaTransactionManager does not support custom isolation levels by default --> </bean> <!-- 简单的事务切面控制 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="select" read-only="true" /> <tx:method name="find*" read-only="true" /> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="*" propagation="SUPPORTS" rollback-for="Throwable" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="interceptorPointCuts" expression="execution(* cn.hccake.test.*..*Service.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" /> </aop:config>
5.使用时,在简单的切面控制事务之外,也可在类名或者方法名上使用@Transactional注解来进行事务控制,和spring事务管理的控制方法是一模一样的
@Service @Transactional public class TestService { @Autowired private AdTypeService adTypeService; @Autowired private BusTypeService busTypeService; @Autowired private AdTypeDao adTypeDao; public void updateTest(){ adTypeService.updateTest("test1"); busTypeService.updateTest("test2"); adTypeDao.updateTest("aaa"); throw new RuntimeException("分布式回滚控制"); } }