版权声明:本文为CSDN博主「luzhouyue1024」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/luzhouyue1024/article/details/128887235
在前一篇学习Spring框架下的分布式事务_luzhouyue1024的博客-CSDN博客 的基础上,实战编程一个分布式事务管理的项目
代码采用H2内存数据库,有两个h2数据源,第一个数据源firstDataSource是一个AtomikosDataSourceBean的实例,并注入了链接了DB1的一个内存数据库(数据库类型H2),配置文件如下:
package com.lydia.multipledatasources.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @description:
* @author: Lydia Lee
*/
@Configuration
//配置 mapper 的扫描位置,指定相应的 sqlSessionTemplate
@MapperScan(basePackages = "com.lydia.multipledatasources.mapper.first", sqlSessionTemplateRef = "firstSqlSessionTemplate")
public class FirstDataSourceConfig {
@Autowired
FirstDataSourceHelper dataSourceHelper;
@Bean
@Primary
// 读取配置,创建Atomiko数据源
public DataSource firstDataSource() {
// 设置数据库连接
org.h2.jdbcx.JdbcDataSource h2XADataSource = new org.h2.jdbcx.JdbcDataSource();
h2XADataSource.setUser(dataSourceHelper.getUsername());
h2XADataSource.setPassword(dataSourceHelper.getPassword());
h2XADataSource.setURL(dataSourceHelper.getJdbcUrl());
// 事务管理器
AtomikosDataSourceBean firstDataSource = new AtomikosDataSourceBean();
firstDataSource.setXaDataSource(h2XADataSource);
firstDataSource.setUniqueResourceName("firstDataSource");
return firstDataSource;
}
@Bean
@Primary
// 创建 SqlSessionFactory
public SqlSessionFactory firstSqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// 设置 xml 的扫描路径
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/first/*.xml"));
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
config.setMapUnderscoreToCamelCase(true);
bean.setConfiguration(config);
return bean.getObject();
}
@Bean
@Primary
// 创建 SqlSessionTemplate
public SqlSessionTemplate firstSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
第二个数据源secondDataSource类似配置即可,详情见GitHub源代码
配置事务管理类如下,创建 AtomikosTransactionManager 用于分布式事务管理,并把它注入到Spring的事务管理框架JtaTransactionManager中
package com.lydia.multipledatasources.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
/**
* @description: 配置并为Spring Boot注入了JTA TransactionManager
* 实现为Atomikos提供的atomikos UserTransactionManager
* @author: Lydia Lee
*/
@Configuration
public class TransactionManagerConfig {
@Bean
@Primary
// 创建 AtomikosTransactionManager 用于分布式事务管理
public UserTransactionManager atomikosTransactionManager(DataSource dataSource) {
UserTransactionManager transactionManager = new UserTransactionManager();
transactionManager.setForceShutdown(true);
return transactionManager;
}
//配置Spring的JtaTransactionManager,底层委派给atomikos进行处理
@Bean
public JtaTransactionManager jtaTransactionManager (UserTransactionManager userTransactionManager) {
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
主要原理是实例化两个数据源first和second,他们均由AtomikosDataSourceBean管理数据链接,然后在一个全局事务中,两个数据源如果成功就会一起提交,失败则会一起回滚。
让我们把注意力聚焦到业务逻辑UserService里面的insertTwoDBWithTXRollback(String name)这个方法中,虽然firstUser和secondUser已经调用了insert操作,但是由于1/0产生了异常,导致整个事务被JtaTransactionManager回滚,也就是两条记录并没有插入两个数据库。
/**
* @description:
* @author: Lydia Lee
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
FirstUserMapper firstUserMapper;
@Autowired
SecondUserMapper secondUserMapper;
@Autowired
JtaTransactionManager jtaTransactionManager;
//1.使用注释型@Transactional
//2.事物开始后,由于除以0的异常,全局事务触发rollback(DB1和DB2都触发rollback)
//虽然DB1和DB2执行了insert,但是由于两阶段提交模型,在事务结束前抛出了异常,因此DB1和DB2数据并没有更改
@Transactional(rollbackFor = Throwable.class)
public void insertTwoDBWithTXRollback(String name) {
User user = new User();
user.setName(name);
firstUserMapper.insert(user);
secondUserMapper.insert(user);
// 主动触发回滚
int i = 1/0;
}
}
而业务逻辑UserService里面的insertTwoDBWithTX能够成功执行,因为没有任何异常发生,所以分别向DB1和DB2插入了数据。
/**
* @description:
* @author: Lydia Lee
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
FirstUserMapper firstUserMapper;
@Autowired
SecondUserMapper secondUserMapper;
@Autowired
JtaTransactionManager jtaTransactionManager;
@Override
//1.使用注释型@Transactional
//2.成功向DB1和DB2插入数据,并提交成功
@Transactional(rollbackFor = Throwable.class)
public void insertTwoDBWithTX(String name) {
User user = new User();
user.setName(name);
// 会回滚
firstUserMapper.insert(user);
// 不会回滚
secondUserMapper.insert(user);
}
}
可以运行test包下的相关方法运行事务并显示数据库存储内容。
Atomikos对分布式事务的实现,从本demo工程来看,可以在类com.atomikos.icatch.imp.CompositeTransactionImp处设一个断点(下图所示位置),运行debug模式,看到在每次dataSoure执行sql语句的prepareStatement阶段,会注册一个Synchronization。这里不详述,感兴趣的朋友可以看源码。这里简述下,Atomikos对每个分布式事务有一个compositeTransaction,用于记录多个数据源。单个资源参与到分布式事务中时,会注册资源到这个分布式事务的上下文中,那么各个资源都注册后,事务管理器就会直到如何去通知他们一起提交或者回滚。
附上源码的github地址:
GitHub - LuzhouyueLee/spring-demo