Spring 事务传播行为

本文最后更新于:2020年9月10日 下午

简介

  • 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()也会回滚。

img

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

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

img

SUPPORTS

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

情况1:a()开启事务

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

img

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

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

img

MANDATORY

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

情况1:a()开启事务

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

img

情况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()的事务已经提交了)。

img

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

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

img

NOT_SUPPORTED

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

情况1:a()开启事务

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

img

情况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()开启了事务。

总结

img

事务传播失效

同一个类中事务传播失效

  • 如果将上文的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();
    }
}

public方法

  • @Transactional只能用于 public 的方法上,否则事务失效。 如果要用在非public方法上,可以开启 AspectJ 代理模式。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!