【Spring】@Transactional常用参数使用示例、以及常见的一些坑

@Transactional常用配置项参数

readOnly

此参数描述如下:

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

按照这个说法我们来验证一下

在这里插入图片描述

可见该情况下查询没有问题。我们再看一下在里面写更新语句。

在这里插入图片描述
所以很明显可以看出,标注了该属性,你就只能执行查询类语句。

rollbackFor

此参数描述如下:

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

可以了解到,这个参数就是用来控制 @Transactional 注解能回滚的异常类型的,下面举个例子,我会新建一个异常,指定抛出该异常就会回滚。

先看数据库表中数据:
在这里插入图片描述
我新建的异常类:
在这里插入图片描述
看写法
在这里插入图片描述
执行结果:
在这里插入图片描述

可见其更新成功了,并且抛出了坤坤异常,且数据库中数据并没有被改掉,所以我们配置的指定坤坤异常回滚生效了。

但是其中还是有点要注意的地方,就是你如果使用try catch 捕获了异常,没有让它抛出来,那么是不会回滚的,数据库的值仍然会被改掉 !!!!!!
如下图:

在这里插入图片描述

你自己将异常捕获,那么便会无法回滚。

最后补充一点:

在这里插入图片描述
它默认在RuntimeExceptionError上回滚,所以要特别注意你代码中会产生的异常是不是在这两类中。例如下图,如果你抛出了IOException,且没有指定回滚类型,那么事务便会失效。
在这里插入图片描述
所以一般情况下,我会这么写:@Transactional(rollbackFor = Exception.class)

rollbackForClassNamenoRollbackFornoRollbackForClassName与此类似,在此不做赘述。

timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时。
这个没啥好说的,自己试一下即可。

propagation

该属性用于设置事务的传播行为,较为常用的便是这个了。

参数说明

  • Propagation.REQUIRED

    • 支持当前事务,如果当前没有事务,就新建一个事务。(默认)
  • Propagation.SUPPORTS

    • 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • Propagation.MANDATORY

    • 支持当前事务,如果当前没有事务,就抛出异常。
  • Propagation.REQUIRES_NEW

    • 新建事务,如果当前存在事务,把当前事务挂起。
  • Propagation.NOT_SUPPORTED

    • 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • Propagation.NEVER

    • 以非事务方式执行,如果当前存在事务,则抛出异常。
  • Propagation.NESTED

    • 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

下面开始通过实际例子说明

Propagation.REQUIRED

调用test3()进行调试

调试代码:

@Service
public class ITestServiceImpl implements ITestService {

