事务在后端中是占着很重要的地位,也是一个比较难处理的地方。以我现在所知可以将事务分为两大类,一类本地事务,一类分布式事务。分类标准也很简单,本地事务就是一个系统只用一个数据库,且只在一个项目下。分布式事务就是一个系统用多个数据库,或一个项目用了多个数据库。本篇呢就简单记录下在一个项目下用多个数据库的事务处理。
一、初识JTA
JTA,即Java Transaction API,JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
这是来自百度百科的解释。他的根本目标就是为了多数据库下的事务统一,维护ACID特性。
要使用JTA事务,必须使用XADataSource来产生数据库连接,产生的连接为一个XA连接。
这是使用JTA事务的前提,数据源必须是XADataSource。在这个条件的约束下一些我们常用的数据库连接池(如:hikaricp,c3p0,dbcp
)就没法使用咯。但我们熟知的阿里还是很强的,阿里的druid
提供了对XADataSource的支持。
JTA只是一套接口定义,具体实现要靠各个厂商的支持。本次使用的是Atomikos
。
二、在spring中添加JAT依赖
使用maven,添加pom依赖
1、SSM
com.atomikos
transactions-jdbc
4.0.6
javax.transaction
javax.transaction-api
1.3
2、springboot
org.springframework.boot
spring-boot-starter-jta-atomikos
其实需要很多的依赖,这里transaction-jdbc
会自动依赖其他需要的。
三、进行JTA配置
之前也说过了要支持JAT事务,一般的数据源是不能使用的,所以一般的数据库连接池就不能使用了。本次我也没有使用阿里家的数据库连接池,直接使用Atimikos提供的数据库连接池。
1、SSM项目配置
- 多数据源配置
- 全局事务配置
- 使用mybatisplus,sqlsession工厂具体配置如下
2、springboot配置
- 多数据源以及sqlsession工厂配置
@Configuration
@MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper01",
sqlSessionFactoryRef = "ds01_sqlSession")
@MapperScan(basePackages = "cn.lkangle.multids01.mappers.mapper02",
sqlSessionFactoryRef = "ds02_sqlSession")
public class MultiDSPlusConfig {
// ----------- 主数据源 -----------
@Bean("ds01")
@Primary
public DataSource data1Source() {
MysqlXADataSource dataSource = new MysqlXADataSource();
dataSource.setURL("jdbc:mysql:///multi_ds01");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
dataSourceBean.setXaDataSource(dataSource);
dataSourceBean.setMaxPoolSize(10);
dataSourceBean.setUniqueResourceName("ds01_datasource");
dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
return dataSourceBean;
}
// ------------- 第二数据源 -------------
@Bean("ds02")
public DataSource data2Source() {
MysqlXADataSource dataSource = new MysqlXADataSource();
dataSource.setURL("jdbc:mysql:///multi_ds02");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
dataSourceBean.setXaDataSource(dataSource);
dataSourceBean.setMaxPoolSize(10);
dataSourceBean.setUniqueResourceName("ds02_datasource");
dataSourceBean.setXaDataSourceClassName("com.mysql.jdbc.Driver");
return dataSourceBean;
}
// ---------- 不同数据源对应的 sql session 工厂 -----------
@Bean("ds01_sqlSession")
public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("ds01") DataSource dataSource) throws IOException {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
Resource[] resources = loader.getResources("classpath:mapper/ds01/*.xml");
bean.setMapperLocations(resources);
return bean;
}
@Bean("ds02_sqlSession")
public MybatisSqlSessionFactoryBean sqlSession2FactoryBean(@Qualifier("ds02") DataSource dataSource) throws IOException {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(dataSource);
ResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
Resource[] resources = loader.getResources("classpath:mapper/ds02/*.xml");
bean.setMapperLocations(resources);
return bean;
}
}
- 开启全局JTA事务配置
@Configuration
@EnableTransactionManagement
public class GlobalTxConfig {
@Bean
public UserTransaction userTransaction() throws SystemException {
UserTransactionImp transactionImp = new UserTransactionImp();
transactionImp.setTransactionTimeout(20000);
return transactionImp;
}
@Bean(initMethod = "init", destroyMethod = "close")
public UserTransactionManager userTransactionManager() {
return new UserTransactionManager();
}
@Bean
public JtaTransactionManager transactionManager(@Qualifier("userTransaction") UserTransaction userTransaction,
@Qualifier("userTransactionManager") UserTransactionManager userTransactionManager) {
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
已上即完成了JTA事务管理的配置。
四、测试
完成已上的各种配置后,我们就可以像使用本地事务那样实现全局事务的处理了。这里就是只贴出service层的代码了。
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private Ds01Mapper ds01Mapper;
@Autowired
private Ds02Mapper ds02Mapper;
// 声明式
@Transactional
public int insert1(MUser user) {
int ds1 = ds01Mapper.insert(user);
int ds2 = ds02Mapper.insert(user);
int p = 9 / user.getNum();
return ds1 + ds2;
}
// 编程式
public int insert(MUser user) {
return transactionTemplate.execute(status -> {
System.out.println(status);
int ds1 = ds01Mapper.insert(user);
int ds2 = ds02Mapper.insert(user);
int p = 9 / user.getNum();
return ds1 + ds2;
});
}
......
}
当num为0时,产生异常事务回滚。
五、总结
捣鼓了好久才调通,在这里记录下。出现的问题有1、再配置全局事务的同时,还配置了jdbc的事务,这是无法使用的。2、在Controller中是用事务。不好意思也没成功,主要是ssm中配置的时候事务只对非controller层中有效
源码地址JTA样例