Spring框架学习笔记(4) --- [Spring的事务传播行为]

本文详细解析了Spring框架中的事务传播行为,包括REQUIRED、SUPPORTS及REQUIRES_NEW三种常见类型的使用场景,并通过实例展示了不同传播行为的效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Spring的事务传播行为

事务发生传播的前提是,至少有两个或两个以上的事务发生关联.
事务传播行为(propagation behavior) 指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
事务传播行为是 Spring 框架独有的事务增强特性,是spring提供的事务增强功能;不属于数据库的行为.

比如说,开启了事务的方法A里面调用了具有事务的方法B;这时,方法B是直接在方法A内开启的事务执行;或者是方法B单独开启自己的单独事务执行,这个的话需要决定于方法B,要考虑到方法B使用了什么传播类型的事务.

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是默认的事务传播行为。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

这里案例的话,主要看看前三种事务传播行为;

案例搭建

这边的话,案例首先通用,这个案例的话就是在一个方法A中调用方法B

xml配置文件通用模板

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
</beans>

还有一点,上篇笔记中忘记补充了,实际的使用时,事务一般加在服务层的代码部分;

这里也就不整数据多的表了;就简简单单的两个数据表;一个字段名是namea,一个字段名是nameb

#测试使用的数据表1;
CREATE TABLE IF NOT EXISTS t_testa(
       `namea` VARCHAR(20)
);
#测试使用的数据表2;
CREATE TABLE IF NOT EXISTS t_testb(
        `nameb` VARCHAR(20)
);

使用的maven包

<dependencies>
    <!-- spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>

    <!--junit测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>provided</scope>
    </dependency>


    <!-- spring-jdbc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- 阿里数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.10</version>
    </dependency>

    <!--mysql-context-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>
    <!--aspectj-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
</dependencies>

database.properties
数据库连接池需要的属性参数

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day20211024_study_mybatis_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
user=root
pwd=123456
initialSize = 5
maxActive = 10

spring-jdbc.xml
这里配置开启事务注解识别

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
    <!--标记开启注解扫描-->
    <context:component-scan base-package="com.xiaozhi.spring">
    </context:component-scan>

    <!--配置引入数据源配置-->
    <context:property-placeholder location="database.properties"/>

    <!--创建连接池配置-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${user}"/>
        <property name="password" value="${pwd}"/>
        <property name="initialSize" value="${initialSize}"/>
        <property name="maxActive" value="${maxActive}"/>
    </bean>
    <!--配置JdbcTemplate 引用数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"/>
    </bean>
    <!--配置事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--开启事务自动配置-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

</beans>

用于整合配置文件的spring.xml,这里案例暂时就引入一个spring-jdbc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--引入使用jdbc-->
    <import resource="spring-jdbc.xml"></import>
</beans>

测试类1的数据访问层TestOneDao

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-21 09:18
 * 测试使用的数据访问类1;
 */
@Repository
public class TestOneDao {
    //自动装配注入spring提供的jdbc;
    @Autowired
    JdbcTemplate jdbcTemplate;
    //这里定义一个方法A;
    public void  methodA(){
        System.out.println("这是方法A的底层实现");
        jdbcTemplate.update("insert into t_testa(namea) values ('阿杰')");
    }
}

测试类2的数据访问层TestTwoDao

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-21 09:19
 * 测试使用的数据访问2
 */
@Repository
public class TestTwoDao {
    //自动装配注入spring提供的jdbc;
    @Autowired
    JdbcTemplate jdbcTemplate;
    //这里定义方法B;
    public void methodB(){
        System.out.println("这是方法B的底层实现");
        jdbcTemplate.update("insert into t_testb(nameb) values ('小智')");
    }
}

测试类2的服务层TestTwoService

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-21 09:23
 *测试使用的服务类2
 */
@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //事务注解加载服务类的方法上;
    @Transactional
    public void methodB(){
        testTwoDao.methodB();
    }
}

测试类1的服务类TestOneService
在这里会用方法A调用测试类2的服务类方法B;

/**
 * @author by CSDN@小智RE0
 * @date 2021-11-21 09:23
 * 测试使用的服务类1
 */
