Spring事务处理

在讲解Spring中使用事务管理之前,首先让我们来看下什么是事务。

数据库事务

一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:

  1. 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
  2. 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
    当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

事务的四个特性

数据库事务拥有以下四个特性,习惯上被称之为ACID特性:

  1. 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
  2. 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
  4. 持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

事务的隔离

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:

脏读

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

不可重复读

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。

虚读(幻读)

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。

事务隔离级别

数据库为我们提供的四种隔离级别:

  1. Serializable (串行化):可避免脏读、不可重复读、幻读的发生
  2. Repeatable read (可重复读):可避免脏读、不可重复读的发生
  3. Read committed (读已提交):可避免脏读的发生
  4. Read uncommitted (读未提交):最低级别,任何情况都无法保证

Spring中事务级别

在SpringBoot中,要使用事务,方法很简单,这里我们以Jpa为例

1
2
3
4
@Transactional(rollbackFor = Exception.class)
public interface FdmtMoItemRepository extends JpaRepository<FdmtMoItem, Integer> {
void deleteByItemIdIn(List<Long> itemIds);
}

在定义的interface中,需要为类或者函数上加上@Transactional注解。打开@Transactional直接的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";

@AliasFor("value")
String transactionManager() default "";

Propagation propagation() default Propagation.REQUIRED;

Isolation isolation() default Isolation.DEFAULT;

int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

boolean readOnly() default false;

Class<? extends Throwable>[] rollbackFor() default {};

String[] rollbackForClassName() default {};

Class<? extends Throwable>[] noRollbackFor() default {};

String[] noRollbackForClassName() default {};
}

在注解中,上述函数的作用分别是:

  1. value transactionManager 用于设置事务管理器的名字
  2. propagation 设置事务的传播机制。也就是,但需要进行事务操作的时候:
    • PROPAGATION_REQUIRED 如果当前已经存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
    • PROPAGATION_SUPPORTS 如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。
    • PROPAGATION_MANDATORY 当前必须存在一个事务,否则抛出异常。
    • PROPAGATN_REQUIRES_NEW 如果当前存在事务,先把当前事务相关内容封装到一个实体,然后重新创建一个新事务,接受这个实体为参数,用于事务的恢复。更直白的说法就是暂停当前事务(当前无事务则不需要),创建一个新事务。 针对这种情况,两个事务没有依赖关系,可以实现新事务回滚了,但外部事务继续执行。
    • NOT_SUPPORTED 如果当前存在事务,挂起当前事务,然后新的方法在没有事务的环境中执行,没有spring事务的环境下,sql的提交完全依赖于 defaultAutoCommit属性值 。
    • PROPAGATION_NEVER 如果当前存在事务,则抛出异常,否则在无事务环境上执行代码。
    • PROPAGATION_NESTED 如果当前存在事务,则使用 SavePoint 技术把当前事务状态进行保存,然后底层共用一个连接,当NESTED内部出错的时候,自行回滚到 SavePoint这个状态,只要外部捕获到了异常,就可以继续进行外部的事务提交,而不会受到内嵌业务的干扰,但是,如果外部事务抛出了异常,整个大事务都会回滚。
  3. isolation 设置事务的隔离级别,也就是对应了上面提到的数据库的几个事务隔离级别
    • DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别。
    • SERIALIZABLE 保证所有的情况不会发生(锁表)
    • REPEATABLE_READ 会出幻读(锁定所读取的所有行)。
    • READ_COMMITTED 会出现不可重复读、幻读问题(锁定正在读取的行)。
    • READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )。
  4. timeout 设置事务的超时时间
  5. readOnly 设置是否为只读事务
  6. rollbackFor 设置事务在法身什么异常的情况下进行回滚
  7. rollbackForClassName 设置当发生对应名字的异常时进行回滚
  8. noRollbackFor 设置发生什么异常的情况下不回滚
  9. noRollbackForClassName 设置当发生对应名字的异常时进行不回滚

欢迎关注个人公众号:
个人公号