利用spring boot集成JTA(Atomikos案例)实现分布式事务控制;
maven引入:
org.springframework.boot
spring-boot-starter-jta-atomikos
原理简述:
将spring原有的数据源信息管理的类型改为atomikos实现的AtomikosDataSourceBean数据源类(他也是javax.sql.DataSource的实现类),该类有一个属性是xaDataSource(xa就是分布式事务处理的一种模型规范),实现一下xaDataSource赋给这个属性;
之后各个数据源不要再独自去声明事务控制对象了,因为这时候Atomikos会自动有一个统一的分布式事务控制对象来控制事务;
代码:
application.yml 数据源配置方式:
spring:
datasource01:
mapperPackage: com.alicyu.springcloud.dao.dbone #mapper包路径
mapperxmlDir: classpath:mybatis/mapper/dbone/**/*.xml #mapper.xml路径
entityPackage: com.alicyu.springcloud.entities.dbone #实体包路径
mybatiscfg: classpath:mybatis/mybatis.cfg.xml #mapper对应mybatis通用配置文件路径
url: jdbc:mysql://localhost:3306/clouddb01?useSSL=false
username: root
password: zhicheng
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
datasource02:
mapperPackage: com.alicyu.springcloud.dao.dbtwo #mapper包路径
mapperxmlDir: classpath:mybatis/mapper/dbtwo/**/*.xml #mapper.xml路径
entityPackage: com.alicyu.springcloud.entities.dbtwo #实体包路径
mybatiscfg: classpath:mybatis/mybatis.cfg.xml #mapper对应mybatis通用配置文件路径
url: jdbc:mysql://localhost:3306/clouddb02?useSSL=false
username: root
password: zhicheng
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
package com.alicyu.config;
import lombok.Data;
@Data
public class DBConfig1 {
private String entityPackage;
private String mapperxmlDir;
private String mybatiscfg;
private String url;
private String username;
private String password;
private int minPoolSize;
private int maxPoolSize;
private int maxLifetime;
private int borrowConnectionTimeout;
private int loginTimeout;
private int maintenanceInterval;
private int maxIdleTime;
private String testQuery;
}
package com.alicyu.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
public class DBConfig2 {
private String entityPackage;
private String mapperxmlDir;
private String mybatiscfg;
private String url;
private String username;
private String password;
private int minPoolSize;
private int maxPoolSize;
private int maxLifetime;
private int borrowConnectionTimeout;
private int loginTimeout;
private int maintenanceInterval;
private int maxIdleTime;
private String testQuery;
}
package com.alicyu.config;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "${spring.datasource01.mapperPackage}", sqlSessionFactoryRef = "SqlSessionFactory")
public class DataSourceOneConfig {
@Bean(name = "DBConfig1")
@ConfigurationProperties(prefix="spring.datasource01")
@Primary
public DBConfig1 dBConfig1() {
return new DBConfig1();
}
@Bean(name = "DataSource")
@Primary
public DataSource dataSource(@Qualifier("DBConfig1")DBConfig1 testConfig) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
// 将本地事务注册到创 Atomikos全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("DataSource");
xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
//提供SqlSeesion
@Bean(name = "SqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("DataSource") DataSource dataSource,@Qualifier("DBConfig1")DBConfig1 testConfig) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//下边的值可以通过@value
// 实体返回映射
sqlSessionFactoryBean.setTypeAliasesPackage(testConfig.getEntityPackage());
// sql xml文件路径
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(testConfig.getMapperxmlDir()));
// 配置文件
sqlSessionFactoryBean.setConfigLocation(resolver.getResource(testConfig.getMybatiscfg()));
return sqlSessionFactoryBean.getObject();
}
// 因为事务会统一交给Atomikos全局事务,(因为是用了AtomikosDataSourceBean管理数据源),所以不能添加其他事务管理器
// // 事务管理
// @Bean(name = "transactionManager")
// @Primary
// public DataSourceTransactionManager transactionManager(@Qualifier("DataSource") DataSource dataSource) {
// return new DataSourceTransactionManager(dataSource);
// }
// sqlSessionTemplate
@Bean(name = "sqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("SqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.alicyu.config;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "${spring.datasource02.mapperPackage}", sqlSessionFactoryRef = "SqlSessionFactory02")
public class DataSourceTwoConfig {
@Bean(name = "DBConfig2")
@ConfigurationProperties(prefix="spring.datasource02")
public DBConfig2 dBConfig2() {
return new DBConfig2();
}
@Bean(name = "DataSource02")
public DataSource dataSource(@Qualifier("DBConfig2")DBConfig2 testConfig) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
// 将本地事务注册到创 Atomikos全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("DataSource02");
xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
//提供SqlSeesion
@Bean(name = "SqlSessionFactory02")
public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("DataSource02") DataSource dataSource,@Qualifier("DBConfig2")DBConfig2 testConfig) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//下边的值可以通过@value
// 实体返回映射
sqlSessionFactoryBean.setTypeAliasesPackage(testConfig.getEntityPackage());
// sql xml文件路径
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(testConfig.getMapperxmlDir()));
// 配置文件
sqlSessionFactoryBean.setConfigLocation(resolver.getResource(testConfig.getMybatiscfg()));
return sqlSessionFactoryBean.getObject();
}
// 因为事务会统一交给Atomikos全局事务,(因为是用了AtomikosDataSourceBean管理数据源),所以不能添加其他事务管理器
// // 事务管理
// @Bean(name = "transactionManager02")
// public DataSourceTransactionManager transactionManager(@Qualifier("DataSource02") DataSource dataSource) {
// return new DataSourceTransactionManager(dataSource);
// }
// sqlSessionTemplate
@Bean(name = "sqlSessionTemplate02")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("SqlSessionFactory02") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
之后对两个数据源的操作,抛异常后数据都会滚了
有时候还会报一些分布式事务授权问题,有时候及时报这个错也能用,然后要给相应的数据库用户受相应的权限
解决方法:数据库执行 GRANT XA_RECOVER_ADMIN ON *.* TO `root`@`%`; 授予root用户在所有库下的所有表上执行 XA RECOVER语句的权限 或参考:https://ask.csdn.net/questions/759904