当更新的多个表不在同一数据库的时候,但是它们又必须在同一事务内,这个时候分布式事务就派上用场啦。
用到的jar包
atomikos-util.3.7.0.jar
transactions-3.7.0.jar
transactions-api-3.7.0.jar
transactions-jdbc-3.7.0.jar
transactions-jta-3.7.0.jar
jta-1.1.jar
配置很关键。
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<!-- 引入数据源信息的properties属性文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- XA方式 -->
<!-- MYSQL数据库配置 -->
<bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
<property name="uniqueResourceName" value="dataSource1"/>
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
<property name="xaProperties">
<props>
<prop key="URL">${mysql.url}</prop>
<prop key="user">${mysql.username}</prop>
<prop key="password">${mysql.password}</prop>
</props>
</property>
<property name="minPoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="borrowConnectionTimeout" value="30" />
<property name="maintenanceInterval" value="60" />
</bean>
<!-- ORACLE数据库配置 -->
<bean id="oracleDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" destroy-method="close">
<property name="uniqueResourceName" value="dataSource2"/>
<property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource" />
<property name="xaProperties">
<props>
<prop key="URL">${orcale.url}</prop>
<prop key="user">${orcale.username}</prop>
<prop key="password">${orcale.password}</prop>
</props>
</property>
<property name="minPoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="borrowConnectionTimeout" value="30" />
<property name="maintenanceInterval" value="60" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config-mysql.xml" />
<property name="dataSource" ref="mysqlDataSource" />
</bean>
<bean id="sqlSessionFactoryOracle" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="dataSource" ref="oracleDataSource" />
</bean>
<!-- MyBatis为不同的mapper注入sqlSessionFactory -->
<bean id="mysqlTransactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<property name="mapperInterface" value="com.springmybatis.system.dao.mysqltransaction.MysqlTransactionTestDao" />
</bean>
<bean id="transactionTestDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactoryOracle" />
<property name="mapperInterface" value="com.springmybatis.system.dao.transaction.TransactionTestDao" />
</bean>
<!-- 分布式事务 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<property name="forceShutdown" value="true"/>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300"/>
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager" ref="atomikosTransactionManager"/>
<property name="userTransaction" ref="atomikosUserTransaction"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 开启注解 -->
<context:annotation-config />
<!-- base-package指向注解要扫描的包 -->
<context:component-scan base-package="com.springmybatis.system" />
<!-- 注册拦截器 -->
<mvc:interceptors>
<bean class="com.springmybatis.system.interceptor.MyInterceptor" />
</mvc:interceptors>
</beans>
JUnit测试类。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TransactionTestMain {
@Autowired
private TransactionTestService transactionTestService;
/**
* 在同一事务有多个数据源
*/
@Test
public void multipleDataSource2() {
transactionTestService.updateMultipleDataSource(1, 2, 200L);
}
}
Service接口。
public interface TransactionTestService {
/**
* 转账操作
* @param deUserId 转出账号
* @param inUserid 装入账号
* @param money 交易金额
*/
public void updateMultipleDataSource(int deUserId, int inUserid, long money);
}
Service实现类。
@Service
public class TransactionTestServiceImpl implements TransactionTestService {
@Autowired
@Qualifier("mysqlTransactionTestDao")
private MysqlTransactionTestDao mysqlTransactionTestDao;
@Autowired
@Qualifier("transactionTestDao")
private TransactionTestDao transactionTestDao;
/**
* 在同一事务有多个数据源
*/
@Override
@Transactional
public void updateMultipleDataSource(int deUserId, int inUserid, long money) {
// 账户1转出操作
mysqlTransactionTestDao.decreaseMoney(deUserId, money);
// 账户2转入操作
transactionTestDao.increaseMoney(inUserid, money);
}
}
MysqlTransactionTestDao接口。
public interface MysqlTransactionTestDao {
public void decreaseMoney(Integer userId, Long money);
}
MysqlTransactionTestDao实现类。
@Repository
public class MysqlTransactionTestDaoImpl extends SqlSessionDaoSupport implements MysqlTransactionTestDao {
// 1,重写父类 【SqlSessionDaoSupport】方法实现注入【SqlSessionFactory】。
// 2,必须要有,在配置文件【applicationContext.xml】要配置过。
@Resource
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
/**
* 转出操作
* @param userId 转出账号
* @param moneyNm 交易金额
*/
@Override
public void decreaseMoney(@Param("userId") Integer userId, @Param("moneyNm") Long moneyNm) {
// 参数设定
TransactionTestDao mapper = super.getSqlSession().getMapper(TransactionTestDao.class);
mapper.decreaseMoney(userId, moneyNm);
}
}
MysqlTransactionTestDao.xml内容。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springmybatis.system.dao.mysqltransaction.MysqlTransactionTestDao">
<!-- 开启二级缓存 -->
<!-- eviction :回收策略 -->
<!-- flushInterval:自动刷新时间 -->
<!-- size :最多缓存对象数 -->
<!-- readOnly :只读 -->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
<update id="decreaseMoney" parameterType="java.util.Map">
UPDATE if_kikaku.test_accout SET balance=balance - #{1,jdbcType=BIGINT} WHERE user_id=#{0,jdbcType=INTEGER}
</update>
</mapper>
TransactionTestDao接口。
public interface TransactionTestDao {
public void increaseMoney(Integer userId, Long moneyNm);
}
TransactionTestDao实现类。
@Repository
public class TransactionTestDaoImpl extends SqlSessionDaoSupport implements TransactionTestDao {
// 1,重写父类 【SqlSessionDaoSupport】方法实现注入【sqlSessionFactory】。
// 2,必须要有,在配置文件【applicationContext.xml】要配置过。
@Resource
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory){
super.setSqlSessionFactory(sqlSessionFactory);
}
/**
* 转入操作
* @param userId 装入账号
* @param money 交易金额
*/
@Override
public void increaseMoney(@Param("userId") Integer userId, @Param("moneyNm") Long moneyNm) {
TransactionTestDao mapper = super.getSqlSession().getMapper(TransactionTestDao.class);
mapper.increaseMoney(userId, moneyNm);
}
}
TransactionTestDao.xml内容。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springmybatis.system.dao.transaction.TransactionTestDao">
<!-- 开启二级缓存 -->
<!-- eviction :回收策略 -->
<!-- flushInterval:自动刷新时间 -->
<!-- size :最多缓存对象数 -->
<!-- readOnly :只读 -->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
<delete id="increaseMoney">
UPDATE if_kikaku.test_accout SET balance=balance + #{1,jdbcType=BIGINT} WHERE user_id=#{0,jdbcType=INTEGER}
</delete>
</mapper>
atomikos的配置文件jta.properties
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.console_file_name=tm.out
com.atomikos.icatch.log_base_name=jdbc
com.atomikos.icatch.tm_unique_name=com.atomikos.spring.jdbc.tm
com.atomikos.icatch.console_log_level=false
代码结构如下图。
mybatis-config-mysql.xml扫描的mapper文件。
<mappers>
<package name="com.springmybatis.system.dao.mysqltransaction" />
</mappers>
mybatis-config.xml扫描的mapper文件。
<mappers>
<!-- 通过mapper元素的resource属性可以指定相对于类路径的Mapper.xml文件 -->
<mapper resource="com/springmybatis/system/dao/TestUserDao.xml" />
<!-- 通过mapper元素的class属性可以指定Mapper接口进行注册 -->
<mapper class="com.springmybatis.system.dao.UserDao" />
<!-- 通过package元素将会把指定包下面的所有Mapper接口进行注册 -->
<package name="com.springmybatis.system.dao.transaction" />
</mappers>