Spring Boot2 系列教程 (十) | 实现声明式事务

前言

如题,今天介绍 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
  2. 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 测试回滚

    debug 模式下可见数据已保存,且获得 id 为 1。:

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

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

  1. Postman 测试不回滚

测试前数据库结果:

遇到 IllegalArgumentException 异常数据不会回滚:

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

源码下载

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

最后

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「一个优秀的废人」,关注后回复「1024」送你一套完整的 java 教程。


原文地址:https://www.cnblogs.com/nasus/p/12149755.html

时间: 2024-08-29 20:18:01

Spring Boot2 系列教程 (十) | 实现声明式事务的相关文章

Spring Boot2 系列教程 (十二) | 整合 thymeleaf

前言 如题,今天介绍 Thymeleaf ,并整合 Thymeleaf 开发一个简陋版的学生信息管理系统. SpringBoot 提供了大量模板引擎,包含 Freemarker.Groovy.Thymeleaf.Velocity 以及 Mustache,SpringBoot 中推荐使用 Thymeleaf 作为模板引擎,因为 Thymeleaf 提供了完美的 SpringMVC 支持.Thymeleaf 是新一代 Java 模板引擎,在 Spring 4 后推荐使用. 什么是模板引擎? Thym

Spring Boot2 系列教程 (十四) | 统一异常处理

如题,今天介绍 SpringBoot 是如何统一处理全局异常的.SpringBoot 中的全局异常处理主要起作用的两个注解是 @ControllerAdvice 和 @ExceptionHandler ,其中 @ControllerAdvice 是组件注解,添加了这个注解的类能够拦截 Controller 的请求,而 ExceptionHandler 注解可以设置全局处理控制里的异常类型来拦截要处理的异常. 比如:@ExceptionHandler(value = NullPointExcept

Spring Boot2 系列教程 (十八) | 整合 MongoDB

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 如题,今天介绍下 SpringBoot 是如何整合 MongoDB 的. MongoDB 简介 MongoDB 是由 C++ 编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,它将数据存储为一个文档,数据结构由键值 (key=>value) 对组成.MongoDB 文档类似于 JSON 对象.字段值可以包含其他文档,数组及文档数组,非常灵活.存储结构如下: { "studentId": &qu

Spring Boot2 系列教程(十五)定义系统启动任务的两种方式

在 Servlet/Jsp 项目中,如果涉及到系统任务,例如在项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web基础中的三大组件( Servlet.Filter.Listener )之一 Listener ,这种情况下,一般定义一个 ServletContextListener,然后就可以监听到项目启动和销毁,进而做出相应的数据初始化和销毁操作,例如下面这样: public class MyListener implements

Spring Boot2 系列教程 (十五) | 服务端参数校验之一

估计很多朋友都认为参数校验是客户端的职责,不关服务端的事.其实这是错误的,学过 Web 安全的都知道,客户端的验证只是第一道关卡.它的参数验证并不是安全的,一旦被有心人抓到可乘之机,他就可以有各种方法来摸拟系统的 Http 请求,访问数据库的关键数据.轻则导致服务器宕机,重则泄露数据.所以,这时就需要设置第二道关卡,服务端验证了. 老项目的服务端校验 @RestController @RequestMapping("/student") public class ValidateOne

Spring boot 入门五:springboot 开启声明式事务

springboot开启事务很简单,只需要一个注解@Transactional 就可以了.因为在springboot中已经默认对jpa.jdbc.mybatis开启了事务.这里以spring整合mybatis为例讲解声明式事务 数据源配置 这里的配置都基于前几讲的配置,之前spring整合mybatis 是基于注解实现CRUD操作,这次基于xml的配置文件.spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.dataso

Spring(四)-- JdbcTemplate、声明式事务

1.Spring提供的一个操作数据库的技术JdbcTemplate,是对Jdbc的封装.语法风格非常接近DBUtils. JdbcTemplate可以直接操作数据库,加快效率,而且学这个JdbcTemplate也是为声明式事务做准备,毕竟要对数据库中的数据进行操纵! JdbcTemplate中并没有提供一级缓存,以及类与类之间的关联关系!就像是spring提供的一个DBUtils. Spring对数据库的操作使用JdbcTemplate来封装JDBC,结合Spring的注入特性可以很方便的实现对

Spring的Service调用本类方法声明式事务无效的解决方案

示例: class Test{ public void a(){ b(); } @Transactional pubic void b(){} } // 此时b方法注解声明式事务是无效的 解决方案: 通过本类实例来调用,代码如下 class Test{ @Autowire private ApplicationContext context; private Test self; @PostConstruct private void setSelf(){ self = context.getB

Spring Boot2 系列教程(二十) | SpringBoot 是如何实现日志的?

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 休息日闲着无聊看了下 SpringBoot 中的日志实现,把我的理解跟大家说下. 门面模式 说到日志框架不得不说门面模式.门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用.用一张图来表示门面模式的结构为: 简单来说,该模式就是把一些复杂的流程封装成一个接口供给外部用户更简单的使用.这个模式中,设计到3个角色. 1).门面角色:外观模式的核心.它被客户角色调用,它熟悉子系统的功能.