@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    @Transactional
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

这次初步搭建案例的话,我就简单地在方法上加了事务注解@Transactional

测试一下

public class TestDemo {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
        TestOneService oneService = app.getBean("testOneService", TestOneService.class);
        //调用执行测试;
        oneService.methodA();
    }
}

搭建基本完成

在这里插入图片描述
已经存入数据

在这里插入图片描述

(1)PROPAGATION_REQUIRED

指定的方法必须在事务内执行,若当前存在事务,加入到当前事务中,若当前没有事务,则创建一个新事务,这种传播行为是最常见的,也是 spring 默认的传播行为;

这个通俗一点,就是说现在有两个方法,方法A和方法B; 我在方法A内调用方法B;方法B底层是开启了PROPAGATION_REQUIRED这个类传播型的事务,

  • 要是方法A已经开启了事务,那么方法B就会加入到方法A开启的事务中运行;
  • 要是方法A没有事务,那么方法B就创建单独属于自己的新事务,在自己的事务中运行;

简单图解看一下

在这里插入图片描述

Ok,用案例来看看效果,具体怎么看到效果呢;
我先把数据库中之前测试的两个姓名数据删除掉;

📢先试试方法A有事务,方法B有事务PROPAGATION_REQUIRED,方法A调用方法B;
这里先给方法A直接用事务;
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //默认的事务
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

方法B中也是使用事务Propagation.REQUIRED
方法B中写个异常方法,不要在这里捕获它;
TestTwoService

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B这里就用这个默认的事务PROPAGATION_REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB(){
        testTwoDao.methodB();
        //这里手动写个算术异常;
        int num =10/0;
    }
}

OK,我再执行测试一下

在这里插入图片描述

由于方法B与方法A的在同一个事务中运行;这里方法B中出错,就连数据表A的数据都没有添加进去;
在这里插入图片描述

📢现在试试方法A没有事务,方法B使用事务PROPAGATION_REQUIRED,方法A调用方法B
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //方法A这里现在不加事务注解
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

方法B使用事务Propagation.REQUIRED;
在方法B中手动写异常;
TestTwoService

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B这里就用这个默认的事务PROPAGATION_REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB(){
        testTwoDao.methodB();
        //这里手动写个算术异常;
        int num =10/0;
    }
}

测试执行

在这里插入图片描述

这里的话,注意到,由于方法A没有事务;方法B中使用了事务Propagation.REQUIRED;然后方法A调用方法B,时,方法B自己单独自己开启注解,和方法A没关系;方法A底层的添加数据实现了;但是方法B由于有异常,事务检测到了异常,拒绝执行添加数据;

在这里插入图片描述

(2)PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行

通俗说一下,现在有两个方法,方法A和方法B; 我在方法A内调用方法B;方法B底层是开启了PROPAGATION_SUPPORTS这个类传播型的事务,

  • 要是方法A开启了事务,那么方法B就会加入到方法A开启的事务中运行;
  • 要是方法A没有开启事务,那么方法B也不开启事务;

简图
在这里插入图片描述

同样,写案例之前,我这里也把之前数据库添加的数据删除掉;

这里演示同样的,分为两种;

📢 方法A中开启了事务,方法B开启事务Propagation.SUPPORTS;方法A调用方法B
方法A就用默认的事务;
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //方法A使用默认的事务PROPAGATION_REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

方法B使用事务Propagation.SUPPORTS;
同样地;方法B中有异常;
TestTwoService

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B这里使用事务Propagation.SUPPORTS
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB(){
        testTwoDao.methodB();
        //这里手动写个算术异常;
        int num =10/0;
    }
}

测试执行

在这里插入图片描述

数据库这边,两张表都没有数据添加情况;
由于方法1开启了事务,方法B开启事务Propagation.SUPPORTS;方法A调用方法B;那么方法B就加入到方法A的事务当中;
在这里插入图片描述

📢 试试方法A不开启事务,方法B开启事务Propagation.SUPPORTS;方法A调用方法B
方法A不使用事务
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //方法A不使用事务
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

