Spring事务管理
什么是事务?
事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务的四大特性(ACID)
- 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency):事务开始前和结束后,数据的完整性约束没有被破环。比如A向B转了钱,转账前后钱的总数不变。
- 隔离性(Isolation):多个用户并发访问数据数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据相互隔离。比如事务A和事务B都修改同一条记录,这条记录就会被重复修改或者后者会覆盖前者的修改记录。
- 持久性(Durability):事务完成后,事务对数据库的更新被保存到数据库,其结果是永久的。
事务并发可能产生的问题
脏数据:事务对缓冲池中的行记录进行修改,但是还没有被提交。
- 脏读:事务A读取到了事务B修改但未提交的数据。如果此时B回滚到修改之前的状态,A就读到了脏数据。
- 不可重复读:事务A多次读取同一个数据,此时事务B在A读取过程中对数据修改并提交了,导致事务A在同一个事务中多次读取同一数据而结果不同。
- 幻读:事务A对表进行修改,这个修改涉及到表中所有的行,但此时事务B新插入了一条数据,事务A就会发现居然还有数据没有被修改,就好像发生幻觉一样。
事务隔离级别
TransactionDefinition接口中定义了事务的隔离级别和传播行为等参数。
为了应对不同的事务问题,有了如下几种事务隔离级别。
隔离级别 | 含义 |
---|---|
DEFAULT | 使用后端数据库默认的隔离级别 |
READ_UNCOMMITTED | 允许读取未提交的数据,可能导致脏读、不可重复读、幻读 |
READ_COMMITTED | 允许并发事务提交后读取,可能导致不可重复读、幻读。 |
REPEATABLE_READ | 在同一次事务中对相同字段的多次读取结果一致,除非数据本身被改变。可防止脏读和不可重复读,但依然存在幻读的可能 |
SERIALIZABLE | 不会发生 脏读、不可重复读、幻读等问题。隔离级别最高,但是并发下效率低,一般是通过锁表来实现的。 |
事务的传播行为
推荐阅读这篇博客,以下关于事务传播行为的内容均出自于该篇博客。
事务传播行为类型 | 说明 |
---|---|
REQUIRED | 如果已经存在一个事务中,加入到这个事务中;如果当前没有事务,就新建一个事务。Spring的默认传播行为 |
SUPPORTS | 如果已经存在一个事务中,加入到这个事务中;如果当前没有事务,就以非事务方式执行。 |
MANDATORY | 如果已经存在一个事务中,加入到这个事务中;如果当前没有事务,就抛出异常 |
REQUIRES_NEW | 如果当前存事务,将当前事务挂起不使用,新建一个事务 |
NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。有时候业务比较复杂,经常可能有service之间互相调用的情况发生,比如下面:
ForumSerivce#addTopic()方法调用了 UserSerice#addCredits()方法,发生关联性服务方法的调用:
public class ForumService {
private UserService userService;
public void addTopic() {
// 被关联调用的业务方法
userService.addCredits();
}
}
当事务隔离级别是REQUIRED、SUPPORTS、MANDATORY时,都是将事务加入到已经存在的事务中。因此将ForumService#addTopic()设置为REQUIRED时, UserSerice#addCredits()设置为REQUIRED、SUPPORTS、 MANDATORY时,运行的效果都是一致的。
当addTopic()运行在一个事务下(如设置为REQUIRED),而addCredits()设置为NESTED时,如果底层数据源支持保存点,Spring将为内部的addCredits()方法产生的一个内嵌的事务。如果 addCredits()对应的内嵌事务执行失败,事务将回滚到addCredits()方法执行前的点,并不会将整个事务回滚。内嵌事务是内层事务的一 部分,所以只有外层事务提交时,嵌套事务才能一并提交。
嵌套事务不能够提交,它必须通过外层事务来完成提交的动作,外层事务的回滚也会造成内部事务的回滚。
REQUIRES_NEW 和NESTED也是容易混淆的两个传播行为。
REQUIRES_NEW启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别和锁,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。
由此可见,NEW 和 NESTED 的最大区别在于:REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,而NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。
事务的状态
TransactionStatus接口中有一些方法可以查询事务的状态。
// 该事务是否是一个新事务
boolean isNewTransaction();
// 该事务是否有保存点
boolean hasSavepoint();
// 设置仅事务回滚。这指示事务管理的唯一可能结果是回滚,作为抛出异常的替代方法,而异常又会触发回滚。
void setRollbackOnly();
// 是否被设置了rollback-only
boolean isRollbackOnly();
// 将潜在的会话(underlying session)刷新到数据库中
void flush();
// 事务是否已经完成
boolean isCompleted();
PlatformTransactionManager
PlatformTransactionManager(平台事务管理器)
Spring为不同的持久化框架提供了不同的PlatformTransactionManager接口实现。
事务的使用
在Spring Boot中,声明事务很简单。只需要在service层的类或者方法上加上@Transactional注解即可,省去了繁琐的配置。在service类上加@Transactional,声明这个service所有方法需要事务管理。每一个业务方法开始时都会打开一个事务。
@Transactional
public void transfer(AccountA a, AccountB a, Double money) {
a.subtract(100.0);
b.add(100.0);
}
@Transactional可以配置如下属性
- transactionManager,可以指定具体的PlatformTransactionManager
- propagation,传播行为,默认是REQUIRED
- isolation,隔离级别,默认是DEFAULT(使用后端数据库默认的隔离级别)
- timeout,设置超时时间(秒),超时后会回滚事务。默认值-1。
- readOnly,该属性用于设置当前事务是否为只读事务,默认值为false。
- rollbackFor,该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。
- noRollbackFor,该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚
- rollbackForClassName,该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。
- noRollbackForClassNam,该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚
Spring默认情况下会对运行时异常(RunTimeException)进行事务回滚,这些异常属于非受检异常。比如ArrayIndexOutOfBoundsException,NullPointerException;但不会对受检异常进行事务回滚,这些异常包括:ClassNotFoundException、NoSuchMetodException等。若要对所有异常都进行事务回滚,可以如下设置。
@Transactional(rollbackFor=Exception.class)