关于@Transactional事务嵌套使用方式

 更新时间:2024年11月08日 14:25:15   作者:夏诗曼CharmaineXia  
Spring框架通过@Transactional注解来管理事务,它可以作用于类和方法上,用于声明事务的属性,如传播行为、隔离级别、超时时间等,Spring事务是基于AOP实现的,它在运行时为加了@Transactional注解的方法或类创建代理

一、概述

事务(Transaction):指数据库中执行的一系列操作被视为一个逻辑单元,要么全部成功地执行,要么全部失败回滚,保证数据的一致性和完整性。

@Transactional注解是Spring框架提供的用于声明事务的注解,作用于类和方法上。

1.1 @Transactional注解

属性可选值作用
propagationREQUIREDREQUIRES_NEWNESTEDNOT_SUPPORTEDSUPPORTSMANDATORY指定事务的传播行为,在事务嵌套时起作用,默认值为Propagation.REQUIRED
isolationDEFAULT(和数据表一致)READ_UNCOMMITTED(读-未提交)READ_COMMITTED(读已提交)REPEATABLE_READ(可重复读)SERIALIZABLE(串行化)指定事务的隔离级别,和数据库的事务一致,默认值为Isolation.DEFAULT
readOnlytruefalse指定事务是否为只读事务,默认值为false。如果将其设置为true,表示事务只涉及读取操作
timeout数字(秒)指定事务的超时时间(秒),默认值为TransactionDefinition.TIMEOUT_DEFAULT。如果事务在指定的时间内未完成,将被自动回滚
rollbackFor异常类.Class指定触发事务回滚的异常类型数组,默认为空。当方法抛出指定类型的异常时,事务将回滚
noRollbackFor异常类.Class指定不触发事务回滚的异常类型数组,默认为空。当方法抛出指定类型的异常时,事务将不回滚
rollbackForClassName异常类名与rollbackFor类似,但是使用异常类型的完全限定名字符串来指定触发事务回滚的异常
noRollbackForClassName异常类名与noRollbackFor类似,但是使用异常类型的完全限定名字符串来指定不触发事务回滚的异常

1.2 Spring事务原理

Spring的事务是依靠aop实现的。

是在程序运行时给代理对象创建代理类。

如果方法或类上添加了@Transcation注解,spring会自动给方法或类创建一个代理类,代理类中包含了开关事务的代码和原始操作。

示意图如下(原始类指加了@Transcation的类):

  

如果嵌套调用呢?  

method1方法上有事务注解,method2没有,method1调用了method2。

会生成如下的代理类:

二、@Transactional使用

2.1 事务失效的7种情况

这里用一个demo举例子:更新一条数据,我们先删除数据,再插入新数据(主键自动递增)。

我们希望删除或插入哪一方失败,数据库都能回滚。Spring事务失效是指发生异常依然不回滚。

1. 同一个类中方法调用

开发中避免不了会对同⼀个类⾥⾯的⽅法调⽤,⽐如有⼀个类 Test,它的⼀个⽅法 A,A 再调⽤本类的⽅法 B(不论⽅法 B 是⽤ public 还是 private 修饰),但⽅法 A 没有声明注解事务,⽽ B ⽅法有。

外部调⽤⽅法 A 之后,⽅法 B 的事务是不会起作⽤的,这也是经常犯错误的⼀个地⽅。

public class Test{
    //外层
    public void A(Category category) {
        this.categoryDao.delete(category);
        this.B(category);//无事务,但有异常
    }
    
    //内层
    @Transactional
    public void B(Category category) {
    	this.categoryDao.insert(category);
        int a=2/0;
    }
}

为什么失效:

其实这还是由于使⽤ Spring AOP 代理造成的,没加@Transactional注解的方法不会被代理类重写,也就不会有事务。

2. 异常被 catch 住,而且没有再次抛出异常

无论是外层异常还是内层异常,只要捕获以后没有抛出异常,都不会回滚。总的来说没有异常不会回滚。

	//外层
    @Transactional
    public void updateById(Category category) {
        try {
            this.categoryDao.deleteById(category.getCid());
            this.categoryDao.insert(category);
            int a=2/0;
        }catch (java.lang.Exception e){
            System.out.println("updateById异常");
        }
    }
    //外层
    public void update(Category category) {
        this.delete(category);
        this.categoryDao.insert(category);
    }
	
	//内层
	@Transactional
    void delete(Category category) {
    	try{
	        this.categoryDao.delete(category.getId);
	        int a=2/0;
        }catch(Exception e){

		}
    }

解决办法:

捕获后再次抛出异常。无论是内层、外层,只要重新抛出异常,就可以回滚。

    //外层
    public void update(Category category) {
        this.delete(category);
        this.categoryDao.insert(category);
    }
	
	//内层
	@Transactional
    void delete(Category category) {
    	try{
	        this.categoryDao.delete(category.getId);
	        int a=2/0;
        }catch(Exception e){
			throw new RuntimeException("service层deleteById方法异常");//可以自定义异常
		}
    }

结论:没有异常不会回滚。

3. 抛出RuntimeException或Error以外的异常

@Transcation有个属性(方法),管理何种异常会引发回滚。  

默认情况下,事务只在RuntimeException和Error上回滚。

抛出RuntimeException和Error以外类型的异常,不会回滚。 

常见的非运行时异常有:SQLException、IOException、FileNotFoundException、ReflectiveOperationException等等。

	@Transactional
    void delete(Category category) throws SQLException{
        this.categoryDao.delete(category.getId);
        int a=2/0;
       	throw new SQLException("xxx");  //抛出异常也不会回滚
    }

