文章目录
- 一、事务的基本概念
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
- 二、Spring 事务管理的优势
- 简化事务管理代码
- 提供多种事务管理方式
- 整合多种持久化技术
- 三、Spring 事务管理的核心接口
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
- 四、事务传播行为
- 五、事务隔离级别
- 六、声明式事务管理
- 七、事务的回滚规则
- 八、事务失效场景
在当今的企业级应用开发中,数据的一致性和完整性是至关重要的。而 Spring 框架提供的事务管理功能,为我们解决这一关键问题提供了强大的支持。本文将深入探讨 Spring 事务管理的相关知识,帮助大家更好地理解和运用它。
一、事务的基本概念
事务是一组逻辑操作单元,这些操作要么全部成功,要么全部失败。例如在银行转账场景中,从账户 A 向账户 B 转账 100 元,涉及到从账户 A 扣款 100 元和向账户 B 存款 100 元两个操作,这两个操作必须作为一个整体来执行,要么都成功,否则就会导致数据不一致。事务具有 ACID 特性:
原子性(Atomicity)
事务中的所有操作要么全部执行成功,要么全部失败回滚,就像一个原子一样不可分割。
一致性(Consistency)
事务执行前后,数据库的完整性约束没有被破坏,数据从一个一致性状态转换到另一个一致性状态。
隔离性(Isolation)
多个事务并发执行时,一个事务的执行不能被其他事务干扰,各个事务之间相互隔离。
持久性(Durability)
一旦事务提交成功,对数据库所做的修改就会永久保存下来,即使系统发生故障也不会丢失。
二、Spring 事务管理的优势
简化事务管理代码
传统的 JDBC 事务管理需要编写大量的样板代码来处理事务的开始、提交、回滚等操作,而 Spring 通过声明式事务管理,让我们可以通过简单的配置或注解来管理事务,大大减少了代码量。
提供多种事务管理方式
Spring 支持编程式事务管理和声明式事务管理。编程式事务管理通过编写代码来控制事务,灵活性较高;声明式事务管理则通过配置或注解来指定事务的边界和属性,更符合面向切面编程(AOP)的思想,便于维护和管理。
整合多种持久化技术
Spring 事务管理可以与多种持久化技术如 JDBC、Hibernate、JPA 等无缝集成,无论你使用哪种持久化技术,都能方便地进行事务管理。
三、Spring 事务管理的核心接口
PlatformTransactionManager
这是 Spring 事务管理的核心接口,它提供了事务管理的基本方法,如获取事务、提交事务、回滚事务等。不同的持久化技术对应不同的实现类,例如对于 JDBC,有 DataSourceTransactionManager;对于 Hibernate,有 HibernateTransactionManager 等。
TransactionDefinition
该接口定义了事务的属性,包括事务的传播行为、隔离级别、超时时间、是否只读等。通过设置这些属性,可以灵活地控制事务的行为。
TransactionStatus
代表一个事务的状态,通过它可以获取事务的相关信息,如是否新事务、是否已完成等,还可以手动回滚事务。
四、事务传播行为
事务传播行为定义了一个事务方法被另一个事务方法调用时,事务应该如何传播。Spring 定义了 7 种事务传播行为:
PROPAGATION_REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新事务。例如方法 A 调用方法 B,若方法 A 已经在一个事务中,方法 B 会加入方法 A 的事务;若方法 A 没有事务,方法 B 会创建一个新事务。
PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
PROPAGATION_REQUIRES_NEW:总是创建一个新事务。如果当前存在事务,则将当前事务挂起,直到新事务执行完毕。
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。
PROPAGATION_NEVER:以非事务方式执行操作。如果当前存在事务,则抛出异常。
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新事务。嵌套事务可以独立于外部事务进行回滚或提交,但外部事务回滚时,嵌套事务也会回滚。
五、事务隔离级别
事务隔离级别用于解决多个事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。Spring 支持以下 5 种事务隔离级别:
ISOLATION_DEFAULT:使用底层数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、不可重复读和幻读。
ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,避免了脏读,但可能会出现不可重复读和幻读。
ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果是一致的,除非数据被当前事务本身修改,避免了脏读和不可重复读,但可能会出现幻读。
ISOLATION_SERIALIZABLE:最高的隔离级别,完全串行化的事务,避免了脏读、不可重复读和幻读,但性能开销较大。
六、声明式事务管理
声明式事务管理是 Spring 推荐的事务管理方式,它基于 AOP 实现。在 Spring 中,可以通过 XML 配置或注解来实现声明式事务管理。
@Service
public class OrderService {@Autowiredprivate OrderDao orderDao;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)public void placeOrder(Order order) {orderDao.save(order);}
}
在@Transactional注解中可以指定事务的传播行为、隔离级别等属性。
七、事务的回滚规则
默认情况下,Spring 事务在遇到运行时异常(RuntimeException 及其子类)和错误(Error)时会自动回滚事务,而遇到受检异常(Checked Exception)时不会自动回滚事务。但我们可以通过@Transactional注解的rollbackFor和noRollbackFor属性来指定回滚规则。
1. rollbackFor:指定需要回滚事务的异常类型,例如:
@Transactional(rollbackFor = CustomBusinessException.class)
public void businessMethod() {// 业务逻辑
}
这里指定了遇到CustomBusinessException异常时回滚事务。
2. noRollbackFor:指定不需要回滚事务的异常类型,例如:
@Transactional(noRollbackFor = DataAccessException.class)
public void dataAccessMethod() {// 数据访问逻辑
}
这里表示遇到DataAccessException异常时不回滚事务。
八、事务失效场景
1. 方法内部调用:当一个类中的非事务方法调用同一类中的事务方法时,事务会失效。因为 Spring 的声明式事务是基于 AOP 代理实现的,在类内部方法调用时,并不会经过代理对象,事务增强逻辑也就不会生效。例如:
@Service
public class UserService {@Autowiredprivate UserDao userDao;
public void nonTransactionalMethod() {// 内部调用事务方法transactionalMethod(); }
@Transactionalpublic void transactionalMethod() {userDao.save(new User());}
}
在上述代码中,nonTransactionalMethod内部调用transactionalMethod,此时transactionalMethod上的事务不会生效。解决办法是将调用逻辑抽取到另一个被 Spring 管理的服务类中,通过依赖注入进行调用。
2. 未被 Spring 管理的类:如果一个类没有被 Spring 容器管理,那么它上面的@Transactional注解不会生效。比如,在一个普通的 Java 类中使用@Transactional,事务不会起作用。确保所有需要事务管理的类都通过@Component、@Service、@Repository等注解交由 Spring 管理。
3. 不支持事务的数据源:若使用的数据源本身不支持事务,如某些轻量级的嵌入式数据库默认不开启事务支持,那么即使配置了 Spring 事务管理,事务也无法正常工作。需要检查并确保所使用的数据源具备事务处理能力,并且正确配置了事务相关参数。
4. 异常被捕获处理:当事务方法内部捕获了异常,却没有重新抛出运行时异常或错误,事务不会回滚。例如:
@Transactional
public void saveUser(User user) {try {userDao.save(user);int i = 1 / 0; // 模拟异常} catch (Exception e) {// 捕获异常但未抛出运行时异常log.error("操作出错", e); }
}
在这种情况下,事务不会回滚。正确做法是在捕获异常后,根据业务逻辑决定是否重新抛出运行时异常,如throw new RuntimeException(e)。
5. 错误的事务传播机制设置:在使用事务传播行为时,如果设置不当,也可能导致事务失效。例如,将一个方法的事务传播行为设置为PROPAGATION_NOT_SUPPORTED,那么该方法内的事务操作实际上是在无事务环境下执行的。需要根据业务场景合理选择事务传播行为。
6. 多线程调用:在多线程环境下,Spring 的事务管理基于线程绑定来工作。如果在一个线程中开启事务,然后在另一个线程中执行事务相关操作,事务上下文无法传递,导致事务失效。要避免在多线程场景下直接使用 Spring 事务管理,可考虑使用支持分布式事务的解决方案,如 Seata 等。