前言

前面讲解了Sring的AOP,可以知道它是用来抽取公共代码,增强方法的。而在JDBC操作数据库进行数据处理时,有很多重复的公共代码;事务的提交与回滚跟AOP的约定流程很相似。因此,Spring数据库事务编程的思想基于AOP的设计思想,数据库事务处理是AOP的一种典型应用。


1. 事务的一些概念

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

什么事务:

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

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

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

事务的操作方法:

  • 声明式事务管理(注解方式)
  • 编程式事务管理(xml配置)

这里仅讨论声明式事务管理

2. 注解声明式事务管理

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

@Transactional

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

Spring数据库事务约定:

具体流程:当事务启动时,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提交结果。(需要设置隔离级别)

三类读的问题:

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

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

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

四类隔离级别:

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

未提交读:

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

读写提交:

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

克服脏读

可重复度:

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

克服不可重复读

串行化:

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

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

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

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

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

修改隔离级别的方法:

  • 在@Transactional注解上配置属性;
    @Service
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public class UserService{

修改隔离级别

  • 通过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方法获取代理对象。

最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

SpringBoot | 1.4 数据库事务处理的更多相关文章

  1. Atitti 数据库事务处理 attilax总结

    Atitti 数据库事务处理 attilax总结 1.1. 为什么要传递Connection?1 1.2. 两种事务处理方式,一种是编程式事务处理;一种是声明...2 1.3. 事务隔离级别 2 1. ...

  2. 十三、EnterpriseFrameWork框架核心类库之数据库操作(多数据库事务处理)

    本章介绍框架中封装的数据库操作的一些功能,在实现的过程中费了不少心思,针对不同数据库的操作(SQLServer.Oracle.DB2)这方面还是比较简单的,用工厂模式就能很好解决,反而是在多数据库同时 ...

  3. SpringBoot使用Druid数据库加密链接完整方案

    网上的坑 springboot 使用 Druid 数据库加密链接方案,不建议采用网上的一篇文章<springboot 结合 Druid 加密数据库密码遇到的坑!>介绍的方式来进行加密链接实 ...

  4. springBoot 集成Mysql数据库

    springBoot 集成Mysql数据库 前一段时间,我们大体介绍过SpringBoot,想必大家还有依稀的印象.我们先来回顾一下:SpringBoot是目前java世界最流行的一个企业级解决方案框 ...

  5. spring security关闭http验证 和 springboot 使用h2数据库

    spring security关闭http验证 最近在跑demo的过程中,访问swagger页面的时候需要验证登录,记得在之前写的代码中是关闭了security验证,无需登录成功访问,直接在appli ...

  6. java中的数据库事务处理

    /*java使用事务处理,首先要求数据库支持事务,如使用MYSQL的事务功能,就要求mysql的表类型为Innodb,*/ /*InnoDB,是MySQL的数据库引擎之一 与传统的ISAM与MyISA ...

  7. 补习系列(18)-springboot H2 迷你数据库

    目录 关于 H2 一.H2 用作本地数据库 1. 引入依赖: 2. 配置文件 3. 样例数据 二.H2 用于单元测试 1. 依赖包 2. 测试配置 3. 测试代码 小结 关于 H2 H2 数据库是一个 ...

  8. MySQL数据库----事务处理

    事物处理  一. 什么是事务    一组sql语句批量执行,要么全部执行成功,要么全部执行失败 二.为什么出现这种技术 为什么要使用事务这个技术呢? 现在的很多软件都是多用户,多程序,多线程的,对同一 ...

  9. SpringBoot入门 (六) 数据库访问之Mybatis

    本文记录学习在SpringBoot中使用Mybatis. 一 什么是Mybatis MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 ...

随机推荐

  1. python基础之pip、.pyc、三元运算、进制、一切皆对象、可变与不可变类型

    一.pip(下载工具==yum) 1.重点(必须掌握的) 列出已安装的包 pip list 安装要安装的包 pip install xxx 安装特定版本 pip install django==1.1 ...

  2. PID参数

    大家奉上一篇关于PID算法及参数整定的知识! 1.位置表达式 位置式表达式是指任一时刻PID控制器输出的调节量的表达式. PID控制的表达式为 式中的y(t)为时刻t控制器输出的控制量,式中的y(0) ...

  3. .NET6系列:Visual Studio 2022 线路图

    系列目录     [已更新最新开发文章,点击查看详细] 在上一篇博客<Visual Studio 2022>中介绍了VS2022的性能改进与重要功能.本文主要介绍在 Visual Stud ...

  4. GO学习-(16) Go语言基础之文件操作

    Go语言文件操作 本文主要介绍了Go语言中文件读写的相关操作. 文件是什么? 计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件. 打开和关闭文件 os.Open( ...

  5. GO学习-(11) Go语言基础之map

    Go语言基础之map Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现. map map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能 ...

  6. jupyter notebook 默认文件路径修改以及启动

    其实这个方法有时候不是特别有效额 方法一: 查了网上好多其他的方法,但是都没用,只好独辟蹊径了. 首先找到anaconda的安装路径,找到jupyter notebook,我的是如下: 发送快捷方式到 ...

  7. ZooKeeper学习笔记二:API基本使用

    Grey ZooKeeper学习笔记二:API基本使用 准备工作 搭建一个zk集群,参考ZooKeeper学习笔记一:集群搭建. 确保项目可以访问集群的每个节点 新建一个基于jdk1.8的maven项 ...

  8. 巧用Reflections库实现包扫描

    1.需求 需要扫描某个包中的某个接口的实现类的需求 2.maven 依赖引入 <dependency> <groupId>org.reflections</groupId ...

  9. A,B,C,D分别为不同的整数,满足以下乘法公式,求A,B,C,D的值

    问题:A,B,C,D分别为不同的整数,满足以下乘法公式,求A,B,C,D的值 解题思路: 由题意可知A,B,C,D为不同的整数,则A!=B,A!=C,A!=D,B!=C,B!=D,C!=D 再由给出公 ...

  10. JVM集合之开篇点题

    大家在平时的开发过程中是否遇到过StackOverflowError.OutOfMemoryError等类似的内存溢出错误呢?大家又是怎么解决这个问题的?再来,大家在面试过程中有没有被面试官提问过jv ...