转载

Spring 事务传播行为

  • Spring在 TransactionDefinition 接口中规定了7种类型的事务传播行为。 事务传播行为是Spring框架独有的事务增强特性,不属于事务实际提供方即数据库的行为。
事务传播行为类型 说明
REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个事务。

准备

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>fun.roran</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- lombok start -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- lombok end -->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

复制代码

application.yml

server:
  port: 80
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
    username: root
    password: 123456
复制代码

sql

CREATE TABLE `tx` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `value` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
复制代码

DAO

TxDAO

package fun.roran.demo.dao;

public interface TxDAO {
    void a();
    void b();
}

复制代码

TxDAOImpl

package fun.roran.demo.dao.impl;

import fun.roran.demo.dao.TxDAO;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
@Repository
public class TxDAOImpl implements TxDAO {
    @Resource
    JdbcTemplate jdbcTemplate;
    @Override
    public void a() {
        jdbcTemplate.execute("INSERT INTO tx (value) VALUES ('a')");
    }

    @Override
    public void b() {
        jdbcTemplate.execute("INSERT INTO tx (value) VALUES ('b')");

    }
}

复制代码

Service

TxServiceA

package fun.roran.demo.service;

public interface TxServiceA {
    void a();
}

复制代码

TxServiceAImpl

package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class TxServiceAImpl implements TxServiceA {
    @Resource
    private TxDAO txDAO;

    @Resource
    private TxServiceB txServiceB;

    @Override
    public void a() {
        txDAO.a();
        txServiceB.b();
    }
}
复制代码

TxServiceB

package fun.roran.demo.service;

public interface TxServiceB {
    void b();
}

复制代码

TxServiceBImpl

package fun.roran.demo.service.impl;

import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceB;

import javax.annotation.Resource;

public class TxServiceBImpl implements TxServiceB {
    @Resource
    private TxDAO txDAO;

    @Override
    public void b() {
        txDAO.b();
    }
}

复制代码

REQUIRED

  • 如果当前没有事务,就新建一个事务。 如果已经存在一个事务中,加入到这个事务中。
  • 默认为该级别。

情况1: a() 开启事务

  • 以上文代码为例,在 a() 开启事务的情况下, a()b() 相当于处于同一个事务。它们要么全都提交,要么全都不提交。
  • 只要 b() 抛出异常, a()b() 会一起回滚。 注意,即使 b() 的异常被 a() 捕获,仍是要一起回滚的。
  • a() 调用完 b() 后抛出异常回滚, b() 也会回滚。
Spring 事务传播行为

情况2: a() 不开启事务

  • a() 不开启事务, b() 开启事务。
  • a() 调用完 b() 后抛出异常, b() 自然不会回滚。 同样若 b() 抛出异常回滚,也不会影响 a() 已经执行过的代码。
Spring 事务传播行为

SUPPORTS

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

情况1: a() 开启事务

  • a() 开启事务的情况下, b() 会加入 a() 的事务。二者属于同一事务。
  • 只要 b() 抛出异常, a()b() 会一起回滚。 注意,即使 b() 的异常被 a() 捕获,仍是要一起回滚的。
  • a() 调用 b() 后抛出异常,则 b() 也会回滚。
Spring 事务传播行为

情况2: a() 不开启事务

  • a() 不开启事务,则 a()b() 都处在非事务环境下。
Spring 事务传播行为

MANDATORY

  • 使用当前的事务,如果当前没有事务,就抛出异常。

情况1: a() 开启事务

  • a() 开启事务的情况下, b() 会加入 a() 的事务。二者属于同一事务。
  • 只要 b() 抛出异常, a()b() 会一起回滚。 注意,即使 b() 的异常被 a() 捕获,仍是要一起回滚的。
  • a() 调用 b() 后抛出异常,则 b() 也会回滚。
Spring 事务传播行为

情况2: a() 不开启事务

  • 调用 b() 时会抛出异常。

异常

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
复制代码

REQUIRES_NEW

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

情况1: a() 开启事务

  • a() 开启事务,执行 b() 时,会将 a() 的事务挂起, b() 创建一个自己的事务。
  • b() 抛出异常回滚,若 a() 没有对异常进行补获,则也要回滚。 若 a() 捕获了异常,则 a() 不用回滚。
  • a() 调用完 b() 后抛出异常, a() 回滚, b() 不用回滚(因为 b() 的事务已经提交了)。
Spring 事务传播行为

情况2: a() 不开启事务

  • b() 会开启一个自身的事务。 b() 若发生异常回滚不会影响到 a() 已执行操作, a() 调用完 b() 后抛出异常自然不会影响到 b()
Spring 事务传播行为

NOT_SUPPORTED

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

情况1: a() 开启事务

  • a() 开启事务,但在执行 b() 的过程中, a() 的事务被挂起且 b() 是无事务方式执行的。
  • a() 调用 b() 后抛出异常,则 b() 不会被回滚(除了其他 b() 的其它操作可以回滚)。
  • b() 执行中抛出异常,则 b() 已经执行的操作也不会回滚。 同时若 a() 没有捕获 b() 抛出的异常, a() 也会被回滚。若 a() 捕获了,则不会被回滚。
  • 总而言之,不管 a()b() 哪个操作抛出异常且未捕获, a() 一定会被回滚, b() 一定不会回滚。
Spring 事务传播行为

情况2: a() 不开启事务

  • a()b() 都处于无事务状态。

NEVER

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

情况1: a() 开启事务

  • 调用 b() 抛出异常。

异常

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
复制代码

情况2: a() 不开启事务

  • a()b() 都处于无事务状态。

NESTED

  • 如果当前存在事务,则在嵌套事务内执行。 如果当前没有事务,则创建一个事务。

情况1: a() 开启事务

  • 调用 b() 时,开启一个事务,这个事务是 a() 的子事务。
  • b() 抛出异常并回滚时。该异常若被 a() 捕获,则 a() 不会回滚,否则 a() 回滚。
  • a() 调用 b() 后抛出异常,则 a()b() 一起回滚。

情况2: a() 不开启事务

  • 相当于只有 b() 开启了事务。

总结

Spring 事务传播行为

事务传播失效

同一个类中事务传播失效

  • 如果将上文的 a()b() 方法写在同一个类中,那么事务传播行为将失效。
  • 因为事务传播行为的实现是通过代理对象实现的。而原来的对象是没有事务传播行为功能的。

代码示例

package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class TxServiceAImpl implements TxServiceA {
    @Resource
    private TxDAO txDAO;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void a() {
        txDAO.a();
        b();
    }

    @Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    @Override
    public void b() {
        txDAO.b();
    }
}
复制代码
  • 调用 a() 方法,发现能够正常运行,不抛出异常。
  • 原因:根据 TxServiceAImpl 对象生产代理对象后,代理对象底层还是调用 TxServiceAImpl 对象的 b() 方法,而这个方法是不支持事务传播功能的。

解决方法

  • 不通过 this 调用方法,而是通过注入代理对象类调用。

代码示例

package fun.roran.demo.service.impl;
import fun.roran.demo.dao.TxDAO;
import fun.roran.demo.service.TxServiceA;
import fun.roran.demo.service.TxServiceB;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
public class TxServiceAImpl implements TxServiceA {
    @Resource
    private TxDAO txDAO;

    @Resource
    private TxServiceA txServiceA;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void a() {
        txDAO.a();
        txServiceA.b();
    }

    @Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
    @Override
    public void b() {
        txDAO.b();
    }
}
复制代码
原文  https://juejin.im/post/5e88103ce51d4546e8575a4d
正文到此结束
Loading...