方法B使用事务Propagation.SUPPORTS;
这里同样地方法B中手动添加异常;

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B这里使用事务Propagation.SUPPORTS
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB(){
        testTwoDao.methodB();
        //这里手动写个算术异常;
        int num =10/0;
    }
}

测试执行一下
在这里插入图片描述

OK,注意到数据表A和B都已经添加了数据,也就是方法A和方法B都没有事务;
方法A不开启事务,方法B开启事务Propagation.SUPPORTS;方法A调用方法B;那么方法B也不开启事务;
在这里插入图片描述


(3)PROPAGATION_REQUIRES_NEW

总是新建一个事务,如果当前存在事务,把当前事务挂起,直到新建的事务结束.

通俗地说,现在有两个方法,方法A和方法B; 我在方法A内调用方法B;方法B底层是开启了PROPAGATION_REQUIRES_NEW这个类传播型的事务;

  • 要是方法A开启了事务,那么方法B会把方法A的事务挂起;转而新建属于自己的事务,在自己的事务内运行;
  • 要是方法A没有开启事务,那么方法B就去创建属于自己的事务,在自己的事务中运行;

简易图解看一下
在这里插入图片描述

写案例之前,我这里也把之前数据库添加的数据删除掉;
这次案例有一点特别;

📢 方法A中开启事务;方法B开启事务PROPAGATION_REQUIRES_NEW,用方法A调用方法B;

方法A使用默认的事务;在调用方法B之后,再手动写个异常;
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;

    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //方法A使用默认的事务PROPAGATION_REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
        //这里手动写个算术异常;
        int num =10/0;
    }
}

方法B使用事务Propagation.REQUIRES_NEW;
TestTwoService

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B使用事务Propagation.REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(){
        testTwoDao.methodB();
    }
}

测试执行一下

在这里插入图片描述

注意到,数据表B添加了数据;即方法B执行了;
刚才方法A中开启事务;方法B开启事务PROPAGATION_REQUIRES_NEW,用方法A调用方法B;
发现方法A中出现异常,并不会影响方法B的执行;因为方法B把方法A的事务挂起;转而自己单独开了事务;
在这里插入图片描述

📢 试试方法A不开启事务,方法B开启事务PROPAGATION_REQUIRES_NEW,方法A调用方法B

首先把之前数据表填入的数据删除掉;

这个案例的话,在方法A处不开启事务;
这里也不写异常
TestOneService

@Service(value = "testOneService")
public class TestOneService {
    //自动装配测试1的数据访问类;
    @Autowired
    TestOneDao testOneDao;
    
    //自动装配测试2的服务类;
    @Autowired
    TestTwoService testTwoService;

    //方法A处不开启事务
    public void methodA(){
        testOneDao.methodA();
        //调用服务类2的方法B;
        testTwoService.methodB();
    }
}

方法B中使用事务Propagation.REQUIRES_NEW;
且在方法B中添加异常;
TestTwoService

@Service(value = "testTwoService")
public class TestTwoService {
    //自动装配测试类2的数据访问类;
    @Autowired
    TestTwoDao testTwoDao;

    //方法B使用事务Propagation.REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(){
        testTwoDao.methodB();
        //这里写个异常;
        int num =10/0;
    }
}

测试执行

在这里插入图片描述

可看到数据表A添加了数据;数据表B并没有添加的数据;
方法A不开启事务,方法B开启事务PROPAGATION_REQUIRES_NEW,方法A调用方法B;
这里方法B自己创建了事务,和调用它的方法A没有关系;
由于方法B中的异常被事务检测到了;所以方法B没有去执行添加数据到数据库;方法B的异常并不会影响到没开事务的方法A;所以方法A正常地添加了数据;

在这里插入图片描述


声明式事务失效的几种情况

  • @Transactional注解 应用在非 public 修饰的方法上;
  • @Transactional注解 的属性 propagation 设置错误时;
  • 在同一个类中方法进行调用,导致@Transactional 失效;
  • 异常在出现时就被 catch 捕获了;导致@Transactional 失效
  • 数据库引擎不支持事务;比如MyIsam和memory引擎则不支持事务;最近使用的数据库默认是Innodb引擎,它支持事务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小智RE0-走在路上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值