SpringBoot 實戰 (十) | 聲明式事務

微信公衆號:一個優秀的廢人
如有問題或建議,請後臺留言,我會盡力解決你的問題。

前言

如題,今天介紹 SpringBoot 的 聲明式事務。

Spring 的事務機制

所有的數據訪問技術都有事務處理機制,這些技術提供了 API 用於開啓事務、提交事務來完成數據操作,或者在發生錯誤時回滾數據。

而 Spring 的事務機制是用統一的機制來處理不同數據訪問技術的事務處理,Spring 的事務機制提供了一個 PlatformTransactionManager 接口,不同的數據訪問技術的事務使用不同的接口實現,如下表:

數據訪問技術 實現
JDBC DataSourceTransactionManager
JPA JPATransactionManager
Hibernate HibernateTransactionManager
JDO JdoTransactionManager
分佈式事務 JtaTransactionManager

聲明式事務

Spring 支持聲明式事務,即使用註解來選擇需要使用事務的方法,他使用 @Transactional 註解在方法上表明該方法需要事務支持。被註解的方法在被調用時,Spring 開啓一個新的事務,當方法無異常運行結束後,Spring 會提交這個事務。如:

@Transactional
public void saveStudent(Student student){
        // 數據庫操作
}

注意,@Transactional 註解來自於 org.springframework.transcation.annotation 包,而不是 javax.transaction。

Spring 提供一個 @EnableTranscationManagement 註解在配置類上來開啓聲明式事務的支持。使用了 @EnableTranscationManagement 後,Spring 容器會自動掃描註解 @Transactional 的方法與類。@EnableTranscationManagement 的使用方式如下:

@Configuration
@EnableTranscationManagement 
public class AppConfig{

}

註解事務行爲

@Transactional 有如下表所示的屬性來定製事務行爲。

屬性 含義
propagation 事務傳播行爲
isolation 事務隔離級別
readOnly 事務的讀寫性,boolean型
timeout 超時時間,int型,以秒爲單位。
rollbackFor 一組異常類,遇到時回滾。(rollbackFor={SQLException.class})
rollbackForCalssName 一組異常類名,遇到回滾,類型爲 string[]
noRollbackFor 一組異常類,遇到不回滾
norollbackForCalssName 一組異常類名,遇到時不回滾。

類級別使用 @Transactional

@Transactional 不僅可以註解在方法上,還可以註解在類上。註解在類上時意味着此類的所有 public 方法都是開啓事務的。如果類級別和方法級別同時使用了 @Transactional 註解,則使用在類級別的註解會重載方法級別的註解。

SpringBoot 的事務支持

  1. 自動配置的事務管理器

在使用 JDBC 作爲數據訪問技術時,配置定義如下:

@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(DataSource.class)
public PlatformTransactionManager transactionManager(){
    return new DataSourceTransactionManager(this.dataSource)
}

在使用 JPA 作爲數據訪問技術時,配置定義如下:

@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager(){
    return new JpaTransactionManager()
}
  1. 自動開啓註解事務的支持

SpringBoot 專門用於配置事務的類爲 org.springframework.boot.autoconfigure.transcation.TransactionAutoConfiguration,此配置類依賴於 JpaBaseConfiguration 和 DataSourceTransactionManagerAutoConfiguration。
而在 DataSourceTransactionManagerAutoConfiguration 配置裏還開啓了對聲明式事務的支持,代碼如下:

@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
@Configuration
@EnableTransactionManagement
protected static class TransactionManagementConfiguration{

}

所以在 SpringBoot 中,無須顯式開啓使用 @EnableTransactionManagement 註解。

實戰

演示如何使用 Transactional 使用異常導致數據回滾與使用異常導致數據不回滾。

  1. 準備工作:

SpringBoot 2.1.3
JDK 1.8
IDEA

  1. pom.xml 依賴:
<?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 http://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.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.nasus</groupId>
    <artifactId>transaction</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>transaction</name>
    <description>transaction Demo project for Spring Boot</description>

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

    <dependencies>
        <!-- JPA 相關 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- web 啓動類 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mysql 連接類 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- lombok 插件,簡化實體代碼 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <!-- 單元測試 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

