03. Spring 事务管理

文章目录

  • [1. Spring 事务管理简介](#1. Spring 事务管理简介)
  • [2. @Transactional 注解属性信息](#2. @Transactional 注解属性信息)
  • [3. @Transactional 简单使用](#3. @Transactional 简单使用)
  • [4. @Transactional 使用注意事项](#4. @Transactional 使用注意事项)
    • [4.1 正确指定事务回滚的异常类型](#4.1 正确指定事务回滚的异常类型)
      • [4.1.1 Java 异常继承体系](#4.1.1 Java 异常继承体系)
    • [4.2 `@Transactional` 注解应用在 `public` 方法或类上才有效](#4.2 @Transactional 注解应用在 public 方法或类上才有效)
    • [4.3 正确设置 `@Transactional` 的 `propagation` 属性。](#4.3 正确设置 @Transactionalpropagation 属性。)
      • [4.3.1 事务的传播行为](#4.3.1 事务的传播行为)
      • [4.3.2 `TransactionDefinition` 和 `Propagation`](#4.3.2 TransactionDefinitionPropagation)
  • [5. Spring 事务失效的场景](#5. Spring 事务失效的场景)
    • [5.1 @Transactional 注解使用在非 public 方法上,导致事务失效](#5.1 @Transactional 注解使用在非 public 方法上,导致事务失效)
    • [5.2 propagation 参数设置错误,导致事务失效](#5.2 propagation 参数设置错误,导致事务失效)
    • [5.3 rollbackFor 参数设置错误,导致事务失效](#5.3 rollbackFor 参数设置错误,导致事务失效)
    • [5.4 在同一个类中,方法之间的相互调用导致事务失效](#5.4 在同一个类中,方法之间的相互调用导致事务失效)
    • [5.5 异常被捕获导致事务失效](#5.5 异常被捕获导致事务失效)
    • [5.6 数据库引擎不支持事务](#5.6 数据库引擎不支持事务)

1. Spring 事务管理简介

事务管理是应用系统开发中必不可少的一部分,Spring 为事务管理提供了丰富的功能。Spring 的事务管理分为编程式和声明式两种方式。声明式事务管理可以使业务代码逻辑不受污染,将业务逻辑和事务处理解耦。声明式事务也有两种方式:一种是在配置文件(xml)中做相关的事务规则声明,一种是基于注解 @Transactional 的方式

事务是数据库的概念,Spring 本身是没有事务的,Spring 的事务是借助 AOP,通过动态代理的方式实现。

在我们要操作数据库的时候,实际是 Spring 通过动态代理进行功能拓展,在我们的代码操作数据库之前通过数据库客户端打开数据库事务,如果代码执行完毕没有异常信息或者是没有 Spring 要捕获的异常信息时,再通过数据库客户端提交事务;如果有异常信息或者是有 Spring 要捕获的异常信息,再通过数据库客户端程序回滚事务,从而控制数据库事务。

Spring 基于注解使用事务主要有两步:

  • 在主配置类上声明 @EnableTransactionManagement 注解,表示开启声明式事务;
  • 在访问数据库的方法上添加 @Transactional 注解。

@Transactional 注解也可以用在类上,表示该类的所有公共方法都配置相同的事务属性。如果类和方法都配置了 @Transactional,应用程序会用方法级别的事务属性来管理事务。

2. @Transactional 注解属性信息

属性名 说明
name 当配置文件中有多个 TransactionManager时,可以用该属性指定选择哪个事务管理器
propagation 事务的传播行为,默认值:REQUIRED
isolation 事务的隔离度,默认值:DEFAULT
timeout 事务的超时时间,默认值 -1。如果超过该时间但事务未完成,则回滚该事务
readOnly 指定事务为只读事务,默认值 false。指定当前事务为只读事务后,当前事务不能进行写操作,否则报错
rollbackFor 指定能够触发事务回滚的异常类型,如果有多个异常类型要指定,用逗号分隔
noRollbackFor 抛出该参数指定的异常类型,不回滚事务

3. @Transactional 简单使用

如下有一个保存数据的方法,加上 @Transactional 注解,运行时抛出运行时异常之后,事务会自动回滚,数据不会插入数据库。

java 复制代码
    @Override
    @Transactional
    public boolean save() {
        boolean res = stateRepository.save();
        if(true) {
            throw new RuntimeException();
        }
        return res;
    }
  • 如果去掉 @Transactional 注解,save() 就是一个普通的没有事务的方法,执行时还是会抛出异常,但是会插入数据。

  • 如果加上 @Transactional 注解,但是在方法内使用 try-catch 捕获异常,则事务不会回滚,数据会插入成功。

    java 复制代码
        @Override
        @Transactional
        public boolean save() {
            boolean res = stateRepository.save();
            try {
                if(true) {
                    throw new RuntimeException();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return res;
        }

4. @Transactional 使用注意事项

4.1 正确指定事务回滚的异常类型

默认情况下,@Transactional 会在运行时异常及其子类异常时回滚事务,其他类型的异常 Spring 不会帮我们回滚事务。如果要在指定类型的异常时回滚事务,需要使用 rollbackFor 指定异常类型,如 @Transacrional(rollbackFor = Exception.class)

4.1.1 Java 异常继承体系

最顶层的异常类型是可抛出异常 Throwable,有 ErrorException 两个子类。

  • Error 表示严重的错误(如 OOM);
  • Exception 分为 运行时异常(RuntimeException)和非运行时异常;
  • 非运行时异常是检查异常(checked exceptions),编译阶段就能检查出来,编码时需要 try-catch 或抛出异常。
  • Error 和运行时异常是非检查异常(unchecked exceptions),编译阶段不会检查。

4.2 @Transactional 注解应用在 public 方法或类上才有效

只有 @Transactional 注解应用到 public 方法,才能进行事务管理。

4.3 正确设置 @Transactionalpropagation 属性。

propagation 属性用于指定事务的传播行为,当 propagation 参数指定为 SUPPORTSNOT_SUPPORTEDNEVER 时,事务将不会发生回滚。

4.3.1 事务的传播行为

事务的传播行为是指,多个事务方法相互调用时,事务如何在这些方法之间传播。

Spring 支持一下 7 种事务传播行为,定义在枚举类 Propagation

传播类型 说明
REQUIRED(默认值) 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,则新建一个事务
SUPPORTS 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,则以非事务方式执行
MANDATORY 支持当前事务,如果有就加入当前事务中。如果当前方法没有事务,就抛出异常
REQUIRES_NEW 如果当前存在事务,就把当前事务挂起,然后新建一个事务;如果当前方法不存在事务,就新建一个事务
NOT_SUPPORTED 以非事务方式执行,如果当前方法存在事务就挂起当前事务,执行完后恢复事务
NEVER 以非事务方式执行,如果当前方法存在事务,则抛出异常
NESTED 如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行。(嵌套事务使用保存点作为回滚点,内部事务回滚不影响外部事务的提交,但是外部事务的回滚会把内部事务一起回滚。)

4.3.2 TransactionDefinitionPropagation

TransactionDefinition 和 Propagation 是 Spring 框架中两个不同的概念,它们在事务管理中起着不同的作用。

  • TransactionDefinition 是一个接口,它定义了事务的基本属性,如隔离级别、传播行为、超时时间等。它允许您为事务设置各种属性,并将这些属性应用于事务管理器。
  • Propagation是一个枚举,它定义了事务的传播行为。事务传播行为确定了当一个事务方法被另一个事务方法调用时会发生什么。它控制了事务如何与现有事务相关联,以及是否创建新的事务。
  • 总之,TransactionDefinition 是关于定义事务属性的,而 Propagation 是关于控制事务传播行为的。它们在 Spring 框架的事务管理中发挥着重要作用,使开发人员能够精细地控制事务的行为。

5. Spring 事务失效的场景

5.1 @Transactional 注解使用在非 public 方法上,导致事务失效

5.2 propagation 参数设置错误,导致事务失效

propagation 参数设置为 SUPPORTSNOT_SUPPORTEDNEVER 时,@Transactional 注解就不会产生效果。

5.3 rollbackFor 参数设置错误,导致事务失效

  • Spring 默认在方法抛出未检查异常或 Error 时回滚事务,其他异常类型不会回滚;
  • 如果要在方法抛出其他异常时也能回滚事务,需要 rollbackFor 参数指定异常类型。

5.4 在同一个类中,方法之间的相互调用导致事务失效

如下,在 StateService 中有两个保存方法:

java 复制代码
	@Override
    @Transactional
    public boolean save() {
    	save2();
        boolean res = stateRepository.save();
        if(true) {
            throw new RuntimeException();
        }
        return res;
    }
    
    private boolean save2() {
        StateDAO stateDAO = new StateDAO();
        stateDAO.setWorkspaceKey("space1");
        return stateRepository.saveState(stateDAO);
    }

save() 方法首先调用了 save2() 方法,然后 save() 方法抛出异常,导致事务回滚,两条数据都不会插入数据库。

现在我们想实现:save() 方法抛异常不影响 save2() 方法插入数据。@Transactionalpropagation 参数可以设置事务的传播行为,我们可以尝试给 save2() 方法设置 propagation = REQUIRES_NEW 去实现。如下:

java 复制代码
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean save() {
    	save2();
        boolean res = stateRepository.save();
        if(true) {
            throw new RuntimeException();
        }
        return res;
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private boolean save2() {
        StateDAO stateDAO = new StateDAO();
        stateDAO.setWorkspaceKey("space1");
        return stateRepository.saveState(stateDAO);
    }

但是,运行之后,发现 save2() 方法的数据也没有插入数据库。

原因:

在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类的两个方法直接调用,不会被 Spring 的事务拦截器,也就是说 save2() 方法的 @Transactional 注解是失效的。

为解决该问题,可以新建一个 service 类,提供 save2() 方法。然后在 save() 方法中调用这个外部的 save2() 方法。

5.5 异常被捕获导致事务失效

事务通知只有捕获到了目标抛出的异常,才能进行回滚处理;如果目标自己处理掉异常,事务通知无法知悉。

如:

java 复制代码
    @Override
    @Transactional
    public boolean save() {
        boolean res = planBaselineStateRepository.save();
        try {
            if(true) {
                throw new RuntimeException();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return res;
    }

5.6 数据库引擎不支持事务

  • InnoDB 引擎支持事务,是 MySQL 默认的引擎;
  • MyIsam 引擎不支持事务。
相关推荐
自律的kkk11 分钟前
mysql基础快速入门
java·数据库·mysql·oracle
唐 城43 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·spring·log4j
阿杰同学1 小时前
如何实现 MySQL 的读写分离?
数据库·mysql
Mr.朱鹏1 小时前
操作002:HelloWorld
java·后端·spring·rabbitmq·maven·intellij-idea·java-rabbitmq
weisian1511 小时前
Redis篇--应用篇3--数据统计(排行榜,计数器)
数据库·redis·缓存
羊村懒哥1 小时前
mysql-二进制安装方式
数据库·mysql
言之。1 小时前
Redis单线程快的原因
数据库·redis·缓存
geovindu2 小时前
python: Oracle Stored Procedure query table
数据库·python·mysql·postgresql·oracle·sqlserver·mssql
山人在山上2 小时前
arcgis server ip修改后服务异常解决方案
数据库·tcp/ip·arcgis
不剪发的Tony老师2 小时前
SQL实现新年倒计时功能
数据库·sql