背景介绍
本地事务依赖于底层资源管理器(数据库连接),事务局限于当前事务资源内。但是随着业务发展,系统出现多数据源等,本地事务无法满足ACID,需要采用分布式事务。
分布式事务处理(JTA)
Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。
JTA架构: 包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分。 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源。
public void transferAccount() {
UserTransaction userTx = null;
Connection connA = null;
Statement stmtA = null;
Connection connB = null;
Statement stmtB = null;
try{
// 获得 Transaction 管理对象
userTx = (UserTransaction)getContext().lookup("\
java:comp/UserTransaction");
// 从数据库 A 中取得数据库连接
connA = getDataSourceA().getConnection();
// 从数据库 B 中取得数据库连接
connB = getDataSourceB().getConnection();
// 启动事务
userTx.begin();
// 将 A 账户中的金额减少 500
stmtA = connA.createStatement();
stmtA.execute("
update t_account set amount = amount - 500 where account_id = 'A'");
// 将 B 账户中的金额增加 500
stmtB = connB.createStatement();
stmtB.execute("\
update t_account set amount = amount + 500 where account_id = 'B'");
// 提交事务
userTx.commit();
// 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
} catch(SQLException sqle){
try{
// 发生异常,回滚在本事务中的操纵
userTx.rollback();
// 事务回滚:转账的两步操作完全撤销
//( 数据库 A 和数据库 B 中的数据更新被同时撤销)
stmt.close();
conn.close();
...
}catch(Exception ignore){
}
sqle.printStackTrace();
} catch(Exception ne){
e.printStackTrace();
}
}
SpringBoot与JTA整合
//数据源配置
@Configuration
public class DataSourceCfg {
//jta数据源1
@Bean(initMethod="init", destroyMethod="close", name="jtaDataSource1")
@Primary
@ConfigurationProperties(prefix = "ds1.jdbc")
public DataSource jtaDataSource1() throws AtomikosSQLException {
return new AtomikosDataSourceBean();
}
//jta数据源1
@Bean(initMethod="init", destroyMethod="close", name="jtaDataSource2")
@ConfigurationProperties(prefix = "ds2.jdbc")
public DataSource jtaDataSource2() throws AtomikosSQLException {
return new AtomikosDataSourceBean();
}
@Bean
public JdbcTemplate ds1JdbcTemplate(@Qualifier("jtaDataSource1") DataSource jtaDataSource1) {
return new JdbcTemplate(jtaDataSource1);
}
@Bean
public JdbcTemplate ds2JdbcTemplate(@Qualifier("jtaDataSource2") DataSource jtaDataSource2) {
return new JdbcTemplate(jtaDataSource2);
}
}
/**
* 事务配置类
*
* @author alvinkk
* @create 2018-05-22 17:32
**/
@Configuration
public class TransactionManagerConfig {
@Bean
public UserTransaction userTransaction() throws SystemException {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager());
return manager;
}
}
@Component
public class TransactionServiceImpl implements TransactionService {
@Autowired
@Qualifier("ds1JdbcTemplate")
private JdbcTemplate jdbcTemplate1;
@Autowired
@Qualifier("ds2JdbcTemplate")
private JdbcTemplate jdbcTemplate2;
@Override
@Transactional
public void save() {
jdbcTemplate1.execute("insert into user values ('alvin', 'sss', 12)");
jdbcTemplate2.execute("insert into user values ('alvin11', 'sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss', 12)");
}
}
//配置文件
ds1.jdbc:
uniqueResourceName: ds1
xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
xaProperties:
url: jdbc:mysql://10.100.1.85:3306/alvin_demo?useUnicode=true&characterEncoding=utf-8
user: root
password: root
exclusiveConnectionMode: true
minPoolSize: 3
maxPoolSize: 10
testQuery: SELECT 1 from dual #由于采用HikiriCP,用于检测数据库连接是否存活。
ds2.jdbc:
uniqueResourceName: ds2
xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
xaProperties:
url: jdbc:mysql://10.100.1.85:3306/alvin1_demo?useUnicode=true&characterEncoding=utf-8
user: root
password: root
exclusiveConnectionMode: true
minPoolSize: 3
maxPoolSize: 10
testQuery: SELECT 1 from dual #由于采用HikiriCP,用于检测数据库连接是否存活。
//测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class AppTest {
@Autowired
private TransactionService transactionService;
@Test
public void testTransaction() {
transactionService.save();
}
}
git地址: https://github.com/alvinlkk/springboot-learning-demo/tree/master/springboot-jta
优缺点
优点: 能够支持分布式事务
缺点: 性能开销大,不适合用于高并发场景
参考
https://www.cnblogs.com/Leo_wl/p/5728027.html https://www.ibm.com/developerworks/cn/java/j-lo-jta/