让我们从最简单的事务模式开始,增加复杂性。
无交易
“无事务”连接是一种不“提交”或“回滚”数据(例如发送电子邮件)的连接。一旦你将消息对象传递给电子邮件服务器,它就会被发送给收件人,再多的恳求也不会再次得到消息。就好像每次调用都在调用返回时提交。这种连接的示例包括与 SMTP、SMS 网关、打印机等的连接。
我相信如果您启用了自动提交,您可以以这种方式使用数据库连接,但它引出了一个问题,即为什么您首先拥有一个完整的 ACID 数据库......
“正常”交易
正常的连接,例如与 SQL 数据库的连接,能够在内部缓冲区中存储一系列状态更改命令。当一切都完成并且一切正常时,整个更改缓冲区将被写入数据存储,其他连接可以看到更改。如果在提交之前或什至在提交期间出现问题,则可以丢弃(回滚)整个更改集。
这种类型的连接的一个关键限制是缓冲区的范围 - 缓冲区是连接本身的一部分。换句话说,只有通过连接才能写入缓冲区。
应用服务器的一个重要职责是管理这些连接。当您要求连接池为您提供连接时,您每次都会神奇地获得相同的连接(在单个事务中)。即使一个 EJB 调用另一个 EJB 或一个 EJB 调用资源适配器时也是如此(假设您使用 REQUIRES_TRANSACTION 语义。您可以使用 REQUIRES_NEW 覆盖它)。这种行为意味着一个 Web 请求可以进行多个 EJB 调用,每个调用都可以与多个实体 bean 交互,并且所有数据操作都发生在具有单个内部缓冲区的单个连接上。它将全部一起提交或回滚。
具有多个连接的事务
当你有一个单一的数据库时这很好 - 但是(根据定义)如果你与单独的数据库实例(例如在不同的机器上)通信,你需要单独的连接。那么会发生什么呢?您的 EJB 事务最终会与多个连接相关联——每个连接到一个唯一的数据库。这似乎运作良好,除了在一种情况下:
- 您有到数据库 A 的连接 A 和到数据库 B 的连接 B
- 您在 A 和 B 上执行 DML 语句
- 您提交 EJB 连接。应用服务器现在:
- 提交连接 A - 成功
- 提交连接 B - 失败(例如约束失败)并且连接 B 回滚
这是一场灾难——您已经在数据库 A 中提交了事务,现在无法回滚。但是,事务(以及整个 EJB)在数据库 B 上回滚。
(有趣的是,您的示例几乎与此相同-您将数据提交给无事务和正常事务,但不在 XA 事务中-三个连接中的最后一个)
XA 交易
这就是 XA 的用武之地。它提供逻辑来协调针对不同数据源提交的事务,并模拟多个数据源上的单个事务。XA 提交由事务协调器管理的“两阶段提交”,该事务协调器管理多个 XA 连接,这些连接被选入 XA 事务。协调员
- 通过 XA 连接向每个数据源发送消息以查看是否可以提交事务:所有约束和数据库逻辑都在最终提交之前执行。如果任何数据库报告失败,XA 协调器将回滚整个事务。阶段 1 是执行几乎所有事务工作的地方,因此需要相对较长的时间
- 当每个数据库都报告可以提交事务时,协调器向每个数据库发送消息以提交事务。这发生得非常快。
请注意,如果在阶段 2 中出现问题(例如,部分网络崩溃或其中一个数据库在阶段 1 和阶段 2 之间关闭) ,则两阶段提交可能会失败。
因为 XA 连接的行为与普通连接如此不同,所以它通常需要一个不同的 ConnectionFactory 对象来实例化与非 XA ConnectionFactory 不同的对象实例。此外,XA ConnectionFactory 需要XA 事务协调器的配置参数,例如XA 事务超时,这些是普通事务属性之外的。
另一个约束:只有由 XA ConnectionFactory 创建的连接才能加入 XA 事务和相关的两阶段提交。您可以让 XA 和非 XA 连接参与单个 Application Server 事务,但是整个事务不能作为单个事务可靠地提交/回滚(如上所述)。
具体答案
我想知道,当事务在某一时刻失败时,应该回滚所有表中的所有数据,还是应该只回滚 XA 服务的数据?
如果事务在应用程序服务器尝试提交之前失败(例如,您的 EJB 获得 NPE 或您故意回滚),每个连接都会收到回滚,一切都应该如您所愿。
但是,如果事务在提交逻辑中失败(例如数据库约束),那么事务管理器将尝试回滚所有内容;如果非 XA 连接已提交,则不会发生这种情况。
我还想知道:据我所知,“事务”是一个原子传输数据的过程。那么为什么连接创建包括定义可以由连接执行的事务类型不是事务的属性吗?
XA 连接使用与普通连接不同的库和协议,因为连接本身需要与 XA 事务协调器通信。普通连接不会这样做。
我还想知道为什么我们必须在连接属性中定义事务类型,而不是我们必须在启动事务时定义事务类型,并且事务管理器必须执行给定类型的事务。
因为 XA 连接使用了不同的代码,所以与普通连接相比,连接池需要加载不同的类。这就是连接池(不是连接)属性不同的原因。