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 中的方法相同,不再赘述。


目录
相关文章
|
20天前
|
存储 SQL 分布式数据库
OceanBase 入门:分布式数据库的基础概念
【8月更文第31天】在当今的大数据时代,随着业务规模的不断扩大,传统的单机数据库已经难以满足高并发、大数据量的应用需求。分布式数据库应运而生,成为解决这一问题的有效方案之一。本文将介绍一款由阿里巴巴集团自主研发的分布式数据库——OceanBase,并通过一些基础概念和实际代码示例来帮助读者理解其工作原理。
69 0
|
14天前
|
缓存 算法 Java
【Java引用规范】强软引用
本文详细介绍了Java中引用的概念和作用,包括强引用、软引用、弱引用和虚引用,并探讨了不同引用类型在内存管理和垃圾回收中的特性与用途。强引用是最常见的引用类型,对象只要被引用就不会被垃圾回收;软引用适用于内存敏感的缓存场景,在内存不足时会被回收;弱引用在更早的垃圾回收阶段被清除;虚引用主要用于对象的finalize过程。文章通过示例代码和内存分析工具展示了软引用的具体应用和回收机制。
【Java引用规范】强软引用
|
13天前
|
Java API 开发者
Java 注释规范
Java中的注释规范包括单行注释(`//`)、多行注释(`/* ... */`)和文档注释(`/** ... */`)。单行注释适用于简短说明,多行注释用于较长描述,文档注释则专为自动生成API文档设计。注释应清晰明了、及时更新,避免冗余,并详细说明参数和返回值。遵循这些规范有助于提高代码的可读性和可维护性。
|
20天前
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
21天前
|
Java
编写规范JAVA代码
本文档制定了Java编程规范,旨在确保系统源程序的可读性和可维护性,适用于所有Java开发、测试及维护过程。规范包括命名规则(如Package、Class及其成员等)与样式规定,强调统一风格以提高协作效率,并列举了具体示例与注意事项,如避免单字符变量名及使用有意义的反义词组命名等。
38 1
|
21天前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。
|
8天前
|
Java 程序员
Java中的异常处理:从入门到精通
在Java编程的世界中,异常处理是保持程序稳定性和可靠性的关键。本文将通过一个独特的视角—把异常处理比作一场“捉迷藏”游戏—来探讨如何在Java中有效管理异常。我们将一起学习如何识别、捕捉以及处理可能出现的异常,确保你的程序即使在面对不可预见的错误时也能优雅地运行。准备好了吗?让我们开始这场寻找并解决Java异常的冒险吧!
|
20天前
|
Java 程序员 UED
Java 中的异常处理:从入门到精通
【8月更文挑战第31天】在Java编程的世界中,异常处理是保持应用稳定性的重要机制。本文将引导你理解异常的本质,学会如何使用try-catch语句来捕获和处理异常,并探索自定义异常类的魅力。我们将一起深入异常的世界,让你的代码更加健壮和用户友好。
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理:从入门到精通
【8月更文挑战第31天】 在编程世界中,错误和异常就像是不请自来的客人,总是在不经意间打扰我们的程序运行。Java语言通过其异常处理机制,为开发者提供了一套优雅的“待客之道”。本文将带你走进Java异常处理的世界,从基础语法到高级技巧,再到最佳实践,让你的程序在面对意外时,也能从容不迫,优雅应对。