解决办法:

使用RollbackFor属 添加 要捕获的异常类型,这样除了RuntimeException和Error类型的异常,遇到Exception以及它的子类的异常,也会发生回滚。

	@Transactional(rollbackFor = Exception.class)
    void delete(Category category) throws SQLException{
        this.categoryDao.delete(category.getId);
        int a=2/0;
       	throw new SQLException("xxx");  //这下就回滚了
    }

结论:使用RollbackFor属性添加要捕获的异常类型

4. 子线程内异常

删除操作新开一个线程执行,并且执行中发生异常。结果是删除回滚,插入没回滚。

多线程环境下,内外层是两个事务,事务具有隔离性,事务之间不会互相干扰。

    //外层
    @Transactional(rollbackFor = java.lang.Exception.class)
    public void update(Category category) {
        Thread thread=new Thread(new Runnable() {
           @Override
           public void run() {
                delete(category);//删除
            }
        });
        this.categoryDao.insert(category);//插入
    }
	
	//内层
	@Transactional(rollbackFor = java.lang.Exception.class)
    void delete(Category category) {
        this.categoryDao.delete(category.getId);
        int a=2/0; //异常
    }

解决办法:

使用Thread.UncaughtExceptionHandler接口捕获线程异常,主线程发现了异常,也跟着回滚。

注:事务还是多个事务

1.创建一个实现了Thread.UncaughtExceptionHandler接口的异常处理器类,该类将负责捕获未被捕获的(没加try-catch的)线程异常并进行处理:

public class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 在此可以处理未被捕获的线程异常
        System.out.println("线程 " + t.getName() + " 发生了异常: " + e.getMessage());
    }
}

2.在主线程或创建的子线程中,设置自定义的异常处理器:

    @Transactional(rollbackFor = java.lang.Exception.class)
    public void updateById(Category category){
        Thread.setDefaultUncaughtExceptionHandler(new CustomUncaughtExceptionHandler());//加上这句
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                deleteById(category.getCid());//删除
            }
        });
        thread.start();
        this.categoryDao.insert(category);//插入
    }

结论:子线程异常抛给主线程,两者一起回滚。

5. 事务方法是private、static、final的

事务是依赖AOP实现的,如果方法不能被重写,就不能生产代理类。

结论:java实现的动态代理的原理是代理类实现被代理类的相同接口、或成为被代理类的子类,然后重写相同方法

6. 数据库不支持事务

mysql的MyISAM存储引擎是不支持事务的,它是旧版 MySQL(MySQL 5.5 之前)中的默认存储引擎。 

7. 设置了某些事务传播行为

在事务嵌套的时候,设置了以下传播行为,会让事务挂起(相当于没有事务)或抛异常。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常
    //外层
    @Transactional
    public void A(Category category) {
        this.categoryDao.insert(category);
        this.B(category.getCid());
    }
	
	//内层
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean B(Integer cid) {
        int i = this.categoryDao.deleteById(cid);
        if(i>0) {
            throw new RuntimeException("xxx"); 
        }
        return true;
    }

2.2 事务6种传播机制

默认是REQUIERD,保证只有一个事务。

总结

spring相当多的功能都用到了动态代理,还需要对这方面知识做个总结,学无止境啊。

相关文章

  • IDEA中的pom.xml文件无法识别问题及解决

    IDEA中的pom.xml文件无法识别问题及解决

    这篇文章主要介绍了IDEA中的pom.xml文件无法识别问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Java如何发起http请求的实现(GET/POST)

    Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • java开发中嵌套类的详解及实例

    java开发中嵌套类的详解及实例

    这篇文章主要介绍了 java开发中嵌套类的详解及实例的相关资料,一般把定义内部类的外围类成为包装类(enclosing class)或者外部类,需要的朋友可以参考下
    2017-07-07
  • java.text.DecimalFormat类十进制格式化

    java.text.DecimalFormat类十进制格式化

    这篇文章主要为大家详细介绍了java.text.DecimalFormat类十进制格式化的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 详解Java如何利用反射提高代码的灵活性

    详解Java如何利用反射提高代码的灵活性

    反射是Java语言的一种特性,它允许程序在运行时动态地获取类的信息并操作类的属性、方法和构造函数,使得我们的代码更加灵活和可扩展,下面就来看看Java中反射机制的具体操作吧
    2023-05-05
  • Mybatis调用SQL Server存储过程的实现示例

    Mybatis调用SQL Server存储过程的实现示例

    在软件开发过程中,经常会使用到存储过程,本文就来介绍一下Mybatis调用SQL Server存储过程的实现示例,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • Java中List删除元素的几种方法总结推荐

    Java中List删除元素的几种方法总结推荐

    这篇文章主要给大家介绍了关于Java中List删除元素的几种方法总结,相信大家在日常的开发过程中,经常需要对List或Map里面的符合某种业务的数据进行删除,需要的朋友可以参考下
    2023-10-10
  • Java实现画线、矩形、椭圆、字符串功能

    Java实现画线、矩形、椭圆、字符串功能

    本篇文章主要介绍了Java实现画线、矩形、椭圆、字符串功能的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • Java实现飞机航班管理系统的思路详解

    Java实现飞机航班管理系统的思路详解

    这篇文章主要介绍了Java实现飞机航班管理系统的思路详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解

    Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解

    这篇文章主要介绍了Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解的相关资料,需要的朋友可以参考下
    2017-09-09

最新评论