Spring事务机制介绍


一、事务介绍

1. 基本介绍

事务在一个应用中的作用是举足轻重轻重的,合理利用事务能够对保证数据的一致性与完整性。

举个常见的例子, 用户A用户B 转账了 1000, 在这个过程中两个用户的金额总额都应该都是 1000,但如果在执行转账时 用户A 进程异常导致失败,此时 用户B 并未收到转账此时 用户A 的账户却已经扣款,这显然违背了数据的一致性。

而事务的出现就是为了解决此类情景,在上述示例中当 用户A 转账出现异常时将自动实现回滚,将扣除的 1000 返回余额中,保证数据的一致性。

2. 传播行为

事务的传播行为是事务中十分重要的一项参数,描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

如下示例示例中即在普通方法 demo() 中嵌套调用了事务方法 sayHello() 方法。

public void demo() {
    sayHello();
}

@Transactional
public void sayHello() {
    System.out.println("Hello World!");
}

Spring 中事务的传播行为可取参数如下:

参数 描述
Propagation.REQUIRED 默认值,如果有事务则加入事务,没有的则新建事务。
Propagation.NOT_SUPPORTED 容器不为这个方法开启事务。
Propagation.REQUIRES_NEW 不管是否存在都新建一个事务,将原事务挂起,待新的执行完毕继续执行老的事务。
Propagation.MANDATORY 必须在一个已有的事务中执行,否则抛出异常。
Propagation.NEVER 必须在一个没有的事务中执行,否则抛出异常,与 MANDATORY 相反。
Propagation.SUPPORTS 如果其他调用这个方法的方法声明了事务则使用其事务,若无则就不使用事务。

3. 事务问题

当存在多个事务时可能触发下列等一系列问题。

  • 脏读:一个事务读取到另一事务未提交的更新数据。
  • 幻读:一个事务读到另一个事务已提交的 insert 数据。
  • 可重复读:在同一事务中多次读取数据时能够保证所读数据一样,不能读到另一事务已提交的更新数据。
  • 不可重复读:在同一事务中多次读取同一数据返回的结果有所不同,后续读取可以读到另一事务已提交的更新数据。

4. 隔离级别

为了解决上述中的几大问题事务引入了隔离级别,其描述一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。

Spring 的事务隔离级别可取参数如下,其中 MYSQL 默认为 REPEATABLE_READ 级别。

(1) Isolation.READ_UNCOMMITTED
  • 读取未提交数据,若事务已经开始写数据,则其它事务仅允许读此行数据。
  • 该隔离级别会出现脏读。
(2) Isolation.READ_COMMITTED
  • 读取已提交数据,若是写事务将会禁止其他事务访问该行数据。
  • 避免了脏读,但会出现不可重复读。
(3) Isolation.REPEATABLE_READ
  • 一个事务内多次读同一个数据,当前事务还没结束则其它事务不能访问该数据。
  • 读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写)。
  • 该隔离级别会出现幻读。
(4) Isolation.SERIALIZABLE
  • 要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。

二、注解介绍

1. 基本使用

Spring 工程中使用注解相对较为简单,通过 @Transactiona 注解即可实现对事件开启事务。

当注解作用于类上时将会为类中所有声明为 pulbic 的方法开启事务,当作用于单个方法时则只为当前方法开启事务,为了细化事务粒度,更推荐使用后者配置事务。

注意当同时在类上和方式上同时使用事务注解则方法上的将会覆盖类上注解的配置,且 @Transactiona 作用于方法时方法必须声明为 public,否则将不会生效。

@Transactional
public class TransactionServiceImpl1 implements TransactionService {
    
    // 覆盖类上配置
    @Transactional
    public void demo() {
        System.out.println("Hello World!");
    }
}

2. 注解参数

(1) propagation

通过 propagation 可指定事务的传播行为,具体可选参数值参考第一大点说明。

配置示例代码如下,其中 NOT_SUPPORTED 表示当前不开启事务。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void demo() {
    System.out.println("Hello World!");
}
(2) isolation

通过 isolation 用于指定事务的隔离级别,具体可选参数值参考第一大点说明,具体使用方式同 propagation

(3) readOnly

通过 readOnly 表示当前事务是否为只读事务,若设置为 true 则在方法体内执行写操作将会抛出异常。

未替事务指定 readOnly 时默认值为 false,配置示例如下:

@Transactional(readOnly = true)
public int insert(SysUser user) {
    return sysUserDao.insert(user);
}
(4) rollbackFor

通过 rollbackFor 参数可以指定在何种情况下进行事务回滚,最常见的设置即配置为异常时回滚。

如下示例中指定类在抛出 IllegalArgumentException 异常时进行事务回滚。

@Transactional(rollbackFor = IllegalArgumentException.class)
public int insert(SysUser user) {
    return sysUserDao.insert(user);
}
(5) noRollbackFor

noRollbackFor 顾名思义即定义在何种情况下不进行回滚。

如下示例中定义了只要出现异常即进行事务回滚,但当抛出 IllegalArgumentException 异常则不回滚。

@Transactional(
        rollbackFor = Exception.class,
        noRollbackFor = IllegalArgumentException.class
)
public int insert(SysUser user) {
    return sysUserDao.insert(user);
}

3. 事务失效

注意在 Spring 中无法实现在事务定义类中调用事务方法,否则将会导致代理失败从而引起事务不生效。

即事务的定义与调用不应该处于同一个类中,如下述示例中 demo() 调用同一个类中事务方法 execute() 可能导致事务失效。

public class Test {

    public void demo() {
        // 此处事务可能失效
        execute();
    }
    
    @Transactional
    public void execute() {
        // dosomething
    }
}

因此事务的定义与调用应分开于不同的类中,改造上述的示例得到下述结果。

public class TestA {

    public void demo() {
        TestB testB = new TestB();
        testB.execute();
    }
}


public class TestB {
    
    @Transactional
    public void execute() {
        // dosomething
    }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录