
在后端开发的世界里,数据一致性是不可触碰的红线。想象这样一个场景:用户发起转账,账户 A 扣款成功,账户 B 却因网络波动未到账,最终钱 “不翼而飞”;或者订单创建成功,库存却未扣减,导致超卖事故。这些问题的根源往往在于事务管理的失控。在 Java 生态中,事务管理主要分为两大流派:传统事务(以声明式事务为代表)和编程式事务。前者以 “零侵入” 的优雅著称,后者以 “全掌控” 的灵活见长。本文将深入剖析这两种事务管理方式的核心原理、实战案例、优缺点对比及最佳实践,帮你在复杂业务场景中做出最优选择。
在探讨事务管理方式之前,我们首先需要明确 “事务” 的本质。事务(Transaction)是数据库操作的基本单元,是一组不可分割的操作集合,要么全部成功,要么全部失败。确保事务正确性的核心是 ACID 特性,这是所有事务管理的理论基础:
原子性要求事务中的所有操作 “要么全部完成,要么全部不完成”,不存在中间状态。就像转账操作,A 账户扣款和 B 账户到账必须同时成功或同时失败,不能出现 “扣了款没到账” 的情况。
事务执行前后,数据库的状态必须保持一致。例如转账前 A 和 B 的总余额为 1000 元,转账后总余额仍应为 1000 元,不能因事务执行导致数据逻辑错误。
多个事务并发执行时,彼此的操作应相互隔离,避免干扰。没有隔离性可能导致脏读(读取未提交数据)、不可重复读(同一事务中多次读取结果不一致)、幻读(读取范围数据时新增数据)等问题。
事务一旦提交,其修改必须永久保存到数据库中,即使发生系统崩溃也不能丢失。数据库通过日志(如 redo log)实现持久性保障。
ACID 特性是评判事务管理有效性的标尺。无论是传统事务还是编程式事务,其最终目标都是确保这些特性在业务场景中得到满足 —— 但它们的实现方式和适用场景却大相径庭。
传统事务在 Java 开发中最典型的实现是声明式事务,尤其是 Spring 框架提供的@Transactional注解。它通过 AOP(面向切面编程)实现事务管理,开发者只需通过注解声明事务规则,无需编写具体的事务控制代码,因此被称为 “传统事务”。
声明式事务的本质是 “通过注解配置事务属性,由框架自动完成事务的开启、提交、回滚”。其底层依赖 Spring 的 AOP 机制:
@Transactional的 Bean 创建代理对象,拦截目标方法的调用。RuntimeException),则回滚事务。这种 “配置即实现” 的方式极大简化了事务管理,让开发者聚焦业务逻辑而非事务控制细节。
下面通过一个经典的转账案例,展示声明式事务的实现过程。
首先引入 Spring 相关依赖(以 Maven 为例):
XML:
<!-- Spring核心依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring事务依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
创建账户表account存储账户信息:
CREATETABLE`account`(
`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'账户ID',
`user_name`varchar(50)NOTNULLCOMMENT'用户名',
`balance`decimal(10,2)NOTNULLDEFAULT0.00COMMENT'余额',
PRIMARYKEY(`id`),
UNIQUEKEY`uk_user_name`(`user_name`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='账户表';
-- 初始化数据
INSERTINTO`account`(`user_name`,`balance`)VALUES('张三',1000.00),('李四',1000.00);
定义 Account 实体和操作数据库的 Mapper 接口:
// 实体类
@Data
publicclassAccount{
privateLong id;
privateString userName;
privateBigDecimal balance;
}
// Mapper接口(使用Spring JdbcTemplate简化)
@Repository
publicclassAccountMapper{
privatefinalJdbcTemplate jdbcTemplate;
// 构造方法注入JdbcTemplate
publicAccountMapper(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
// 根据用户名查询账户
publicAccountgetByUserName(String userName){
String sql ="SELECT id, user_name, balance FROM account WHERE user_name = ?";
return jdbcTemplate.queryForObject(sql,
(rs, rowNum)->{
Account account =newAccount();
account.setId(rs.getLong("id"));
account.setUserName(rs.getString("user_name"));
account.setBalance(rs.getBigDecimal("balance"));
return account;
},
userName);
}
// 更新账户余额
publicintupdateBalance(String userName,BigDecimal amount){
String sql ="UPDATE account SET balance = balance + ? WHERE user_name = ?";
return jdbcTemplate.update(sql, amount, userName);
}
}
在 Service 层添加@Transactional注解,声明事务规则:
java
@Service
@Slf4j
publicclassTransferService{
privatefinalAccountMapper accountMapper;
publicTransferService(AccountMapper accountMapper){
this.accountMapper = accountMapper;
}
/**
* 转账业务:从fromUser扣减金额,向toUser增加金额
* 注解声明事务: propagation=REQUIRED(默认),rollbackFor=Exception.class(指定所有异常回滚)
*/
@Transactional(rollbackFor =Exception.class)
publicvoidtransfer(String fromUser,String toUser,BigDecimal amount){
log.info("开始转账:{}向{}转账{}元", fromUser, toUser, amount);
// 1. 校验余额是否充足
Account fromAccount = accountMapper.getByUserName(fromUser);
if(fromAccount.getBalance().compareTo(amount)<0){
thrownewInsufficientBalanceException("余额不足,转账失败");
}
// 2. 扣减转出账户金额(amount为负数)
accountMapper.updateBalance(fromUser, amount.negate());
log.info("{}扣减完成,当前余额:{}", fromUser, fromAccount.getBalance().subtract(amount));
// 模拟异常:测试事务回滚(实际开发中可删除)
// if (true) throw new RuntimeException("模拟转账异常");
// 3. 增加转入账户金额
accountMapper.updateBalance(toUser, amount);
log.info("{}到账完成,当前余额:{}", toUser, accountMapper.getByUserName(toUser).getBalance());
log.info("转账成功");
}
}
// 自定义异常:余额不足
classInsufficientBalanceExceptionextendsRuntimeException{
publicInsufficientBalanceException(String message){
super(message);
}
}
编写测试类验证事务效果:
java
@SpringBootTest
classTransferServiceTest{
@Autowired
privateTransferService transferService;
@Test
voidtestTransferSuccess(){
// 测试正常转账
transferService.transfer("张三","李四",newBigDecimal("200"));
// 断言:张三余额减少200,李四增加200
}
@Test
voidtestTransferRollback(){
// 测试异常场景下的回滚(需在transfer方法中手动触发异常)
assertThrows(RuntimeException.class,()->
transferService.transfer("张三","李四",newBigDecimal("300")));
// 断言:张三和李四的余额均未变化
}
}
@Transactional注解提供了丰富的参数配置事务行为,常用参数如下:
参数名 | 作用 | 常用值 |
|---|---|---|
propagation | 事务传播行为 | REQUIRED(默认,当前无事务则新建,有则加入)、REQUIRES_NEW(无论是否有事务,均新建事务)、SUPPORTS(有事务则加入,无则非事务执行)等 |
isolation | 事务隔离级别 | DEFAULT(默认,使用数据库隔离级别)、READ_COMMITTED(读已提交)、REPEATABLE_READ(可重复读)等 |
rollbackFor | 指定回滚异常类型 | Exception.class(所有异常均回滚)、RuntimeException.class(默认,仅运行时异常回滚) |
noRollbackFor | 指定不回滚异常类型 | 如BusinessException.class(业务异常不回滚) |
timeout | 事务超时时间(秒) | 默认为 - 1(无超时),如5表示 5 秒超时则回滚 |
readOnly | 是否为只读事务 | true(只读,优化查询性能)、false(默认,可读写) |
这些参数让声明式事务在 “零侵入” 的同时,具备一定的灵活性。
编程式事务是另一种事务管理方式,它通过显式编写代码控制事务的开启、提交、回滚,开发者完全掌控事务的生命周期。在 Spring 中,编程式事务主要通过TransactionTemplate或直接操作PlatformTransactionManager实现。
编程式事务的本质是 “开发者通过 API 直接操作事务状态”,核心流程如下:
getTransaction()方法开启事务,获取事务状态对象。commit()提交事务;若发生异常,调用rollback()回滚事务。这种方式虽然需要编写额外代码,但能实现声明式事务难以完成的复杂控制,如 “条件回滚”“多事务嵌套” 等场景。
下面通过一个 “创建订单 + 扣减库存 + 记录日志” 的复杂业务场景,展示编程式事务的实现。
需求:用户创建订单时,需完成三步操作:
order表)。inventory表)。operation_log表)。
要求:前两步必须在同一事务中(要么都成功,要么都失败);日志记录无论前两步成功与否都必须保存(独立事务)。创建订单表、库存表和日志表:
sql
-- 订单表
CREATETABLE`order`(
`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'订单ID',
`user_id`bigint(20)NOTNULLCOMMENT'用户ID',
`product_id`bigint(20)NOTNULLCOMMENT'商品ID',
`quantity`int(11)NOTNULLCOMMENT'购买数量',
`status`tinyint(1)NOTNULLDEFAULT0COMMENT'订单状态(0-创建中,1-成功,2-失败)',
`create_time`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,
PRIMARYKEY(`id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='订单表';
-- 库存表
CREATETABLE`inventory`(
`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'库存ID',
`product_id`bigint(20)NOTNULLCOMMENT'商品ID',
`stock`int(11)NOTNULLDEFAULT0COMMENT'库存数量',
PRIMARYKEY(`id`),
UNIQUEKEY`uk_product_id`(`product_id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='库存表';
-- 操作日志表
CREATETABLE`operation_log`(
`id`bigint(20)NOTNULLAUTO_INCREMENTCOMMENT'日志ID',
`user_id`bigint(20)NOTNULLCOMMENT'操作人ID',
`operation`varchar(255)NOTNULLCOMMENT'操作描述',
`status`tinyint(1)NOTNULLCOMMENT'操作状态(0-失败,1-成功)',
`create_time`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP,
PRIMARYKEY(`id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='操作日志表';
-- 初始化库存数据
INSERTINTO`inventory`(`product_id`,`stock`)VALUES(1,100);-- 商品1库存100
使用TransactionTemplate实现编程式事务,分离核心事务与日志事务:
java
// 1. 实体类定义(Order、Inventory、OperationLog,省略getter/setter)
@Data
classOrder{
privateLong id;
privateLong userId;
privateLong productId;
privateInteger quantity;
privateInteger status;
privateLocalDateTime createTime;
}
@Data
classInventory{
privateLong id;
privateLong productId;
privateInteger stock;
}
@Data
classOperationLog{
privateLong id;
privateLong userId;
privateString operation;
privateInteger status;
privateLocalDateTime createTime;
}
// 2. Mapper层(使用JdbcTemplate操作数据库)
@Repository
classOrderMapper{
privatefinalJdbcTemplate jdbcTemplate;
// 构造方法注入,省略getByUserId、insert等方法
}
@Repository
classInventoryMapper{
privatefinalJdbcTemplate jdbcTemplate;
// 构造方法注入,省略getByProductId、decreaseStock等方法
}
@Repository
classLogMapper{
privatefinalJdbcTemplate jdbcTemplate;
// 构造方法注入,实现insertLog方法
publicintinsertLog(OperationLog log){
String sql ="INSERT INTO operation_log (user_id, operation, status) VALUES (?, ?, ?)";
return jdbcTemplate.update(sql, log.getUserId(), log.getOperation(), log.getStatus());
}
}
// 3. 业务层:编程式事务核心实现
@Service
@Slf4j
classOrderService{
privatefinalOrderMapper orderMapper;
privatefinalInventoryMapper inventoryMapper;
privatefinalLogMapper logMapper;
privatefinalTransactionTemplate transactionTemplate;// 编程式事务模板
privatefinalTransactionTemplate logTransactionTemplate;// 日志独立事务模板
// 构造方法注入
publicOrderService(OrderMapper orderMapper,InventoryMapper inventoryMapper,
LogMapper logMapper,PlatformTransactionManager transactionManager){
this.orderMapper = orderMapper;
this.inventoryMapper = inventoryMapper;
this.logMapper = logMapper;
// 初始化核心事务模板: propagation=REQUIRED,隔离级别=默认,超时30秒
this.transactionTemplate =newTransactionTemplate(transactionManager);
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
this.transactionTemplate.setTimeout(30);
// 初始化日志事务模板: propagation=REQUIRES_NEW(独立事务)
this.logTransactionTemplate =newTransactionTemplate(transactionManager);
this.logTransactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
/**
* 创建订单:核心事务(订单+库存)+ 独立日志事务
*/
public void createOrder(Long userId,Long productId,Integer quantity){
// 日志描述
String operationDesc =String.format("用户%d购买商品%d,数量%d", userId, productId, quantity);
boolean isSuccess =false;
try{
// 执行核心事务:订单创建+库存扣减
transactionTemplate.execute(status ->{
try{
// 1. 检查库存
Inventory inventory = inventoryMapper.getByProductId(productId);
if(inventory.getStock()< quantity){
thrownewInsufficientStockException("库存不足,创建订单失败");
}
// 2. 创建订单(状态为“创建中”)
Order order =newOrder();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setStatus(0);// 0-创建中
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 3. 扣减库存
int rows = inventoryMapper.decreaseStock(productId, quantity);
if(rows <=0){
thrownewRuntimeException("库存扣减失败,可能已被其他订单占用");
}
log.info("库存扣减成功,商品{}当前库存:{}", productId, inventory.getStock()- quantity);
// 4. 更新订单状态为“成功”
order.setStatus(1);// 1-成功
orderMapper.updateStatus(order.getId(),1);
return order.getId();// 事务执行结果
}catch(Exception e){
// 手动标记回滚(可选,抛出异常也会自动回滚)
status.setRollbackOnly();
log.error("核心事务执行失败", e);
throw e;// 继续抛出异常,让外层捕获
}
});
isSuccess =true;
log.info("订单创建成功");
}catch(Exception e){
log.error("订单创建失败", e);
// 若订单已创建但事务回滚,需手动更新订单状态为“失败”
Order lastOrder = orderMapper.getLastUnfinishedOrder(userId, productId);
if(lastOrder !=null){
orderMapper.updateStatus(lastOrder.getId(),2);// 2-失败
}
}finally{
// 记录日志(独立事务,无论核心事务成功与否都执行)
logTransactionTemplate.execute(status ->{
OperationLog log =newOperationLog();
log.setUserId(userId);
log.setOperation(operationDesc);
log.setStatus(isSuccess ?1:0);// 1-成功,0-失败
logMapper.insertLog(log);
log.info("操作日志记录完成,日志ID:{}", log.getId());
returnnull;
});
}
}
}
// 自定义异常:库存不足
classInsufficientStockExceptionextendsRuntimeException{
publicInsufficientStockException(String message){
super(message);
}
}
Spring 编程式事务的核心 API 包括:
PlatformTransactionManager事务管理器,是所有事务操作的入口,如DataSourceTransactionManager(JDBC 事务)、JpaTransactionManager(JPA 事务)等。TransactionDefinition事务定义,封装事务属性(传播行为、隔离级别、超时时间等)。TransactionStatus事务状态,代表当前事务的运行状态,可通过它标记回滚(setRollbackOnly())。TransactionTemplate事务模板类,简化编程式事务代码,通过execute()方法执行事务逻辑。TransactionTemplate的execute()方法接收TransactionCallback接口,该接口的doInTransaction()方法封装事务逻辑,返回值为事务执行结果。
传统事务(声明式)和编程式事务各有优劣,选择时需结合业务场景。以下从多个维度进行对比:
维度 | 传统事务(声明式) | 编程式事务 |
|---|---|---|
开发效率 | 高:只需添加注解,无需编写事务控制代码,框架自动处理。 | 低:需手动编写事务开启、提交、回滚代码,模板代码较多。 |
代码侵入性 | 低:通过注解声明,业务代码与事务代码分离,符合 AOP 思想。 | 高:事务控制代码嵌入业务逻辑,代码耦合度高。 |
学习成本 | 低:只需掌握@Transactional注解参数即可。 | 高:需理解事务管理器、传播行为、模板 API 等概念。 |
结论:简单业务场景下,声明式事务开发效率更高,代码更简洁。
维度 | 传统事务(声明式) | 编程式事务 |
|---|---|---|
事务控制粒度 | 粗:以方法为单位,无法在方法内部动态调整事务行为。 | 细:可在方法内部任意位置开启、提交、回滚事务,支持条件控制。 |
异常处理 | 固定:默认根据异常类型回滚,无法根据业务结果动态决定是否回滚。 | 灵活:可根据业务逻辑(如返回码、状态)手动调用setRollbackOnly()回滚。 |
多事务嵌套 | 有限:依赖传播行为配置,复杂嵌套场景易出错。 | 强:可手动控制多个事务的开启和提交顺序,支持精细化嵌套。 |
事务属性动态调整 | 弱:注解参数需在编译期确定,无法运行时动态修改。 | 强:可在运行时根据条件动态设置事务属性(如超时时间、隔离级别)。 |
案例:若业务要求 “当订单金额> 1000 时使用REPEATABLE_READ隔离级别,否则使用READ_COMMITTED”,声明式事务无法实现(注解参数固定),而编程式事务可动态设置。
场景 | 推荐方式 | 原因 |
|---|---|---|
简单 CRUD 操作(如单表增删改) | 传统事务 | 注解方式简洁,无需额外代码。 |
复杂业务流程(多表操作、条件回滚) | 编程式事务 | 需精细化控制事务生命周期,支持动态调整。 |
事务属性固定的场景 | 传统事务 | 注解参数一次配置,无需重复修改。 |
事务属性动态变化的场景 | 编程式事务 | 可根据运行时条件修改事务属性。 |
团队协作开发 | 传统事务 | 规范统一,减少因手动控制导致的错误。 |
框架底层或中间件开发 | 编程式事务 | 需要更高的灵活性和控制力。 |
事务管理的性能主要取决于事务边界的控制和资源的释放效率:
结论:性能差异并非选择的核心因素,业务需求和开发效率更重要。
在复杂业务中,单一事务管理方式往往无法满足需求,需结合传统事务和编程式事务的优势。以下是几个典型混合场景的实现方案。
需求:在声明式事务方法中,根据业务结果(非异常)决定是否回滚。例如:“转账后若检测到接收方账户异常,即使无异常也回滚事务”。
java
@Service
publicclassMixedTransferService{
privatefinalAccountMapper accountMapper;
privatefinalTransactionStatusAccessor transactionStatusAccessor;// 事务状态访问器
public MixedTransferService(AccountMapper accountMapper,
PlatformTransactionManager transactionManager){
this.accountMapper = accountMapper;
// 初始化事务状态访问器(用于获取当前事务状态)
this.transactionStatusAccessor =newTransactionStatusAccessor(transactionManager);
}
@Transactional(rollbackFor =Exception.class)
publicvoidtransferWithCheck(String fromUser,String toUser,BigDecimal amount){
// 1. 扣减转出账户金额
accountMapper.updateBalance(fromUser, amount.negate());
// 2. 增加转入账户金额
accountMapper.updateBalance(toUser, amount);
// 3. 检查接收方账户状态(假设存在checkAccountStatus方法)
boolean isReceiverValid =checkAccountStatus(toUser);
if(!isReceiverValid){
// 编程式回滚:即使无异常,也手动标记回滚
TransactionStatus status = transactionStatusAccessor.getCurrentTransactionStatus();
status.setRollbackOnly();
log.warn("接收方账户异常,事务回滚");
}
}
// 检查账户状态的业务方法
privatebooleancheckAccountStatus(String userName){
// 实际业务中可能调用外部接口或查询黑名单
return!"异常账户".equals(userName);// 模拟:若用户名为“异常账户”则返回false
}
}
// 事务状态访问器:获取当前事务状态
classTransactionStatusAccessor{
privatefinalPlatformTransactionManager transactionManager;
publicTransactionStatusAccessor(PlatformTransactionManager transactionManager){
this.transactionManager = transactionManager;
}
publicTransactionStatusgetCurrentTransactionStatus(){
return transactionManager.getTransaction(newDefaultTransactionDefinition());
}
}
需求:在编程式事务的大事务中,嵌套一个独立的声明式小事务(如 “订单创建过程中,记录中间状态日志,日志事务独立提交”)。
java
@Service
publicclassNestedTransactionService{
privatefinalOrderMapper orderMapper;
privatefinalLogService logService;// 声明式事务的日志服务
privatefinalTransactionTemplate transactionTemplate;
publicNestedTransactionService(OrderMapper orderMapper,LogService logService,
PlatformTransactionManager transactionManager){
this.orderMapper = orderMapper;
this.logService = logService;
this.transactionTemplate =newTransactionTemplate(transactionManager);
}
// 编程式大事务
publicvoidcreateOrderWithLog(Long userId,Long productId){
transactionTemplate.execute(status ->{
// 1. 创建订单(大事务中)
Order order =newOrder();
order.setUserId(userId);
order.setProductId(productId);
orderMapper.insert(order);
// 2. 调用声明式事务的日志服务(传播行为=REQUIRES_NEW,独立事务)
logService.recordIntermediateLog(userId,"订单创建中", order.getId());
// 3. 继续执行其他业务逻辑(如扣减库存)
// ...
return order.getId();
});
}
}
// 声明式事务的日志服务
@Service
classLogService{
privatefinalLogMapper logMapper;
publicLogService(LogMapper logMapper){
this.logMapper = logMapper;
}
// 声明式事务:传播行为=REQUIRES_NEW,独立于外部事务
@Transactional(propagation =Propagation.REQUIRES_NEW, rollbackFor =Exception.class)
publicvoidrecordIntermediateLog(Long userId,String content,Long orderId){
OperationLog log =newOperationLog();
log.setUserId(userId);
log.setOperation(content +",订单ID:"+ orderId);
log.setStatus(1);
logMapper.insertLog(log);
}
}
需求:根据业务参数动态选择事务管理方式 —— 简单订单用声明式事务,复杂订单用编程式事务。
java
@Service
publicclassDynamicTransactionService{
privatefinalSimpleOrderService simpleOrderService;// 声明式事务服务
privatefinalComplexOrderService complexOrderService;// 编程式事务服务
publicDynamicTransactionService(SimpleOrderService simpleOrderService,
ComplexOrderService complexOrderService){
this.simpleOrderService = simpleOrderService;
this.complexOrderService = complexOrderService;
}
/**
* 动态选择事务方式:金额<=1000用声明式,>1000用编程式
*/
publicvoidcreateOrderDynamically(Long userId,Long productId,Integer quantity,BigDecimal amount){
if(amount.compareTo(newBigDecimal("1000"))<=0){
// 简单订单:用声明式事务
simpleOrderService.createSimpleOrder(userId, productId, quantity);
}else{
// 复杂订单:用编程式事务(支持更细粒度控制)
complexOrderService.createComplexOrder(userId, productId, quantity);
}
}
}
// 声明式事务的简单订单服务
@Service
classSimpleOrderService{
@Transactional(rollbackFor =Exception.class)
publicvoidcreateSimpleOrder(Long userId,Long productId,Integer quantity){
// 简单订单逻辑:创建订单+扣减库存
// ...
}
}
// 编程式事务的复杂订单服务
@Service
classComplexOrderService{
privatefinalTransactionTemplate transactionTemplate;
publicComplexOrderService(PlatformTransactionManager transactionManager){
this.transactionTemplate =newTransactionTemplate(transactionManager);
}
publicvoidcreateComplexOrder(Long userId,Long productId,Integer quantity){
transactionTemplate.execute(status ->{
// 复杂订单逻辑:创建订单+扣减库存+风控检查+通知商家
// ...
returnnull;
});
}
}
无论使用传统事务还是编程式事务,实际开发中都可能遇到各种 “坑”。以下是高频问题及解决方案。
现象:添加了@Transactional注解,但事务未生效(异常发生后数据未回滚)。
常见原因及解决方案:
@Transactional仅对 public 方法生效。
✅ 解决方案:将方法改为 public。@Transactional
publicvoidtransfer(){
try{
// 业务逻辑
}catch(Exception e){
log.error("错误", e);// 仅日志记录,未抛出异常
}
}
// 正确示例
@Transactional
publicvoidtransfer(){
try{
// 业务逻辑
}catch(Exception e){
log.error("错误", e);
thrownewRuntimeException("转账失败", e);// 重新抛出异常
}
}
@Service
publicclassOrderService{
publicvoidcreateOrder(){
// 自调用:事务不生效
doCreateOrder();
}
@Transactional
publicvoiddoCreateOrder(){
// 业务逻辑
}
}
// 正确示例:通过注入自身Bean调用
@Service
publicclassOrderService{
@Autowired
privateOrderService orderService;// 注入自身
publicvoidcreateOrder(){
orderService.doCreateOrder();// 通过代理对象调用
}
@Transactional
publicvoiddoCreateOrder(){
// 业务逻辑
}
}
RuntimeException及其子类回滚, checked 异常不回滚。
✅ 解决方案:指定rollbackFor = Exception.class。@EnableTransactionManagement,且存在PlatformTransactionManagerBean。现象:编程式事务中,因代码不规范导致数据库连接未释放,最终连接池耗尽。
原因及解决方案:
getTransaction()后发生异常,未调用commit()或rollback()。
✅ 解决方案:使用 try-finally 块确保事务关闭:
TransactionStatus status = transactionManager.getTransaction(definition);
try{
// 业务逻辑
transactionManager.commit(status);
}catch(Exception e){
transactionManager.rollback(status);
throw e;
}
execute()方法执行事务,直接调用业务逻辑。
✅ 解决方案:所有事务逻辑必须放在TransactionCallback的doInTransaction()方法中。现象:嵌套事务中,子事务回滚导致父事务也回滚,或父事务回滚不影响子事务。
常见错误场景:
REQUIRED,子事务用REQUIRED:子事务回滚会导致父事务也回滚。
✅ 解决方案:若子事务失败不应影响父事务,子事务改用REQUIRES_NEW。NESTED当作REQUIRES_NEW:NESTED是嵌套事务(依赖数据库 savepoint),REQUIRES_NEW是独立事务。
✅ 解决方案:明确业务需求,若需完全独立的事务,使用REQUIRES_NEW。现象:并发场景下出现脏读、不可重复读等问题,导致数据不一致。
解决方案:
READ_COMMITTED):避免脏读,性能较好,适合大多数场景。REPEATABLE_READ):避免脏读和不可重复读,MySQL 默认级别,适合对数据一致性要求高的场景。SERIALIZABLE):完全隔离,性能差,仅用于极端场景。SELECT ... FOR UPDATE)防止并发问题:
transactionTemplate.execute(status ->{
// 悲观锁:锁定记录防止其他事务修改
Account account = jdbcTemplate.queryForObject(
"SELECT * FROM account WHERE user_name = ? FOR UPDATE",
params, rowMapper);
// 业务逻辑
returnnull;
});
事务管理方式的选择没有绝对标准,但遵循以下原则可帮助你做出更合理的决策:
@Transactional可减少因个人编码风格差异导致的问题。TransactionTemplate而非直接操作TransactionManager,减少模板代码。从传统事务到编程式事务,本质是在 “开发效率” 和 “控制灵活性” 之间寻找平衡。声明式事务以注解为刃,削去了冗余的事务代码,让开发者聚焦业务逻辑;编程式事务以 API 为尺,精准丈量事务的每一个细节,应对复杂业务挑战。
没有放之四海而皆准的事务管理方式:简单业务用声明式事务,享受 “零侵入” 的优雅;复杂场景用编程式事务,掌控 “全链路” 的细节。在实际开发中,我们更应根据业务需求灵活选择,甚至混合使用两种方式 —— 毕竟,能保障数据一致性的事务管理方式,才是最好的方式。
最后,记住事务管理的核心目标:让数据在任何情况下都保持一致,让用户的每一分钱、每一条订单都有迹可循。无论是传统事务还是编程式事务,都只是实现这一目标的工具。理解工具的本质,才能在复杂的业务场景中做出最合理的选择。