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

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

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

SUPPORTS

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

状况1:a()开启事务

  • a()开启事务的状况下,b()会加入a()的事务。两者属于同一事务。
  • 只要b()抛出异常,a()b()会一块儿回滚。 注意,即便b()的异常被a()捕获,还是要一块儿回滚的。
  • a()调用b()后抛出异常,则b()也会回滚。

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

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

MANDATORY

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

状况1:a()开启事务

  • a()开启事务的状况下,b()会加入a()的事务。两者属于同一事务。
  • 只要b()抛出异常,a()b()会一块儿回滚。 注意,即便b()的异常被a()捕获,还是要一块儿回滚的。
  • a()调用b()后抛出异常,则b()也会回滚。

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

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

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

NOT_SUPPORTED

  • 以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。

状况1:a()开启事务

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

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

总结

事务传播失效

同一个类中事务传播失效

  • 若是将上文的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 代理模式。
相关文章
相关标签/搜索