微信公衆號:一個優秀的廢人
如有問題或建議,請後臺留言,我會盡力解決你的問題。
前言
如題,今天介紹 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 的事務支持
- 自動配置的事務管理器
在使用 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()
}
- 自動開啓註解事務的支持
SpringBoot 專門用於配置事務的類爲 org.springframework.boot.autoconfigure.transcation.TransactionAutoConfiguration,此配置類依賴於 JpaBaseConfiguration 和 DataSourceTransactionManagerAutoConfiguration。
而在 DataSourceTransactionManagerAutoConfiguration 配置裏還開啓了對聲明式事務的支持,代碼如下:
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
@Configuration
@EnableTransactionManagement
protected static class TransactionManagementConfiguration{
}
所以在 SpringBoot 中,無須顯式開啓使用 @EnableTransactionManagement 註解。
實戰
演示如何使用 Transactional 使用異常導致數據回滾與使用異常導致數據不回滾。
- 準備工作:
SpringBoot 2.1.3
JDK 1.8
IDEA
- 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>
代碼註釋很清楚,沒啥好說的。
- 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
- 實體類:
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;
}
- 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> {
}
- 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;
}
}
代碼註釋同樣很清楚,沒啥好說的。
- 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 方法上打上斷點。
測試前數據庫結果:
- Postman 測試回滾
debug 模式下可見數據已保存,且獲得 id 爲 1。:
繼續執行拋出異常 IllegalArgumentException,將導致數據回滾:
測試後數據庫結果:並無新增數據,回滾成功。
- Postman 測試不回滾
測試前數據庫結果:
遇到 IllegalArgumentException 異常數據不會回滾:
測試後數據庫結果:新增數據,數據不回滾。
源碼下載
https://github.com/turoDog/Demo/tree/master/springboot_transaction_demo
後語
以上爲 SpringBoot 聲明式事務的教程。最後,對 Python 、Java 感興趣請長按二維碼關注一波,我會努力帶給你們價值,如果覺得本文對你哪怕有一丁點幫助,請幫忙點好看,讓更多人知道。
另外,關注之後在發送 1024 可領取免費學習資料。資料內容詳情請看這篇舊文:Python、C++、Java、Linux、Go、前端、算法資料分享