

Spring事务管理详解:从理论到实践的完美指南
深入理解事务本质、传播机制与最佳实践
在分布式系统大行其道的今天,数据一致性成为了系统设计的重中之重。Spring框架提供了强大而灵活的事务管理机制,让开发者能够从繁琐的事务管理中解放出来,专注于业务逻辑的实现。本文将深入剖析Spring事务管理的核心原理、实现方式与实战技巧。
一、事务的本质:数据一致性的守护者
想象一下,当用户在电商平台下单时,扣减库存、创建订单、扣减余额这一系列操作,如果中间某一步失败了,后果不堪设想。事务,正是解决这类问题的关键技术。
事务具有四大核心特性,通常被称为 ACID 特性:
在Java EE时代,开发者需要手动管理事务的开启、提交和回滚,代码冗余且容易出错。Spring框架的出现彻底改变了这一现状。
二、Spring 事务管理的核心架构
Spring 事务管理的核心在于其抽象层设计,通过一套统一的接口屏蔽了不同持久化技术的事务管理差异。理解这些核心接口,是掌握 Spring 事务实现原理的关键。
PlatformTransactionManager是 Spring 事务管理的核心接口,它定义了事务管理的基本操作:
public interface PlatformTransactionManager {
// 获取事务状态
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}Spring 为不同的持久化技术提供了具体实现:
DataSourceTransactionManager用于 JDBC 和 MyBatis 等基于 DataSource 的持久化技术HibernateTransactionManager用于 Hibernate 框架JpaTransactionManager用于 JPA 技术JtaTransactionManager用于分布式事务管理以DataSourceTransactionManager为例,它通过管理Connection对象来实现事务控制,本质上是对 JDBC 事务的封装。
TransactionDefinition接口定义了事务的基本属性,包括隔离级别、传播行为、超时时间和是否只读等:
public interface TransactionDefinition {
// 事务传播行为
int getPropagationBehavior();
// 事务隔离级别
int getIsolationLevel();
// 事务超时时间(秒)
int getTimeout();
// 是否为只读事务
boolean isReadOnly();
// 事务名称
@Nullable
String getName();
}
Spring 提供了DefaultTransactionDefinition作为默认实现,我们也可以根据需要自定义实现。
TransactionStatus接口用于表示事务的当前运行状态,提供了事务控制的基本方法:
public interface TransactionStatus extends SavepointManager, Flushable {
// 是否是新事务
boolean isNewTransaction();
// 是否有保存点
boolean hasSavepoint();
// 设置事务为仅回滚状态
void setRollbackOnly();
// 事务是否为仅回滚状态
boolean isRollbackOnly();
// 刷新事务
@Override
void flush();
// 事务是否已完成
boolean isCompleted();
}
通过TransactionStatus,我们可以获取事务的当前状态,并对事务进行一些控制操作,如设置回滚点、标记事务为仅回滚等。
这三个核心接口构成了 Spring 事务管理的基础架构,它们之间的协作关系如下:
TransactionDefinition定义事务属性PlatformTransactionManager根据TransactionDefinition获取TransactionStatusTransactionStatus对事务进行提交、回滚等操作
三、Spring 事务传播机制:解决事务嵌套难题
事务传播机制是 Spring 事务管理中最具特色的功能之一,它解决了多个事务方法嵌套调用时的事务如何传播的问题。Spring 定义了 7 种事务传播行为,每种行为都有其特定的适用场景。
为了更好地理解这些传播行为,我们通过一个电商下单的场景来进行实例分析。假设有三个核心方法:
createOrder()创建订单deductInventory()扣减库存deductBalance()扣减余额我们将通过不同的传播行为组合,观察事务的执行效果。
首先,我们定义一个订单实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer quantity;
private BigDecimal amount;
private String status;
}
然后,创建相关的 Mapper 接口:
@Mapper
public interface OrderMapper {
@Insert("INSERT INTO `order` (user_id, product_id, quantity, amount, status) " +
"VALUES (#{userId}, #{productId}, #{quantity}, #{amount}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(Order order);
}
@Mapper
public interface InventoryMapper {
@Update("UPDATE inventory SET stock = stock - #{quantity} WHERE product_id = #{productId} AND stock >= #{quantity}")
int deduct(@Param("productId") Long productId, @Param("quantity") Integer quantity);
}
@Mapper
public interface AccountMapper {
@Update("UPDATE account SET balance = balance - #{amount} WHERE user_id = #{userId} AND balance >= #{amount}")
int deduct(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
}
接下来,我们实现 Service 层代码,并通过不同的传播行为组合来测试事务效果:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Autowired
private AccountService accountService;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
/**
* 创建订单,使用默认的REQUIRED传播行为
*/
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 查询商品单价(实际项目中应该有专门的商品服务)
BigDecimal price = new BigDecimal("99.99");
BigDecimal amount = price.multiply(new BigDecimal(quantity));
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setAmount(amount);
order.setStatus("PENDING");
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存
inventoryService.deductInventory(productId, quantity);
// 扣减余额
accountService.deductBalance(userId, amount);
// 更新订单状态为已完成
// 这里省略更新订单状态的代码
log.info("订单创建完成");
}
}
@Service
@Slf4j
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
/**
* 扣减库存,使用REQUIRED传播行为
*/
@Transactional(propagation = Propagation.REQUIRED)
public void deductInventory(Long productId, Integer quantity) {
log.info("开始扣减库存,商品ID:{},数量:{}", productId, quantity);
int rows = inventoryMapper.deduct(productId, quantity);
if (rows == 0) {
log.error("库存不足,商品ID:{},请求数量:{}", productId, quantity);
throw new InsufficientInventoryException("库存不足");
}
log.info("库存扣减成功");
}
}
@Service
@Slf4j
public class AccountService {
@Autowired
private AccountMapper accountMapper;
/**
* 扣减余额,使用REQUIRED传播行为
*/
@Transactional(propagation = Propagation.REQUIRED)
public void deductBalance(Long userId, BigDecimal amount) {
log.info("开始扣减余额,用户ID:{},金额:{}", userId, amount);
int rows = accountMapper.deduct(userId, amount);
if (rows == 0) {
log.error("余额不足,用户ID:{},请求金额:{}", userId, amount);
throw new InsufficientBalanceException("余额不足");
}
log.info("余额扣减成功");
}
}在上面的代码中,三个方法都使用了REQUIRED传播行为。当我们调用createOrder()方法时:
createOrder()方法会创建一个新的事务deductInventory()方法时,由于当前已存在事务,会加入到该事务中deductBalance()方法时,同样会加入到已存在的事务中接下来,我们修改deductBalance()方法的传播行为为REQUIRES_NEW:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductBalance(Long userId, BigDecimal amount) {
// 方法实现不变
}此时,当我们调用createOrder()方法时:
createOrder()方法创建一个新的事务(事务 A)deductInventory()方法加入事务 AdeductBalance()方法时,会暂停事务 A,创建一个新的事务(事务 B)这种传播行为适用于那些即使主事务失败也需要确保执行成功的操作,例如日志记录。
在多用户并发访问数据库的场景下,事务隔离级别决定了一个事务对数据的修改如何被其他事务可见。Spring 定义了 5 种事务隔离级别,对应数据库的隔离级别。
为了更好地理解不同隔离级别如何解决并发问题,我们通过具体实例来分析脏读、不可重复读和幻读。
脏读是指一个事务读取了另一个未提交事务修改的数据。当未提交的事务回滚时,已读取的数据就变成了 "脏数据"。
示例场景:
解决方案:使用 READ_COMMITTED 或更高的隔离级别。
不可重复读是指在同一个事务中,多次读取同一数据时,得到的结果不一致。这通常是因为另一个事务修改并提交了该数据。
示例场景:
解决方案:使用 REPEATABLE_READ 或 SERIALIZABLE 隔离级别。
幻读是指在同一个事务中,多次执行相同的查询时,得到的结果集不一致。这通常是因为另一个事务插入或删除了数据并提交。
示例场景:
解决方案:使用 SERIALIZABLE 隔离级别。
在 Spring 中,我们可以通过@Transactional注解的isolation属性来设置事务隔离级别:
@Service
@Slf4j
public class ProductService {
@Autowired
private ProductMapper productMapper;
/**
* 查询商品信息,使用READ_COMMITTED隔离级别
*/
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public Product getProductById(Long id) {
log.info("查询商品信息,商品ID:{}", id);
return productMapper.selectById(id);
}
/**
* 更新商品库存,使用REPEATABLE_READ隔离级别
*/
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateStock(Long productId, Integer quantity) {
log.info("更新商品库存,商品ID:{},数量:{}", productId, quantity);
// 先查询当前库存
Product product = productMapper.selectById(productId);
if (product == null) {
throw new ProductNotFoundException("商品不存在");
}
// 检查库存是否充足
if (product.getStock() < quantity) {
throw new InsufficientInventoryException("库存不足");
}
// 更新库存
productMapper.updateStock(productId, product.getStock() - quantity);
}
}
需要注意的是,隔离级别越高,数据一致性越好,但并发性能也会随之降低。在实际应用中,我们需要根据业务需求和性能要求,选择合适的隔离级别。
五、Spring 事务实现方式:编程式与声明式
Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。编程式事务管理需要手动编写代码来控制事务,而声明式事务管理则通过注解或 XML 配置来实现,更加简洁灵活。
编程式事务管理通过PlatformTransactionManager和TransactionTemplate来实现,它的优点是可以精确控制事务的边界,但缺点是代码侵入性强,与业务逻辑混杂在一起。
@Service
@Slf4j
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private AccountMapper accountMapper;
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 获取事务状态
TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
try {
// 查询商品单价
BigDecimal price = new BigDecimal("99.99");
BigDecimal amount = price.multiply(new BigDecimal(quantity));
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setAmount(amount);
order.setStatus("PENDING");
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存
int inventoryRows = inventoryMapper.deduct(productId, quantity);
if (inventoryRows == 0) {
throw new InsufficientInventoryException("库存不足");
}
log.info("库存扣减成功");
// 扣减余额
int accountRows = accountMapper.deduct(userId, amount);
if (accountRows == 0) {
throw new InsufficientBalanceException("余额不足");
}
log.info("余额扣减成功");
// 提交事务
transactionManager.commit(status);
log.info("订单创建完成,事务已提交");
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
log.error("创建订单失败,事务已回滚", e);
throw e;
}
}
}
TransactionTemplate是对PlatformTransactionManager的封装,使用模板方法模式简化了编程式事务管理:
@Service
@Slf4j
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private AccountMapper accountMapper;
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
transactionTemplate.execute(status -> {
try {
// 查询商品单价
BigDecimal price = new BigDecimal("99.99");
BigDecimal amount = price.multiply(new BigDecimal(quantity));
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setAmount(amount);
order.setStatus("PENDING");
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存
int inventoryRows = inventoryMapper.deduct(productId, quantity);
if (inventoryRows == 0) {
throw new InsufficientInventoryException("库存不足");
}
log.info("库存扣减成功");
// 扣减余额
int accountRows = accountMapper.deduct(userId, amount);
if (accountRows == 0) {
throw new InsufficientBalanceException("余额不足");
}
log.info("余额扣减成功");
log.info("订单创建完成,事务将自动提交");
return true;
} catch (Exception e) {
status.setRollbackOnly();
log.error("创建订单失败,事务将回滚", e);
throw e;
}
});
}
}
声明式事务管理是 Spring 推荐的事务管理方式,它通过 AOP 技术实现,将事务管理与业务逻辑分离,代码侵入性低,配置灵活。
这是最常用的声明式事务管理方式,通过在方法或类上添加@Transactional注解来指定事务属性:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Autowired
private AccountService accountService;
/**
* 创建订单,使用声明式事务管理
*/
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ,
timeout = 30,
rollbackFor = {Exception.class}
)
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 查询商品单价
BigDecimal price = new BigDecimal("99.99");
BigDecimal amount = price.multiply(new BigDecimal(quantity));
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setProductId(productId);
order.setQuantity(quantity);
order.setAmount(amount);
order.setStatus("PENDING");
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存
inventoryService.deductInventory(productId, quantity);
// 扣减余额
accountService.deductBalance(userId, amount);
log.info("订单创建完成");
}
}
@Transactional注解的主要属性:
propagation事务传播行为,默认 REQUIREDisolation事务隔离级别,默认 DEFAULTtimeout事务超时时间(秒),默认 - 1(不超时)readOnly是否为只读事务,默认 falserollbackFor指定哪些异常会触发事务回滚noRollbackFor指定哪些异常不会触发事务回滚除了注解方式,Spring 还支持通过 XML 配置来实现声明式事务管理:
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="create*" propagation="REQUIRED" isolation="REPEATABLE_READ" timeout="30"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP切入点 -->
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.example.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
这种方式的优点是可以集中管理事务属性,不需要修改 Java 代码,但缺点是配置相对繁琐,且事务属性与代码分离,可读性较差。
六、Spring 事务底层实现原理:AOP 与动态代理
Spring 事务管理的底层实现依赖于 AOP(面向切面编程)和动态代理技术。理解这些技术原理,有助于我们更好地使用 Spring 事务,并排查可能出现的问题。
Spring 事务管理本质上是一个 AOP 切面,它在目标方法执行前后插入事务管理的逻辑。具体来说,就是在目标方法执行前开启事务,在目标方法执行后根据执行结果决定提交或回滚事务。
Spring 事务切面的核心是TransactionInterceptor,它实现了MethodInterceptor接口,负责拦截目标方法的调用,并在拦截过程中实现事务管理逻辑。
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标类
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 执行事务增强逻辑
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
invokeWithinTransaction方法是事务管理的核心实现,它的主要流程如下:
@Transactional注解配置的属性)Spring 事务切面通过动态代理技术应用到目标对象上。Spring 支持两种动态代理方式:JDK 动态代理和 CGLIB 动态代理。
JDK 动态代理是基于接口的代理方式,它要求目标类必须实现一个或多个接口。Spring 会为目标类创建一个实现相同接口的代理类,代理类在调用目标方法时会先执行事务切面逻辑。
// 目标接口
public interface OrderService {
void createOrder(Long userId, Long productId, Integer quantity);
}
// 目标类
@Service
public class OrderServiceImpl implements OrderService {
@Override
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
// 业务逻辑实现
}
}
// JDK动态代理创建的代理类(简化版)
public class OrderServiceProxy implements OrderService {
private OrderService target;
private TransactionInterceptor interceptor;
public OrderServiceProxy(OrderService target, TransactionInterceptor interceptor) {
this.target = target;
this.interceptor = interceptor;
}
@Override
public void createOrder(Long userId, Long productId, Integer quantity) {
// 创建方法调用对象
MethodInvocation invocation = new MethodInvocation() {
@Override
public Method getMethod() {
try {
return OrderService.class.getMethod("createOrder", Long.class, Long.class, Integer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
public Object[] getArguments() {
return new Object[]{userId, productId, quantity};
}
@Override
public Object proceed() throws Throwable {
// 调用目标方法
return target.createOrder(userId, productId, quantity);
}
// 其他方法实现省略
};
// 执行事务拦截器逻辑
try {
interceptor.invoke(invocation);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它可以在运行时动态生成目标类的子类,并在子类中重写目标方法,实现代理逻辑。CGLIB 不要求目标类实现接口。
// 目标类(不实现接口)
@Service
public class OrderService {
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
// 业务逻辑实现
}
}
// CGLIB动态代理创建的代理类(简化版)
public class OrderService$$EnhancerByCGLIB$$ extends OrderService {
private TransactionInterceptor interceptor;
@Override
public void createOrder(Long userId, Long productId, Integer quantity) {
// 创建方法调用对象
MethodInvocation invocation = new MethodInvocation() {
@Override
public Method getMethod() {
try {
return OrderService.class.getMethod("createOrder", Long.class, Long.class, Integer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@Override
public Object[] getArguments() {
return new Object[]{userId, productId, quantity};
}
@Override
public Object proceed() throws Throwable {
// 调用父类(目标类)的方法
return super.createOrder(userId, productId, quantity);
}
// 其他方法实现省略
};
// 执行事务拦截器逻辑
try {
interceptor.invoke(invocation);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
Spring 默认的代理策略是:如果目标类实现了接口,则使用 JDK 动态代理;否则,使用 CGLIB 动态代理。我们也可以通过配置强制使用 CGLIB 代理。
Spring 事务管理中,一个关键的组件是TransactionSynchronizationManager,它使用ThreadLocal来存储当前线程的事务状态,确保在多线程环境下事务的隔离性。
public abstract class TransactionSynchronizationManager {
// 存储当前线程的事务资源(如Connection)
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 存储当前线程的事务同步器
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
// 存储当前线程的事务名称
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 存储当前线程的事务是否为只读
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
// 存储当前线程的事务隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
// 存储当前线程是否存在活跃事务
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
// 方法省略...
}
当DataSourceTransactionManager开启事务时,它会从数据源获取一个Connection对象,并将其绑定到TransactionSynchronizationManager的ThreadLocal变量中:
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// 设置自动提交为false
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 将连接绑定到当前线程
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
} catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
// 其他方法省略...
}
在同一个事务中,后续的数据库操作会从TransactionSynchronizationManager中获取已绑定的Connection对象,从而保证使用的是同一个数据库连接,实现事务的 ACID 特性。
七、Spring 事务实战陷阱与解决方案
尽管 Spring 事务管理非常强大,但在实际使用中,仍然会遇到各种问题。本节将介绍一些常见的事务陷阱,并提供相应的解决方案。
自调用是指在同一个类中,一个方法调用另一个方法。由于 Spring 事务是基于动态代理实现的,自调用时不会经过代理对象,导致事务注解失效。
问题代码示例:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 非事务方法调用事务方法
public void placeOrder(Long userId, Long productId, Integer quantity) {
log.info("开始处理订单");
// 自调用,事务注解失效
createOrder(userId, productId, quantity);
log.info("订单处理完成");
}
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}
解决方案:
@Service
@Slf4j
public class OrderProcessService {
@Autowired
private OrderService orderService;
public void placeOrder(Long userId, Long productId, Integer quantity) {
log.info("开始处理订单");
orderService.createOrder(userId, productId, quantity);
log.info("订单处理完成");
}
}
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}
通过 AopContext 获取当前代理对象:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public void placeOrder(Long userId, Long productId, Integer quantity) {
log.info("开始处理订单");
// 通过AopContext获取代理对象,再调用事务方法
OrderService proxy = (OrderService) AopContext.currentProxy();
proxy.createOrder(userId, productId, quantity);
log.info("订单处理完成");
}
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}使用这种方式需要在 Spring 配置中启用暴露代理:
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
// 配置内容
}
默认情况下,Spring 事务只对未被捕获的RuntimeException和Error进行回滚。如果异常被捕获而没有重新抛出,事务将不会回滚。
问题代码示例:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
try {
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存,可能抛出异常
inventoryService.deductInventory(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
// 异常被捕获但未重新抛出,事务不会回滚
}
}
}
解决方案:
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存,可能抛出异常
inventoryService.deductInventory(productId, quantity);
}
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
try {
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存,可能抛出异常
inventoryService.deductInventory(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
// 重新抛出异常,触发事务回滚
throw new RuntimeException("创建订单失败", e);
}
}
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
try {
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 扣减库存,可能抛出异常
inventoryService.deductInventory(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
// 手动设置事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
默认情况下,Spring 事务只对RuntimeException和Error类型的异常进行回滚,对于受检异常(如IOException、SQLException等)不会回滚。
问题代码示例:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) throws Exception {
log.info("开始创建订单");
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 模拟抛出受检异常
throw new Exception("创建订单失败");
}
}解决方案:
通过@Transactional注解的rollbackFor属性指定需要回滚的异常类型:
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long userId, Long productId, Integer quantity) throws Exception {
log.info("开始创建订单");
// 创建订单
Order order = new Order();
// 设置订单属性...
orderMapper.insert(order);
log.info("订单创建成功,订单ID:{}", order.getId());
// 模拟抛出受检异常
throw new Exception("创建订单失败");
}
Spring 事务注解@Transactional只能应用在 public 方法上,对非 public 方法(如 private、protected、default)的注解会被忽略,导致事务失效。
问题代码示例:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public void placeOrder(Long userId, Long productId, Integer quantity) {
log.info("开始处理订单");
createOrder(userId, productId, quantity);
log.info("订单处理完成");
}
// 非public方法,事务注解失效
@Transactional
private void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}
解决方案:
将方法改为 public 访问权限:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public void placeOrder(Long userId, Long productId, Integer quantity) {
log.info("开始处理订单");
createOrder(userId, productId, quantity);
log.info("订单处理完成");
}
// 改为public方法
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}
在使用多数据源的场景中,如果事务管理器配置不正确,可能导致事务失效。
问题代码示例:
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.first")
public DataSource firstDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSource secondDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
在上面的配置中,Spring 会自动选择一个数据源作为主数据源,但当我们操作其他数据源时,事务管理可能失效。
解决方案:
为每个数据源配置单独的事务管理器,并在@Transactional注解中指定使用的事务管理器:
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.first")
public DataSource firstDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.second")
public DataSource secondDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager firstTransactionManager() {
return new DataSourceTransactionManager(firstDataSource());
}
@Bean
public DataSourceTransactionManager secondTransactionManager() {
return new DataSourceTransactionManager(secondDataSource());
}
}
在 Service 中指定事务管理器:
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 指定使用第一个事务管理器
@Transactional(transactionManager = "firstTransactionManager")
public void createOrder(Long userId, Long productId, Integer quantity) {
log.info("开始创建订单");
// 业务逻辑实现
// ...
}
}
@Service
@Slf4j
public class InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
// 指定使用第二个事务管理器
@Transactional(transactionManager = "secondTransactionManager")
public void deductInventory(Long productId, Integer quantity) {
log.info("开始扣减库存");
// 业务逻辑实现
// ...
}
}
如果需要跨多个数据源的事务管理,则需要使用分布式事务解决方案,如 Seata、TCC 等。
八、Spring 事务最佳实践
掌握 Spring 事务的最佳实践,能够帮助我们写出更健壮、更高效的代码。以下是一些经过实践检验的最佳实践建议。
事务边界应尽可能小,只包含必要的数据库操作,避免将耗时操作(如远程调用、IO 操作)包含在事务中,以减少锁竞争,提高并发性能。
推荐做法:
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
// 只包含数据库操作
createOrderRecord(userId, productId, quantity);
deductInventory(productId, quantity);
deductBalance(userId, calculateAmount(productId, quantity));
}
// 非事务方法,包含耗时操作
public void processOrder(Long userId, Long productId, Integer quantity) {
// 远程调用获取商品信息
Product product = productService.getProduct(productId);
// 验证库存(非事务性查询)
if (!inventoryService.checkInventory(productId, quantity)) {
throw new InsufficientInventoryException("库存不足");
}
// 验证余额(非事务性查询)
if (!accountService.checkBalance(userId, calculateAmount(productId, quantity))) {
throw new InsufficientBalanceException("余额不足");
}
// 调用事务方法
createOrder(userId, productId, quantity);
// 发送消息通知(异步操作)
messageService.sendOrderCreatedMessage(userId, productId, quantity);
// 记录操作日志(异步操作)
logService.recordOperationLog(userId, "CREATE_ORDER", "创建订单成功");
}
根据业务需求选择合适的事务传播行为,避免滥用REQUIRES_NEW等传播行为,因为它们可能导致事务嵌套和资源竞争。
推荐做法:
REQUIRED传播行为即可SUPPORTS传播行为MANDATORY传播行为REQUIRES_NEW传播行为根据业务对数据一致性和并发性能的要求,选择合适的事务隔离级别。
推荐做法:
READ_COMMITTED隔离级别REPEATABLE_READ隔离级别SERIALIZABLE隔离级别,除非确实需要最高级别的数据一致性虽然 Spring 有默认的事务回滚规则,但为了代码的可读性和明确性,建议显式指定回滚异常类型。
推荐做法:
// 显式指定回滚异常类型
@Transactional(rollbackFor = {BusinessException.class, DataAccessException.class})
public void createOrder(Long userId, Long productId, Integer quantity) {
// 业务逻辑实现
}
对于只涉及查询操作的事务,设置readOnly = true可以提示数据库优化事务处理,提高性能。
推荐做法:
// 只读事务
@Transactional(readOnly = true)
public List<Order> queryUserOrders(Long userId) {
return orderMapper.selectByUserId(userId);
}为事务设置合理的超时时间,避免长时间运行的事务占用数据库资源,导致锁竞争和性能问题。
推荐做法:
// 设置事务超时时间为30秒
@Transactional(timeout = 30)
public void createOrder(Long userId, Long productId, Integer quantity) {
// 业务逻辑实现
}
长事务是指运行时间较长的事务,它们会长时间占用数据库连接和锁资源,导致并发性能下降。
推荐做法:
在事务中捕获异常时,要确保异常能够正确触发事务回滚,避免出现数据不一致的情况。
推荐做法:
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
try {
// 业务逻辑实现
} catch (BusinessException e) {
log.error("业务异常", e);
// 重新抛出异常,触发回滚
throw e;
} catch (Exception e) {
log.error("系统异常", e);
// 包装为运行时异常抛出,触发回滚
throw new SystemException("系统异常", e);
}
}九、总结
Spring 事务管理是 Spring 框架中最核心、最常用的功能之一,它通过 AOP 和动态代理技术,为开发者提供了简单易用、灵活强大的事务管理能力。