代碼註釋很清楚,沒啥好說的。

  1. application.yaml 配置:
spring:
  # \u6570\u636E\u5E93\u76F8\u5173
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=true
    username: root
    password: 123456
  # jpa \u76F8\u5173
  jpa:
    hibernate:
      ddl-auto: update   # ddl-auto:\u8BBE\u4E3A create \u8868\u793A\u6BCF\u6B21\u90FD\u91CD\u65B0\u5EFA\u8868
    show-sql: true
  1. 實體類:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    private Integer age;
}
  1. dao 層
import com.nasus.transaction.domain.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
}
  1. service 層
import com.nasus.transaction.domain.Student;

public interface StudentService {

    Student saveStudentWithRollBack(Student student);

    Student saveStudentWithoutRollBack(Student student);

}

實現類:

import com.nasus.transaction.domain.Student;
import com.nasus.transaction.repository.StudentRepository;
import com.nasus.transaction.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    // 直接注入 StudentRepository 的 bean
    private StudentRepository studentRepository;

    // 使用 @Transactional 註解的 rollbackFor 屬性,指定特定異常時,觸發回滾
    @Transactional(rollbackFor = {IllegalArgumentException.class})
    @Override
    public Student saveStudentWithRollBack(Student student) {
        Student s = studentRepository.save(student);
        if ("高斯林".equals(s.getName())){
            //硬編碼,手動觸發異常
            throw new IllegalArgumentException("高斯林已存在,數據將回滾");
        }
        return s;
    }

    // 使用 @Transactional 註解的 noRollbackFor 屬性,指定特定異常時,不觸發回滾
    @Transactional(noRollbackFor = {IllegalArgumentException.class})
    @Override
    public Student saveStudentWithoutRollBack(Student student) {
        Student s = studentRepository.save(student);
        if ("高斯林".equals(s.getName())){
            throw new IllegalArgumentException("高斯林已存在,數據將不會回滾");
        }
        return s;
    }

}

代碼註釋同樣很清楚,沒啥好說的。

  1. controller 層
import com.nasus.transaction.domain.Student;
import com.nasus.transaction.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/student")
public class StudentController {

    // 注入 studentservice 的 bean
    @Autowired
    private StudentService studentService;

    // 測試回滾情況
    @PostMapping("/withRollBack")
    public Student saveStudentWithRollBack(@RequestBody Student student){
        return studentService.saveStudentWithRollBack(student);
    }

    // 測試不回滾情況
    @PostMapping("/withOutRollBack")
    public Student saveStudentWithoutRollBack(@RequestBody Student student){
        return studentService.saveStudentWithoutRollBack(student);
    }
}

Postman 測試結果

爲了更清楚地理解回滾,以 debug (調試模式) 啓動程序。並在 StudentServiceImpl 的 saveStudentWithRollBack 方法上打上斷點。

測試前數據庫結果:
測試前數據庫結果

  1. Postman 測試回滾

Postman 測試異常導致數據回滾
debug 模式下可見數據已保存,且獲得 id 爲 1。:
回滾

繼續執行拋出異常 IllegalArgumentException,將導致數據回滾:
導致數據回滾,控制檯打印出信息

測試後數據庫結果:並無新增數據,回滾成功。
測試後數據庫結果

  1. Postman 測試不回滾

測試前數據庫結果:
測試前數據庫結果

遇到 IllegalArgumentException 異常數據不會回滾:
數據不回滾,控制檯打印信息

測試後數據庫結果:新增數據,數據不回滾。
新增數據,數據不回滾

源碼下載

https://github.com/turoDog/Demo/tree/master/springboot_transaction_demo

後語

以上爲 SpringBoot 聲明式事務的教程。最後,對 Python 、Java 感興趣請長按二維碼關注一波,我會努力帶給你們價值,如果覺得本文對你哪怕有一丁點幫助,請幫忙點好看,讓更多人知道。

另外,關注之後在發送 1024 可領取免費學習資料。資料內容詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、算法資料分享

一個優秀的廢人

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章