Java 分布式事务规范 JTA 从入门到精通(上)

简介: 前言最近回顾 Spring 事务相关的设计与实现,发现 Spring 事务设计的最初目的是为了统一 Java 中 JDBC、JTA 与 JPA 事务的使用方式,并且其实现参考了 JTA 规范。大多数人对 JDBC 都比较熟悉,而 JTA 和 JPA 由于使用较少,很多人对其比较陌生,尤其是 JTA。

前言


最近回顾 Spring 事务相关的设计与实现,发现 Spring 事务设计的最初目的是为了统一 Java 中 JDBC、JTA 与 JPA 事务的使用方式,并且其实现参考了 JTA 规范。大多数人对 JDBC 都比较熟悉,而 JTA 和 JPA 由于使用较少,很多人对其比较陌生,尤其是 JTA。


网上 JTA 的相关文章,要么参照规范照本宣科的把原理简单介绍,要么就直接上 Spring Boot 整合 JTA 框架的代码,由于知识跨度比较大,增加了对 JTA 理解的难度。经过几天的不懈努力,查阅相关资料,我也对 JTA 有了一定的认识,这里将知识结构以循序渐进的方式进行介绍,也避免小伙伴们走弯路。


JTA 概述


JTA 全称 Java Transaction API,是 X/OPEN CAE 规范中分布式事务 XA 规范在 Java 中的映射,是 Java 中使用事务的标准 API,同时支持单机事务与分布式事务。


作为 J2EE 平台规范的一部分,JTA 与 JDBC 类似,自身只提供了一组 Java 接口,需要由供应商来实现这些接口,与 JDBC 不同的是这些接口需要由不同的供应商来实现。


JTA 定义了分布式事务的 5 种角色,不同角色关注不同的内容,如下图所示。


image.png


1. 事务管理器


事务管理器 Transaction Manager 是分布式事务的核心,在 JTA 中使用 TransactionManager 接口表示,提供了事务操作、资源管理、同步、事务传播等功能,等同于 Spring 中的 PlatformTransactionManager。


事务管理:包括将事务对象存至线程上下文,开始事务,提交事务,回滚事务等常规操作。

资源管理:将资源与事务对象建立关系,以便通过两阶段提交实现事务。

同步:同步原文为 Synchronization,其实是事务完成前后的一个回调接口。

事务传播:指在一个事务中开启子事务时,子事务的行为,与 Spring 事务传播行为类似。


2. 资源管理器


资源管理器 Resource Manager 提供了对资源访问的能力,典型的代表是关系型数据库、消息队列,在 JTA 中使用接口 XAResource 表示,通常通过一个资源适配器来实现,例如 JDBC 中的数据库驱动。


3. 通信资源管理器


通信资源管理器用于支持跨应用分布式事务的事务管理器之间的通信,JTA 规定它实现 JTS 规范定义的接口即可,通常用户不用关心。


4. 应用


使用分布式事务的应用,应用可以通过 JTA 规范中的 UserTransaction 接口来操作事务。


5. 应用服务器


应用的运行环境,JTA 规定事务管理器应该由应用服务器来实现,如 jboss、weblogic、websphere,不过并非所有的应用服务器都实现了事务管理器,如 Tomcat。如果想在标准环境使用 JTA,可以使用支持 JTA 的第三发类库,如 Atomikos、Bitronix。


两阶段提交


事务中有一个特性是原子性,它表示事务中的所有操作要么全部完成,要么全部不完成。事务管理器通过两阶段提交实现这一特性。


两阶段提交将对事务的提交分成两部分,分别为准备阶段和提交阶段。


在准备阶段,事务管理器向资源管理器(如 JMS 消息队列或数据库)询问是否同意提交事务,如果资源管理器回复同意,则表示资源管理器可以将资源持久化。


在提交阶段,如果所有的资源管理器都回复同意,则事务管理器向所有的资源管理器发出提交请求,否则事务管理器向资源管理器发出回滚请求。


正常情况下的两阶段提交可以用如下的图来表示。


111.png

准备阶段失败导致事务回滚的两阶段提交过程可以用如下图来表示。

112.png


细心的小伙伴可能会想到一个问题,虽然准备阶段事务管理器与资源管理器正常交互,不过在提交阶段如果发生一些意外导致事务管理器与资源管理器之间的通信中断怎么办呢?这很可能导致分布式事务违反原子性。


如果资源管理器已经接收到事务管理器的提交请求,事务管理器恢复后要求资源管理器回滚将抛出 HeuristicCommitException 异常。

如果资源管理器决定回滚,事务管理器恢复后要求资源管理器提交,将抛出 HeuristicRollbackException 异常。