    private static final String QUERY_ALL = "select * from zzp_test_tx";
    private static final String ZHANG_SAN = "update zzp_test_tx set ACCOUNT = 90 where NAME='zhangsan'";
    private static final String LI_SI = "update zzp_test_tx set ACCOUNT = 90 where NAME='lisi'";
    private static final String WANG_WU = "update zzp_test_tx set ACCOUNT = 90 where NAME='wangwu'";
    private static final String ZHAO_SI = "update zzp_test_tx set ACCOUNT = 90 where NAME='zhaosi'";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    @Lazy
    private ITestService iTestService;

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Object test1() {
        //更新王五数据
        jdbcTemplate.update(WANG_WU);
        return 1;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Object test2() {
        //更新赵四数据
        jdbcTemplate.update(ZHAO_SI);
        return 1;
    }

    @Transactional
    @Override
    public Object test3() throws Exception {
        /*
             按照 REQUIRED 的意义分析:
             如果外层 test3() 开启了事务,
             则 test2() 和 test1() 都加入 test3() 的事务中,属于同一个事务。
             否则,test2() 和 test2() 则都会创建一个自己的事务,属于分开的两个事务。
         */
        //注意这里的调用方式,我是自己注入自己调用的,也可以通过spring获取该Bean的实例调用,
        //直接this调用,不管怎么配置的事务,test1()和test2()上的事务注解都不生效。
        iTestService.test1();
        iTestService.test2();
        //按照分析,调用方法 test3 , 这里抛出异常,那更新的两条数据都会回滚。
        if (1 == 1) {
            throw new RuntimeException();
        }
        return 1;
    }
}

按上图说明所示,test3()加上@Transactional,那么这三个方法在同一个事务里面,出错一起回滚。
然后将test3()上的@Transactional去掉,也就是test3()不加事务,此时 test2() test1()是独立的一个事务。

也就是,按照上述代码,test3()抛出异常,不会使test()2test1()回滚,但是你在test1()里面抛出异常,test1()的回滚会导致test2()回滚,因为test1() test2()是同一个事务 。

有人会问,为什么test3()没加事务,test1()test2()还是一个事务呢?

很好理解,Propagation.REQUIRED 的定义就是支持当前事务,执行时发现test1()有事务,test2()自动就加入了test1()的事务。

Propagation.SUPPORTS

有了上面的分析,我们在来看 Propagation.SUPPORTS 我们再再次回顾一下它的含义:支持当前事务,如果当前没有事务,就以非事务方式执行。

所以还是上面的代码,我们把 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.SUPPORTS)
同时我们:

  1. test3() 加上@Transactional
    这种情况下,test1()test2()test3()仍然是在一个事务里面,不论谁出错都会一起回滚
  2. 不给test3()@Transactional
    这种情况下就是所有方法都不存在事务。
Propagation.MANDATORY

同样的,我们再再次回顾一下Propagation.MANDATORY的含义:支持当前事务,如果当前没有事务,就抛出异常。

这个验证起来就简单了,我们把 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.MANDATORY)

对于 test3()

  1. 不加@Transactional
    方法test3()调用即报错。
    在这里插入图片描述

  2. @Transactional
    同样的三个方法在一个事务中,不论谁抛异常,一起回滚。

Propagation.REQUIRES_NEW

同样的,我们再再次回顾一下Propagation.REQUIRES_NEW的含义:新建事务,如果当前存在事务,把当前事务挂起。

用以上的三个方法举例,我们还是先将 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.REQUIRES_NEW),那么按照含义来说,不论我的 test3()使用或者不使用@Transactionaltest1()test2()都是各自单独的事务。
也就是说:

  1. test3()使用了事务,test3()抛异常,不会导致 test1()test2()的回滚
    test1()test2()是各自的事务,他俩之间也不会相互影响,谁抛异常谁自己回滚,也即是test1()抛异常回滚,test2()还是正常执行。)。
  2. test3 未使用事务,情况同上。
Propagation.NOT_SUPPORTED

同样的,我们再再次回顾一下Propagation.NOT_SUPPORTED的含义:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

这个很好理解,还是用上面三个方法举例子,首先我们还是先将 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.NOT_SUPPORTED)

此时test3()加不加@Transactionaltest1()test2()都不会有任何事务。

此时可能有人会问,我的 test3() 加了@Transactional 那么 test3() 里面的更新操作会不会回滚呢。咱们试试看:

@Service
public class ITestServiceImpl implements ITestService {

