SpringBoot | 1.4 数据库事务处理

简介: 前面讲解了Sring的AOP,可以知道它是用来抽取公共代码,增强方法的。而在JDBC操作数据库进行数据处理时,有很多重复的公共代码;事务的提交与回滚跟AOP的约定流程很相似。因此,Spring数据库事务编程的思想基于AOP的设计思想,数据库事务处理是AOP的一种典型应用。注:在说明注解时,第一点加粗为注解中文含义,第二点为一般加在哪身上,缩进或代码块为示例,如:**@注解** - **中文含义** - 加在哪 - 其他…… - `语句示例` ```java //代码示例 ```

1. 事务的一些概念

首先我们要对事务常用概念有一个了解。

什么事务:

  • 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
  • 典型场景:银行转账


数据库事务四个特性(ACID):

  • 原子性(业务单元的操作要么全部成功,要么全部失败)
  • 一致性(事务完成时,所有数据保持一致)
  • 隔离性(核心,为了压制丢失更新的产生,处理高并发的关键)
  • 持久性(事务结束后,所有数据固化到一个地方,如:磁盘)


事务的操作方法:

  • 声明式事务管理(注解方式)
  • 编程式事务管理(xml配置)
这里仅讨论声明式事务管理


2. 注解声明式事务管理

Spring AOP的约定,会将我们的代码织入到约定的流程中。基于AOP思想的事务处理,也有这样一个约定,其中最重要的注解是@Transactional

@Transactional

  • 事务性的
  • 可以标注在类和方法上,推荐类上;
  • 该注解可以配置一些属性,如:事务隔离级别、传播行为与异常类型等。Spring IoC容器在加载时将配置信息解析,存到事务定义器TransactionDefinition里,记录哪些类或方法需要采用什么策略去启动事务功能。


Spring数据库事务约定:

20210706205817861.png


具体流程:当事务启动时,Spring会根据事务定义器内的配置设置事务。首先根据传播行为确定事务策略;然后是隔离级别、超越时间、只读等内容设置。直到调用开发者的业务代码,此时若没有异常,Spring数据库拦截器会替我们提交事务;如果发生异常,需要判断事务定义器内配置,若事务定义器约定了该类型异常不回滚,则提交事务;若没有配置或配置回滚,则进行事务回滚并抛出异常。


@Transactional源码

从源码中知可以配置哪些信息:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    //通过bean name制定事务管理器
    @AliasFor("transactionManager")
    String value() default "";

    //同value属性
    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    //制定传播行为(重点)
    Propagation propagation() default Propagation.REQUIRED;

    //制定隔离级别(重点)
    Isolation isolation() default Isolation.DEFAULT;

    //制定超时时间(单位秒)
    int timeout() default -1;

    String timeoutString() default "";

    //是否只读事件
    boolean readOnly() default false;

    //方法在发生指定异常时回滚,默认所有异常回滚
    Class<? extends Throwable>[] rollbackFor() default {};

    //方法在发生指定异常名称时回滚,默认所有异常回滚
    String[] rollbackForClassName() default {};

    //方法在发生指定异常时“不”回滚,默认所有异常回滚
    Class<? extends Throwable>[] noRollbackFor() default {};

    //方法在发生指定异常名称时“不”回滚,默认所有异常回滚
    String[] noRollbackForClassName() default {};
}


3. 隔离级别

从上面分析可知,隔离级别isolation与传播行为propagation是@Transactional注解的两个十分重要的配置项,因此这里单独拿出来讲。

丢失更新:

  • 第一类丢失更新:一个事务回滚,另一个事务提交引发数据不一致。(如今数据库系统已解决)
  • 第二类丢失更新:事务1无法知道事务2存在,按事务1提交结果。(需要设置隔离级别)


三类读的问题:

  • 脏读:一个事务读取另一个事务没有提交的数据;

20210706223715918.png


  • 不可重复读:库存对于事务2而言是个可变化值;不可重复读的是数据库单一记录值。

20210706225036662.png


  • 幻读:幻读的数据不是数据库存储值,是统计值。

20210706230254829.png


四类隔离级别:

用来解决上述三类读问题,隔离级别由高到低分为:未提交读、读写提交、可重复读、串行化。

未提交读:

  • 允许一个事务读取另一个事务没有提交的数据;
  • 优点:并发能力高;
  • 缺点:可能发生脏读;
  • 是最低的隔离级别,一种危险的隔离级别。