如果一部分资源管理器将资源提交,另一部分资源管理器将资源回滚,将导致 HeuristicMixedException 异常。

异常的场景可以用如下图来描述。


113.png


JTA API


上面只是介绍了 JTA 相关的一些概念和基本原理,要想理解 JTA,还得看相关 API。


JTA API 分类


JTA 的 API 大概可以分别为三部分,如下。


114.png


其中,应用使用的接口 UserTransaction 也同样需要事务管理器来实现。

XAResourceXid 接口是 JDK 已提供的接口,位于包 javax.transaction.xa 包中。


image.png


其他的接口需要单独引入 jta 依赖,坐标如下。


<dependency>
    <groupId>jakarta.transaction</groupId>
    <artifactId>jakarta.transaction-api</artifactId>
    <version>1.3.3</version>
</dependency>


其他接口位于包 javax.transaction 中。


image.png


JTA 相关 API


除了 JTA 规范中的 API,特定于实现的资源管理器通常还会实现其他与 JTA 相关的接口。


1. JDBC


对于关系型数据库来说,还需要实现接口 javax.sql.XADataSource 与 javax.sql.XAConnection。


XADataSource 接口定义如下。


public interface XADataSource extends CommonDataSource {
  XAConnection getXAConnection() throws SQLException;
  XAConnection getXAConnection(String user, String password) throws SQLException;
}


XAConnection 接口定义如下。


public interface XAConnection extends PooledConnection {
  javax.transaction.xa.XAResource getXAResource() throws SQLException;
}


也就是说,通过 XADataSource 获取 XAConnection,通过 XAConnection 获取 XAResource,然后再进行资源的相关操作。例如,MySQL 的 JDBC 驱动包就进行了相关实现。


115.png


另外一些第三方包也会进行相关实现,然后将实现作为代理,调用真正的实

现,如 Druid。


116.png


2. JMS


对于 JMS 规范来说,消息队列作为资源管理器,除了要实现 XAResource 接口,还要实现 javax.jms.XASessionjavax.jms.XAConnection 接口。这两个接口定义如下。


public interface XAConnection extends Connection {
    XASession createXASession() throws JMSException;
    Session createSession(boolean var1, int var2) throws JMSException;
}
public interface XASession extends Session {
    Session getSession() throws JMSException;
    XAResource getXAResource();
    boolean getTransacted() throws JMSException;
    void commit() throws JMSException;
    void rollback() throws JMSException;
}


与 JDBC 类似,JMS 通过 XAConnection 获取 XASession,通过 XASession 获取 XAResource,然后再进行资源相关操作。


JTA API 详解


JTA API 围绕事务的整个生命周期,一般来说,用户不用关心具体的 API,不过只有了解 JTA API 的设计才能理解其设计与实现。


TransactionManager


事务管理器可以创建新的事务,并设置事务的相关属性,还允许应用获取创建后的事务,并且将事务与线程绑定。具体有以下方法。


begin:创建新的事务,并且将事务与线程关联,如果不支持嵌套事务并且事务已存在将抛出 NotSupportedException 异常。

commit:提交与线程关联的事务,并断开线程与事务的关联。如果没有事务与线程关联将抛出异常。实现将触发 Transaction.commit、XAResource.prepare、XAResource.commit 方法调用。

rollback:回滚线程关联的事务,并断开线程与事务的关联。如果没有事务与线程关联也将抛出异常。实现将触发 Transaction.rollback、XAResource.rollback 方法的调用。

getTransaction:获取线程关联的事务 Transaction 对象,如果没有事务与当前线程关联则返回 null。

setTansactionalTimeout:设置线程关联事务的超时秒数。

getStatus:获取线程关联事务的状态。

setRollbackOnly:设置线程关联事务的唯一结果是回滚。

suspend:暂时挂起与线程关联的事务,将断开线程与事务的关联,方法返回的 Transaction 对象可作为 resume 方法参数恢复线程与事务的关系。实现将触发 Transaction.delistResource 与 XResource.end 方法的调用。

resume:恢复指定事务与线程之间的关联。实现将触发 Transaction.enlistResource、XAResource.start 方法的调用。


Transaction


Transaction 事务接口用于对活动的事务进行操作,这里活动的事务是指未提交的事务,对其他事务不可见。


enlistResource:将资源添加到事务,以便执行两阶段提交,允许添加多个资源,资源的最终的操作结果将与事务保持一致,即要么提交、要么回滚。实现将触发 XAResource.start 方开启事务分支。

delistResource:解除资源与事务的关联。实现将触发 XAResource.end 方法调用结束事务分支。

registerSynchronization:注册 Synchronization,接口方法将在事务完成前后被回调。

commit:与 TransactionManager.commit 含义相同。

