SpringBoot 实战 (十) | 声明式事务

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 如题,今天介绍 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;
    }
}


代码注释同样很清楚,没啥好说的。


7. 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 方法上打上断点。


测试前数据库结果:


640.png


  1. Postman 测试回滚


640.png


且获得 id 为 1。:


640.jpg


继续执行抛出异常 IllegalArgumentException,将导致数据回滚:


640.png


测试后数据库结果:并无新增数据,回滚成功。


640.png


  1. Postman 测试不回滚


测试前数据库结果:


640.png


遇到 IllegalArgumentException 异常数据不会回滚

640.png

测试后数据库结果:新增数据,数据不回滚。


640.png



源码下载


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


后语


以上为 SpringBoot 声明式事务的教程。最后,对 Python 、Java 感兴趣请长按二维码关注一波,我会努力带给你们价值,如果觉得本文对你哪怕有一丁点帮助,请帮忙点好看,让更多人知道。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
安全 Java 编译器
springboot @resource与private final声明式的有何区别??
【6月更文挑战第3天】在Spring Boot中,@Resource 和 private final 常用于依赖注入,但它们的用途和行为有一些重要的区别。
136 1
|
Java 测试技术 Maven
SpringBoot 3.0 新特性,内置声明式 HTTP 客户端
从 Spring 6 和 Spring Boot 3 开始,Spring 框架支持将远程 HTTP 服务代理成带有特定注解的 Java http interface。类似的库,如 OpenFeign 和 Retrofit 仍然可以使用,但 http interface 为 Spring 框架添加内置支持。
1173 1
SpringBoot 3.0 新特性,内置声明式 HTTP 客户端
|
安全 Java 关系型数据库
Mall电商实战项目全面升级!支持最新版SpringBoot,干掉循环依赖
技术栈升级 mall项目采用现阶主流技术实现,这些主流技术基本都升级了目前最新稳定版,具体升级内容大家可以参考下表。 技术版本说明
|
Java Docker Python
docker部署项目 dockerfile 实战 SpringBoot、flask
作者主页:https://www.couragesteak.com/
docker部署项目 dockerfile 实战 SpringBoot、flask
|
负载均衡 Java API
SpringBoot整合elasticsearch-rest-client实战
SpringBoot整合elasticsearch-rest-client实战
1300 0
SpringBoot整合elasticsearch-rest-client实战
|
JSON Java 测试技术
SpringBoot业务开发 01、Springboot实战:实现Gitee图床上传及删除(含完整代码)
SpringBoot业务开发 01、Springboot实战:实现Gitee图床上传及删除(含完整代码)
SpringBoot业务开发 01、Springboot实战:实现Gitee图床上传及删除(含完整代码)
|
Java 应用服务中间件 容器
springboot原理实战(15)--springboot优化和定制tomcat
springboot原理实战(15)--springboot优化和定制tomcat
536 0
springboot原理实战(15)--springboot优化和定制tomcat
|
Java Spring
springboot原理实战(12)--扫描包,banner,配置属性默认值的3种方式
springboot原理实战(12)--扫描包,banner,配置属性默认值的3种方式
773 0
springboot原理实战(12)--扫描包,banner,配置属性默认值的3种方式
|
Java 容器 Spring
springboot原理实战(11) -- springboot扩展分析CommandLineRunner
springboot原理实战(11) -- springboot扩展分析CommandLineRunner
169 0
springboot原理实战(11) -- springboot扩展分析CommandLineRunner
|
Java Spring 容器
springboot实战原理(10)--配置事件监听的4种方式和原理
springboot实战原理(10)--配置事件监听的4种方式和原理
522 0
springboot实战原理(10)--配置事件监听的4种方式和原理