戴老师的博客 - 事务
type: channel
link: https://www.tushu.info/tag/%E4%BA%8B%E5%8A%A1/
items: JTA 深度历险 - 原理与实现
items: 全面分析 Spring 的编程式事务管理及声明式事务管理
items: Spring 事务管理高级应用难点剖析

JTA 深度历险 - 原理与实现
type: item
date: 2026-07-09T04:38:15+00:00
description: 什么是事务处理事务是计算机应用中不可或缺的组件模型，它保证了用户操作的原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）和持久性（Durability）。关于事务最经典的示例莫过于信用卡转账：将用户 A 账户中的 500 元人民币转移到用户 B 的账户中，其操作流程如下：将 A 账户中的金额减少 500；将 B 账户中的金额增加 500。这两个操作必须保证 ACID 的事务属性：即要么全部成功，要么全部失败。假若没有事务保障，用户的账号金额将可能发生问题：假如第一步操作成功而第二步失败，那么用户 A 账户中的金额就将减少 500 元，而用户 B 的账号却没有任何增加（不翼而飞）；同样如果第一步出错而第二步成功，那么用户 A 的账户金额不变，而用户 B 的账号将增加 500 元（凭空而生）。上述任何一种错误都会产生严重的数据不一致问题，事务的缺失对于一个稳定的生产系统是不可接受的。J2EE 事务处理方式1. 本地事务本地事务紧密依赖于底层资源管理器（例如数据库连接），事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖，因而部署灵活，但无法支持多数据源的分布式事务。在数据库连接中使用本地事务示例如下：清单 1. 本地事务处理实例public void transferAccount() { Connection conn = null; Statement stmt = null; try { conn = getDataSource().getConnection(); // 将自动提交设置为 false， // 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交 conn.setAutoCommit(false.html); stmt = conn.createStatement(); // 将 A 账户中的金额减少 500 stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'".html); // 将 B 账户中的金额增加 500 stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'".html); // 提交事务 conn.commit(); // 事务提交：转账的两步操作同时成功 } catch (SQLException sqle.html) { try { // 发生异常，回滚在本事务中的操作 conn.rollback(); // 事务回滚：转账的两步操作完全撤销 stmt.close(); conn.close(); } catch (Exception ignore.html) { // 忽略关闭异常 } sqle.printStackTrace(); } }2. 分布式事务处理Java 事务编程接口（JTA：Java Transaction API）和 Java 事务服务（JTS：Java Transaction Service）为 J2EE 平台提供了分布式事务服务。分布式事务（Distributed Transaction）包括事务管理器（Transaction Manager）和一个或多个支持 XA 协议的资源管理器（Resource Manager）。我们可以将资源管理器看做任意类型的持久化数据存储；事务管理器承担着所有事务参与单元的协调与控制。JTA 事务有效地屏蔽了底层事务资源，使应用可以以透明的方式参与到事务处理中。但是与本地事务相比，XA 协议的系统开销大，在系统开发过程中应慎重考虑是否确实需要分布式事务。若确实需要分布式事务以协调多个事务资源，则应实现和配置所支持 XA 协议的事务资源，如 JMS、JDBC 数据库连接池等。使用 JTA 处理事务的示例如下（注意：connA 和 connB 是来自不同数据库的连接）：清单 2. JTA 事务处理public void transferAccount() { UserTransaction userTx = null; Connection connA = null; Statement stmtA = null; Connection connB = null; Statement stmtB = null; try { // 获得 Transaction 管理对象 userTx = (UserTransaction.html) getContext().lookup("java:comp/UserTransaction".html); // 从数据库…

全面分析 Spring 的编程式事务管理及声明式事务管理
type: item
date: 2026-07-09T04:31:33+00:00
description: 开始之前关于本教程本教程将深入讲解 Spring 简单而强大的事务管理功能，包括编程式事务和声明式事务。通过对本教程的学习，您将能够理解 Spring 事务管理的本质，并灵活运用之。先决条件本教程假定您已经掌握了 Java 基础知识，并对 Spring 有一定了解。您还需要具备基本的事务管理知识，比如事务的定义、隔离级别的概念等。本文将直接使用这些概念而不做详细解释。另外，您最好掌握数据库的基础知识，虽然这不是必须的。系统需求要试验这份教程中的工具和示例，硬件配置需求为：至少带有 512MB 内存（推荐 1GB）的系统。需要安装以下软件：Sun JDK 5.0 或更新版本，或 IBM Developer Kit for the Java 5 platform 版本。Spring Framework 2.5。本教程附带的示例代码已经在 Spring 2.5.6 上测试过。MySQL 5.0 或更新版本。Spring 事务属性分析事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的，即便出现了异常的访问情况，也不至于破坏后台数据的完整性。就像银行的自助取款机，通常都能正常为客户服务，但是也难免遇到操作过程中机器突然出故障的情况。此时，事务就必须确保出故障前对账户的操作不生效，就像用户刚才完全没有使用过取款机一样，以保证用户和银行的利益都不受损失。在 Spring 中，事务是通过 TransactionDefinition 接口来定义的。该接口包含与事务属性有关的方法，具体如清单 1 所示：清单 1. TransactionDefinition 接口中定义的主要方法public interface TransactionDefinition { int getIsolationLevel(); int getPropagationBehavior(); int getTimeout(); boolean isReadOnly(); }也许你会奇怪，为什么接口只提供了获取属性的方法，而没有提供相关设置属性的方法。其实道理很简单，事务属性的设置完全是程序员控制的，因此程序员可以自定义任何设置属性的方法，而且保存属性的字段也没有任何要求。唯一的要求是：Spring 进行事务操作的时候，通过调用以上接口提供的方法必须能够返回事务相关的属性取值。事务隔离级别隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量。在典型的应用程序中，多个事务并发运行，经常会操作相同的数据来完成各自的任务。并发虽然是必须的，但可能会导致以下问题：脏读（Dirty Read）： 当一个事务正在访问数据并且对数据进行了修改，而这种修改还没有提交到数据库中，这时另外一个事务也访问了这个数据，然后使用了这个数据。因为这个数据是还没有提交的数据，那么另外一个事务读到的这个数据是“脏数据”，依据“脏数据”所做的操作可能是不正确的。丢失修改（Lost Update）： 指在一个事务读取一个数据时，另外一个事务也访问了该数据，那么在第一个事务中修改了这个数据后，第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失，因此称为丢失修改。例如： 事务 1 读取某表中的数据 A=20，事务 2 也读取 A=20，事务 1 修改 A=A-1，事务 2 也修改 A=A-1，最终结果 A=19，事务 1 的修改被丢失。不可重复读（Unrepeatable Read）： 指在一个事务内多次读同一数据。在这个事务还没有结束时，另一个事务也访问该数据。那么，在第一个事务中的两次读数据之间，由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况。幻读（Phantom Read）： 幻读与不可重复读类似。它发生在一个事务（T1）读取了几行数据，接着另一个并发事务（T2）插入了一些数据时。在随后的查询中，第一个事务（T1）就会发现多了一些原本不存在的记录，就好像发生了幻觉一样。不可重复读和幻读的区别：不可重复读的重点是修改，幻读的重点在于新增或者删除。例 1（不可重复读）： 事务 1 中的 A 先生读取自己的工资为 1000 的操作还没完成，事务 2 中的 B 先生就修改了 A 的工资为 2000，导致 A 再读自己的工资时工资变为 2000。例 2（幻读）： 假某工资单表中工资大于 3000 的有 4 人，事务 1 读取了所有工资大于 3000 的人，共查到 4 条记录，这时事务 2 又插入了一条工资大于 3000 的记录，事务 1 再次读取时查到的记录就变为了 5 条。TransactionDefinition 接口中定义的五个隔离级别常量如下：Tr…

Spring 事务管理高级应用难点剖析
type: item
date: 2026-07-09T04:27:50+00:00
description: 概述Spring 最成功、最吸引人的特性莫过于轻量级的声明式事务管理。仅此一点，它就宣告了重量级 EJB 容器的覆灭。Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来，使其能专注于业务逻辑的开发，这是一件值得推崇的事情。但是，世界并未从此消停。开发人员需要面对的是层出不穷的应用场景，这些场景往往逾越了普通 Spring 技术书籍的理想界定。因此，随着应用开发的深入，在使用经过 Spring 层层封装的声明式事务时，开发人员越来越觉得自己坠入了迷雾，陷入了沼泽，体会不到外界所宣称的那种畅快淋漓。本系列文章的目标旨在整理并剖析实际应用中种种让我们迷茫的场景，让阳光照进云遮雾障的山头。DAO 和事务管理的牵绊很少有使用 Spring 但不使用 Spring 事务管理器的应用，因此常常有人会问：是否用了 Spring，就一定要用 Spring 事务管理器？否则就无法进行数据的持久化操作吗？事务管理器和 DAO 是什么关系呢？也许是 DAO 和事务管理如影随行的缘故，这个看似简单的问题实实在在地存在着，从初学者心中涌出，萦绕在开发老手的脑际。答案当然是否定的！我们都知道：事务管理是保证数据操作的事务性（即原子性、一致性、隔离性、持久性，也即所谓的 ACID）。脱离了事务性，DAO 照样可以顺利地进行数据操作。下面，我们来看一段使用 Spring JDBC 进行数据访问的代码：清单 1. UserJdbcWithoutTransManagerService.javapackage user.withouttm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.apache.commons.dbcp.BasicDataSource; @Service("service1".html) public class UserJdbcWithoutTransManagerService { @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd.html) { String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql, toAdd, userName.html); } public static void main(String[] args.html) { ApplicationContext ctx = new ClassPathXmlApplicationContext("user/withouttm/jdbcWithoutTransManager.xml".html); UserJdbcWithoutTransManagerService service = (UserJdbcWithoutTransManagerService.html) ctx.getBean("service1".html); JdbcTemplate jdbcTemplate = (JdbcTemplate.html) ctx.getBean("jdbcTemplate".html); BasicDataSource basicDataSource = (BasicDataSource.html) jdbcTemplate.getDataSource(); // ①. 检查数据源 autoCommit 的设置 System.out.println("autoCommit:" + basicDataSource.getDefaultAutoCommit(.html)); // ②. 插入一条记录，初始分数为 10 jdbcTemplate.execute( "INSERT INTO t_user(user_name,password,score.html) VALUES('tom','123456',10.html)"); // ③.…