getStatus:与 TransactionManager.getStatus 含义相同。

rollback:与 TransactionManager.rollback 含义相同。

setRollbackOnly:与 TransactionManager.setRollbackOnly 含义相同。


Xid


表示事务与资源的关联,一个事务可以关联多个资源。可以由资源管理器或事务管理器来实现这个接口。接口方法如下:


getFormatId:获取全局事务格式 ID。

getGlobalTransactionId:获取全局事务ID。

getBranchQualifier:获取事务分支限定符。


XAResource


XAResource 用来表示分布式事务角色中的资源管理器,资源适配器将实现 XAResource 接口以支持事务与资源的关联,允许不同的线程同时对 XAResource 进行操作,但一个 XAResource 同时只能关联一个事务。事务提交时,触发资源管理器准备与提交。具体方法如下:


start:开启事务分支,全局事务通过该方法与事务资源关联,由资源适配器获取资源(连接)时隐式触发。一但调用该方法资源将一直保持开启状态,直到释放(close)资源。

end:结束事务分支,解除资源与事务的关联,调用该方法后可再调用 start 方法与其他事务建立关联。

prepare:为 xid 指定的事务提交做准备。

commit:提交 xid 指定的事务分支。

rollback:回滚 xid 指定的事务分支。

isSameRM:判断当前资源管理器是否与给定的资源管理器相同,用于 Transaction.enlistResource 添加资源时判断资源是否已添加。

recover:用于意外导致事务管理器与资源管理器断开通信后,事务管理器恢复时查询准备好的事务。XAResource 在故障发生时未持久化,事务管理器需要有某种方法查找 XAResource,如通过 JNDI 查找机制,事务管理器可以忽略不属于它的事务。

forget:用于忘记准备好的事务分支。

getTransactionTimeout:获取事务超时秒数。

setTransactionTimeout:设置事务超时秒数。


Synchronization


这个接口比较简单,用来表示事务完成前后的回调,由应用实现这个接口。这个接口未持久化,崩溃恢复事务后将丢失 Synchronization 实例。接口方法如下:


beforeCompletion:事务提交前回调该方法。

afterCompletion:事务提交或或回滚后调用该方法。


UserTransaction


功能受限的事务接口,暴露给应用使用,由事务管理器实现。


方法包括 begin、commit、rollback、setRollbackOnly、getStatus、setTransactionTimeout,含义与 Transaction 中的方法相同,不再赘述。


目录
相关文章
|
3天前
|
设计模式 前端开发 Java
【前端学java】SpringBootWeb极速入门-分层解耦(03)
【8月更文挑战第13天】SpringBootWeb极速入门-分层解耦(03)
8 2
【前端学java】SpringBootWeb极速入门-分层解耦(03)
|
4天前
|
开发框架 前端开发 Java
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
【8月更文挑战第12天】SpringBootWeb极速入门-实现一个简单的web页面01
15 3
【前端学java】SpringBootWeb极速入门-实现一个简单的web页面01
|
4天前
|
JSON 前端开发 Java
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
【8月更文挑战第12天】SpringBootWeb极速入门-请求参数解析(02)
10 1
【前端学java】SpringBootWeb极速入门-请求参数解析(02)
|
6天前
|
存储 NoSQL Java
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
|
6天前
|
Java 测试技术 Spring
Java 新手入门:依赖注入的 N 种姿势,总有一款适合你!
Java 新手入门:依赖注入的 N 种姿势,总有一款适合你!
15 2
|
6天前
|
Java
Java 新手入门:重载和重写傻傻分不清?一篇文章带你清晰理解!
Java 新手入门:重载和重写傻傻分不清?一篇文章带你清晰理解!
17 0
Java 新手入门:重载和重写傻傻分不清?一篇文章带你清晰理解!
|
4天前
|
存储 监控 算法
掌握Java内存管理:从入门到精通
在Java的世界里,内存管理是程序运行的心脏。本文将带你走进Java内存管理的奥秘,从基础概念到高级技巧,一步步揭示如何优化你的Java应用。准备好迎接挑战,让我们共同揭开高效内存使用的面纱!
|
5天前
|
Java 网络安全 开发工具
新手入门Java。如何下载Eclipse、写出最基本的“Hello word”以及如何连接github并且上传项目。
新手入门Java。如何下载Eclipse、写出最基本的“Hello word”以及如何连接github并且上传项目。
16 0
|
5天前
|
Java 程序员 API
【JAVA】JAVA入门(长期维护)(2)
【JAVA】JAVA入门(长期维护)(2)
|
5天前
|
存储 Oracle Java
【JAVA】JAVA入门(长期维护)(1)
【JAVA】JAVA入门(长期维护)(1)