读写提交:

  • 一个事务只能读取另一个事务已经提交的数据;
  • 优点:解决脏读问题;
  • 缺点:可能造成不可重复读问题。

20210706225948787.png


克服脏读


可重复度:

  • 克服不可重复读问题;
  • 优点:克服不可重复读问题;
  • 缺点:

20210706230118432.png


克服不可重复读


串行化:

  • 要求所有SQL按顺序执行;
  • 优点:保证数据一致性;
  • 缺点:性能低。


使用合理的隔离级别解决三类读的问题:

2021070623123733.png


使用隔离级别解决三类读问题


考虑性能,在实际中会以读写提交为主,其能防止脏读,不能避免不可重复读与幻读。为了克服数据不一致性与性能问题,可以使用乐观锁或使用Redis作为数据载体。

对于隔离级别,不同数据库支持不同:Oracle支持读写提交和串行化,默认读写提交;MySQL支持4种,默认可重复读。

修改隔离级别的方法:

  • 在@Transactional注解上配置属性;

    @Service
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public class UserService{

20210706232354459.png


修改隔离级别


  • 通过application.properties配置文件配置;
  #隔离级别数字配置含义:
  #-1 数据库默认隔离级别
  #1  未提交读
  #2  读写提交
  #4  可重复读
  #8  串行化
  #tomcat数据源默认隔离级别
  spring.datasource.tomcat.default-transaction-isolation=2
  #dbcp2数据库连接池默认隔离级别
  #spring.datasource.dbcp2.default-transaction-isolation=2


4. 传播行为

传播行为是方法间调用事务采取的策略问题。如在处理批量文件时,大部分成功,小部分失败,我们只希望那小部分失败的回滚。

Spring在Propagation源码中定义了7种传播行为:

public enum Propagation {
    /**
     * 需要事务,默认传播行为,如果当前存在事务,就沿用当前事务,
     * 否则新建一个事务运行子方法
     */
    REQUIRED(0),

    /**
     * 支持事务,如果当前存在事务,就沿用当前事务,
     * 否则继续采用无事务方式运行子方法
     */
    SUPPORTS(1),

    /**
     * 必须使用事务,如果当前存在事务,就沿用当前事务,
     * 如果当前没有事务,则会抛出异常
     */
    MANDATORY(2),

    /**
     * 无论当前事务是否存在,都会创建新事务运行方法,
     * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
     */
    REQUIRES_NEW(3),

    /**
     * 不支持事务,当前存在事务时,将挂起事务,运行方法
     */
    NOT_SUPPORTED(4),

    /**
     * 不支持事务,如果当前存在事务时,则抛出异常,否则继续使用无事务机制运行
     */
    NEVER(5),

    /**
     * 在当前方法调用子方法时,如果子方法发生异常
     * 只回滚子方法执行过的sql,而不回滚当前方法的事务
     */
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

其中,REQUIREDREQUIRES_NEWNESTED三种传播行为最常用。


添加传播行为方法:

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService{

对于NESTED而言,并不是所有数据库支持保存点技术,因此Spring的内部规则是:如果数据库支持保存点技术,就启用保存点技术;反之则新建一个任务去运行子方法,相当于REQUIRES_NEW

NESTEDREQUIRES_NEW的区别是:前者会沿用当前事务的隔离级别和锁等特性,后者拥有自己的隔离级别和锁等特性。


5. @Transactional自调用失效问题

一个类自身方法之间的调用,每次调用不能产生新的事务。

失效原因:
AOP原理是动态代理,而自调用是类自身的调用,不是代理对象去调用,就不会产生AOP,开发者代码无法织入到约定流程中去。


自调用失效问题解决:

  • 用一个service调用另一个service;
  • 从Spring IoC容器中使用applicationContext.getBean方法获取代理对象。


相关文章
|
28天前
|
Java 数据库连接 测试技术
SpringBoot入门 - 添加内存数据库H2
SpringBoot入门 - 添加内存数据库H2
39 3
SpringBoot入门 - 添加内存数据库H2
|
1月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
52 4
SpringBoot入门(4) - 添加内存数据库H2
|
24天前
|
Java 关系型数据库 数据库连接
使用 Spring Boot 执行数据库操作:全面指南
使用 Spring Boot 执行数据库操作:全面指南
84 1
|
2月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
36 2
SpringBoot入门(4) - 添加内存数据库H2
|
1月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
67 13
|
1月前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
47 4
|
1月前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
24 0
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
178 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
116 62
|
9天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
60 13