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 代理模式。
相關文章
相關標籤/搜索