清晰、明了的@Transcation事务嵌套使用

简介: 清晰、明了的@Transcation事务嵌套使用

概述

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

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

@Transcation注解

image.png

事务实现原理

 Spring的事务是依靠动态代理技术实现的,是在程序运行时给代理对象创建代理类。如果方法或类上添加了@Transcation注解,spring会自动给方法或类创建一个代理类,代理类中包含了开关事务的代码和原始操作。


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

@Transcation使用

1、事务生效的情况:

  这里用一个demo举例子:更新一条数据,我们先删除数据,再插入新数据(主键自动递增)。我们希望删除或插入哪一方失败,数据库都能回滚。

1. 外层有事务,内层无事务

  内外层哪一方有异常,全部都会回滚。

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

结论:外层有事务,内层也会有事务

2. 外层事务(requierd),内层事务(not_supported)


事务隔离级别:not_supported

  先说说事务隔离级别为not_supported时,事务会被挂起,等于没有事务,有异常也不会回滚。

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean deleteById(Integer cid) {
        int i = this.categoryDao.deleteById(cid);
        if(i>0) {
            throw new RuntimeException("xxx"); //有异常也不回滚
        }
        return true;
    }

  如果外层事务为required(不写类型默认required):内外层哪一方有异常,全部都会回滚。

    //外层
    @Transactional(propagation = Propagation.REQUIRED)
    public void updateById(Category category) {
        this.categoryDao.insert(category);
        this.deleteById(category.getCid());
    }
  //内层
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean deleteById(Integer cid) {
        int i = this.categoryDao.deleteById(cid);
        if(i>0) {
            throw new RuntimeException("xxx"); 
        }
        return true;
    }

结论:不管内层是什么级别的事务、有没有事务,只以外层事务为准

总结:

  为什么外层有事务,内层就有事务?借用下图就能理解,method1方法上有事务注解,method1调用了method2。

  生成的代理类长什么样子呢:用事务“包”住了这两个方法的原始代码。

2、事务不生效的情况:

1. 外层无事务,内层有事务

  内层有异常会回滚,和外层无关。外层有异常不会回滚。

    //外层
    public void update(Category category) {
        this.delete(category);
        this.categoryDao.insert(category);
        //int a=2/0; //外有异常,内无异常,删除成功,插入失败
    }
  //内层
  @Transactional
    void delete(Category category) {
        this.categoryDao.delete(category.getId);
        //int a=2/0; //外无异常,内有异常,删除、插入都失败
    }

结论:内层有事务,不影响外层。

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());
    }
}
  1. 在主线程或创建的子线程中,设置自定义的异常处理器:
    @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的

  方法不能被重写,就不能生产代理类。

结论:java实现的动态代理的原理是代理类实现被代理类的相同接口,重写相同方法

6. 数据库不支持事务

总结

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


相关文章
|
监控 druid Java
Spring Boot 3 集成 Druid 连接池详解
在现代的Java应用中,使用一个高效可靠的数据源是至关重要的。Druid连接池作为一款强大的数据库连接池,提供了丰富的监控和管理功能,成为很多Java项目的首选。本文将详细介绍如何在Spring Boot 3项目中配置数据源,集成Druid连接池,以实现更高效的数据库连接管理。
9853 2
Spring Boot 3 集成 Druid 连接池详解
|
Java 数据库连接 mybatis
Mybatis使用注解方式实现批量更新、批量新增
Mybatis使用注解方式实现批量更新、批量新增
848 1
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
1064 4
Spring事务传播机制(最全示例)
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
1385 1
|
JSON Java Maven
在 Java 中如何将 ArrayNode 转换为 ArrayList
【8月更文挑战第23天】
591 2
|
Java Spring
Spring Boot 中的事务传播行为是什么,原理,如何使用
Spring Boot 中的事务传播行为是什么,原理,如何使用
|
XML Java API
Spring揭秘:ApplicationContextAware应用场景及实现原理!
ApplicationContextAware接口能够轻松感知并在Spring中获取应用上下文,进而访问容器中的其他Bean和资源,这增强了组件间的解耦,了代码的灵活性和可扩展性,是Spring框架中实现高级功能的关键接口之一。
435 5
Spring揭秘:ApplicationContextAware应用场景及实现原理!
|
SQL 监控 关系型数据库
TiDB 分布式数据库快速入门详解
这些示例展示了TiDB的一些基本操作。实际使用时,你可能需要根据具体的业务需求和环境进行调整和优化。
1275 4
|
存储 Java Maven
Java能这么轻松识别二维码
Java能这么轻松识别二维码
2046 1