前言

之前我的项目中事务处理都是直接在对应事务方法上加@Transacational注解让spring容器帮我处理,但其具体有哪些细节并不是太了解。在浏览许多博客学习之后,我对spring的事务处理有了更清晰的认识。

什么是事务

首先,事务是什么?简单理解事务就是一组操作。比如增删改查,对于数据库来说这些操作要么成功执行提交要么不执行回滚。事务最经典的例子就是转账:A要给B转账1000元,这个过程包含两个关键操作

  1. 将A的账户减少1000元
  2. 将B的账户增加1000元

如果在整个过程中出现异常比如网络问题,A的账户减少了1000元但是B的账户却没有增加金钱,这样整个事务必须回滚到执行前的状态。事务就是保证操作要么成功要么失败,这是事务的原子性。在业务中我们最常使用到事务的是数据库操作。

事务的特性

  • 原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
  • 一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
  • 隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

Spring对事务的支持

Spring提供两种方式对事务进行处理:

  1. 编程式事务处理。通过TransactionTemplate或者TransactionManager手动对事务进行提交和回滚。
    //TransacationTemplate管理事务
    @Autowired
    private TransactionTemplate transactionTemplate;
    public void testTransaction() {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    @Override
    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    try {
    //具体事务
    } catch (Exception e){
    //手动回滚
    transactionStatus.setRollbackOnly();
    }

    }
    });
    }


    //TransactionManager管理事务
    @Autowired
    private PlatformTransactionManager transactionManager;

    public void testTransaction() {
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
    //具体事务操作
    transactionManager.commit(status);
    } catch (Exception e) {
    //捕捉异常手动回滚
    transactionManager.rollback(status);
    }
    }
  2. 声明式事务处理。通过@Transactional注解让容器进行处理。
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void aMethod {
    //do something
    B b = new B();
    C c = new C();
    b.bMethod();
    c.cMethod();
    }
    两个事务例子来源于:https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/spring/Spring%E4%BA%8B%E5%8A%A1%E6%80%BB%E7%BB%93.md

事务的传播机制

如果带事务的方法A调用带事务的方法B,A出现异常了B会不会回滚?反过来呢?这些事务之间的调用问题执行结果就需要根据事务的传播行为来决定。
首先要知道spring中传播行为有哪些:

  • PROPAGATION_REQUIRED。使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。例子:AB都使用PROPAGATION_REQUIRED。A调B,无论是A出现异常还是B出现异常整个过程都会回滚。

  • PROPAGATION_REQUIRES_NEW。创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。例子:A使用PROPAGATION_REQUIRED,B使用PROPAGATION_REQUIRES_NEW,A调B,如果A出现异常,B的事务照样成功提交,而A自身回滚;但如果B抛出异常且未被手动捕获(try...catch)则A和B都回滚

  • PROPAGATION_NESTED。如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。也就是说,在外部方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。例子:A使用PROPAGATION_REQUIRED,B使用PROPAGATION_NESTED,A调B,如果B出现异常则B回滚而A不回滚;但A出现异常则AB都回滚

  • PROPAGATION_MANDATORY。如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)。方法必须在一个已存在事务中执行。例子:A不使用事务,B使用PROPAGATION_MANDATORY,A调B,无论B是否出现异常都会抛异常,B事务不会执行;如果A使用事务并使用PROPAGATION_REQUIRED,则B事务属于A事务,B出现异常回滚则A也回滚

  • PROPAGATION_SUPPORTS。如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。例子:B使用PROPAGATION_SUPPORTS,如果A存在事务且传播行为是PROPAGATION_REQUIRED,则B抛异常AB一起回滚;如果A不存在事务则相当于两个都没开启事务,不存在回滚。

  • PROPAGATION_NOT_SUPPORTED。以非事务方式运行,如果当前存在事务,则把当前事务挂起。例子:A不使用事务,B使用PROPAGATION_NOT_SUPPORTED,A调B,则相当于两个都不使用事务,不会存在回滚操作;如果A存在事务,则会挂起A,先执行B,如果没抛异常则无事发生。

  • PROPAGATION_NEVER。以非事务方式运行,如果当前存在事务,则抛出异常。跟PROPAGATION_MANDATORY相反,方法不能在事务中执行。例子:B使用PROPAGATION_NEVER,A调B,如果A使用事务则抛异常,相当于强制AB都不使用事务。

通常用的有PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED,如果错误的配置了PROPAGATION_SUPPORTSPROPAGATION_NOT_SUPPORTEDPROPAGATION_NEVER这三种传播行为,事务将不会回滚。毕竟我们是用事务管理目的就是在业务出现异常时让操作回复到原本的状态,所以传播行为使用前三种足够了

事务的隔离级别

  • ISOLATION_DEFAULT :使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别, Oracle 默认采用的 READ_COMMITTED 隔离级别.

  • ISOLATION_READ_UNCOMMITTED :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

  • ISOLATION_READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

  • ISOLATION_REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

  • ISOLATION_SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。通过上锁的操作让所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

@Transactional注解的属性以及使用

@Transactional使用范围

  • 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  • 类 :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
  • 接口 :不推荐在接口上使用。

timeout超时时间

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒,默认值为-1,即是不超时。

readOnly只读

事务为只读事务,如果里面存在对数据库的增删改操作则会抛出异常并回滚

isolation隔离级别

设置事务的隔离级别

propagation传播行为

设置事务的传播行为

rollbackFor和noRollbackFor

自定义回滚或不回滚异常