    private static final String QUERY_ALL = "select * from zzp_test_tx";
    private static final String ZHANG_SAN = "update zzp_test_tx set ACCOUNT = 90 where NAME='zhangsan'";
    private static final String LI_SI = "update zzp_test_tx set ACCOUNT = 90 where NAME='lisi'";
    private static final String WANG_WU = "update zzp_test_tx set ACCOUNT = 90 where NAME='wangwu'";
    private static final String ZHAO_SI = "update zzp_test_tx set ACCOUNT = 90 where NAME='zhaosi'";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    @Lazy
    private ITestService iTestService;

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Override
    public Object test1() {
        //更新王五数据
        jdbcTemplate.update(WANG_WU);
        return 1;
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public Object test2() {
        //更新赵四数据
        jdbcTemplate.update(ZHAO_SI);
        return 1;
    }

    @Transactional
    @Override
    public Object test3() throws Exception {
        /*
            按照 NOT_SUPPORTED 的定义,下面这种情况,会回滚的就只有 zhangsan 的更新操作
         */
        iTestService.test1();
        iTestService.test2();
        //更新张三
        jdbcTemplate.update(ZHANG_SAN);
        if (1==1){
            throw new RuntimeException();
        }
        return 1;
    }
}

结果是:test3()抛异常,test1()test2()的数据没回滚,test3()更新zhangsan的操作还是正常回滚了。

Propagation.NEVER

同样的,我们再再次回顾一下Propagation.NEVER的含义:以非事务方式执行,如果当前存在事务,则抛出异常。
这个验证起来就简单了,
我们把 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.NEVER)

对于 test3()

  1. @Transactional
    方法test3()调用即报错。
    在这里插入图片描述
Propagation.NESTED

同样的,我们再再次回顾一下Propagation.NEVER的含义:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

这个可能比较奇怪,还是先描述一下:
我们先把 test1()test2() 上的注解换成 @Transactional(propagation = Propagation.NESTED)

对于 test3()

  • 使用了@Transactional,那么test1()test2()便会创建嵌套事务,什么是嵌套事务呢?也就是 内部异常不会导致外部回滚,外部回滚会导致内部回滚。举个例子来说,就是这种情况下,test3() 里面抛异常,test1() test2()会一起回滚,但是 test1() 或者 test2()抛出异常,并不会导致test3()中的更新操作回滚。(注意内部异常情况,你的test1()或test2()异常不可向上抛出,否则会导致test3()感知到异常,导致外部也跟着一起回滚了。或者说,你的test1()或test2()异常向上抛出,在test3()里面必须手动trycatch抛异常的方法。)。

看示例代码吧:
在这里插入图片描述
说实话,这其实就是在test3中,强行吃掉了test2()的异常,也就是让test3()检测不到任何异常,此外,由于test2有嵌套的事务test2()它自己会回滚,从而不影响其他更新方法。

说实话,我属实也是没想到 Propagation.NESTED 的应用场景。。

  • 未使用@Transactional,那么test1()test2()各自新建自己事务执行。此时test3()没有任何事务,它自己的更新操作即使出错了也不会回滚。

isolation

该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

参数说明

首先了解一下数据库中可能会发生的脏读等问题:
可见这篇文章 这里不做过多演示!

Spring的隔离级别:

  • Isolation.DEFAULT

    • 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别.
  • Isolation.READ_UNCOMMITTED

    • 读未提交。这是事务最低的隔离级别,这种隔离级别会产生脏读,不可重复读和幻像读。
  • Isolation.READ_COMMITTED

    • 读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险。
  • Isolation.REPEATABLE_READ

    • 可重复读,解决不可重复读的隔离级别,但还是有幻读风险。
  • Isolation.SERIALIZABLE :

    • 串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,解决脏读、不可重复读和幻读。
下面最后记录一个问题: Spring的隔离级别和数据库的隔离级别有什么关系?假如两个不一样,到底是哪个生效呢?

答: 既然是封装,那么Spring项目应该就是以Spring事务为准的,除非使用 @Transactional(isolation = Isolation.DEFAULT)时,才会使用数据库设置的隔离级别。

下面开始一个简单的验证:

先看一下MYSQL8数据库的隔离级别:

在这里插入图片描述
可见是可重复读。 接下来在spring事务指定为读未提交,看看究竟使用的哪个。

在这里插入图片描述
由图中可见很明显不是可重复读的隔离级别。
为了防止误判,我们把它改成@Transactional(isolation = Isolation.DEFAULT)再试一次。(重试之间记得把数据改回原来的样子)
在这里插入图片描述
可见,就是可重复读。所以就是 当spring隔离级别和数据库隔离级别不一样时,spring的优先

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值