转载请注明出处: https://www.cnblogs.com/qnlcy/p/15237377.html

一、事务的定义

事务(Transaction),是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit),是恢复和并发控制的基本单位。

事务的产生,其实是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.

二、事务的属性

事务具有4个属性,简称 ACID

属性 说明
Atomicity 原子性 一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
Consistency 一致性 事务执行的结果必须是使数据库从一个一致性状态c0变到另一个一致性状态c1
Isolation 隔离性 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
Durability 持久性 指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

三、Spring 事务的隔离级别

当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。

在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题

3.1 隔离级别引出的问题

3.1.1 脏读

是指在没有隔离的情况下,一个事务读取了另外一个事务已修改但未提交(有可能回滚也有可能继续修改)的缓冲区数据。

3.1.2 不可重复读

数据库中的某项数据在一个事务多次读取,但是在多次读取期间,其他事务对其有修改并提交,导致返回值不同,这就发生了不可重复读。

不可重复读侧重修改。

3.1.3 幻读

幻读和不可重复读相似。当一个事务(T1)读取几行记录后(事务并没有结束),另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。

幻读侧重新增或者删除。

3.2 隔离级别

在理想状态下,事务之间将完全隔离(即下表中的 Isolation.SERIALIZABLE ),从而可以防止这些问题发生。

然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。

完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。

隔离级别 含义
Isolation.DEFAULT 使用后端数据库默认的隔离级别
Isolation.READ_UNCOMMITTED 允许读取尚未提交的更改。可能导致脏读、幻读或不可重复读。
Isolation.READ_COMMITTED (Oracle 默认级别)允许从已经提交的并发事务读取。可防止脏读,但幻读和不可重复读仍可能会发生。
Isolation.REPEATABLE_READ (MYSQL默认级别)对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻读仍可能发生。
Isolation.SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

四、Spring 事务的传播机制

Spring 事务的传播机制描述了在嵌套事务当中,当前事务与外部事务(最近的那个,有可能没有)的继承关系。

比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。

Spring 事务的传播有如下机制

类型 描述
PROPAGATION_REQUIRED Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
PROPAGATION_REQUES_NEW 该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
PROPAGATION_SUPPORT 如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORT 该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER 该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY 与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED 该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

五、Spring 事务的应用(声明式)

Spring 声明式事务是指依托注解 @TransactionalAOP 功能,在其方法两端添加事务的操作,实现对被注解修饰方法的增强

5.1 事务只读

从事务开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。

事务只读只适用于 当传播机制为 PROPAGATION_REQUIRED,PROPAGATION_REQUES_NEW 的情况

5.1.1 应用场景

在诸如统计查询、报表查询的过程当中,需要多次查询,为了避免在查询过程当中对剩余查询数据的修改,保证数据整体在某一时刻的一致性,需要使用只读事务。

5.1.2 使用方式

@Transactional(propagation = Propagation.REQUIRES, readOnly = true)
public List<Product> findAllProducts() {
return this.productDao.findAllProducts();
}

5.2 事务回滚

在事务注解 @Transactional 中指定了某个异常后,捕获到事务方法抛出了该异常或者其子类异常,会造成事务回滚。默认当捕获到方法抛出的 RuntimeException 异常后,事务就会回滚。还可以设置当出现某异常时候不回滚,即使是运行时异常

5.2.1 使用方式

// 回滚Exception类型异常
@Transactional(rollbackFor = Exception.class)
public void test1() throws Exception {
// ..
} // 回滚自定义类型异常
@Transactional(rollbackForClassName = "org.transaction.demo.CustomException")
public void test2() throws Exception {
// ..
} // 不回滚自定义类型异常
@Transactional(noRollbackFor = CustomException.class)
public void test3() throws Exception {
// ..
}

5.3 事务超时

如果一个事务长时间占用数据库连接,会导致服务等待从而引起服务雪崩效应,所以设置一个合理的超时时间,是必要的。默认不超时。事务超时会引起事务回滚。

事务超时只适用于 当传播机制为 PROPAGATION_REQUIRED,PROPAGATION_REQUES_NEW 的情况

5.3.1 使用方式

//设置事务超时时间,单位秒
@Transactional(timeout = 5)
public void test() {
// ..
}

5.4 事务传播机制的使用方式

//每次外层事务调用都会开启一个新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test() {
// ..
}

5.5 事务隔离机制的使用方式

指定事务隔离机制只适用于 当传播机制为 PROPAGATION_REQUIRED,PROPAGATION_REQUES_NEW 的情况

//设置事务隔离级别为串行
@Transactional(isolation = Isolation.SERIALIZABLE))
public void test() {
// ..
}

六、Spring 声明式事务的 AOP 陷阱

总所周知,声明式事务依托 AOP 功能实现对事务方法的增强,而 AOP 底层则是代理,存在代理陷阱。

6.1 AOP 代理陷阱复现

    @Transactional(rollbackFor = RuntimeException.class)
public void insertUser(User user) {
userMapper.insertUser(user);
throw new RuntimeException("");
} /**
* 内部调用新增方法
*/
public void insertMale(User user) {
user.setGender("male");
this.insertUser(user);
}

当外部方法直接调用 insertMale(user) 的时候,事务并不会生效。

6.2 原因分析

AOP使用的是动态代理的机制,它会给类生成一个代理类,事务的相关操作都在代理类上完成。内部调用使用的是实例调用,并没有通过代理类调用方法,所以会导致事务失效。

6.2.1 伪代码

  • 代理类
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理之前做增强
System.out.println("代理之前...");
//根据需要添加事务处理逻辑
...
//调用原有方法 insertMale
Object obj = method.invoke(object, args);
//做增强
System.out.println("代理之后...");
//根据需要添加事务处理逻辑
...
return obj;
}

当执行 insertMale() 方法时,因为没有事务注解,所以没有添加事务处理逻辑,所以直接调用了目标类的 insertMale() 方法。

  • 目标类执行情况
    public void insertMale(User user) {
user.setGender("male");
//这里的 this 指向了目标类而不是代理类
//所以及时下面的方法添加了事务注解,但是并没有除法增强实现,事务也还是不生效的
this.insertUser(user);
}

6.3 解决方案

6.3.1 注入自身

利用Spring可以循环依赖来解决问题

@Service
public class TestService {
@Autowired
private TestService testService; @Transactional(rollbackFor = RuntimeException.class)
public void insertUser(User user) {
userMapper.insertUser(user);
throw new RuntimeException("");
} /**
* 内部调用新增方法
*/
public void insertMale(User user) {
user.setGender("male");
//这里使用 字段 testService 调用事务方法
testService.insertUser(user);
}
}

6.3.2 使用 ApplicationContext 获取目标类

注入 Spring 上下文 ApplicationContex, 然后获取到 目标 bean, 再调用事务方法

@Service
public class TestService {
@Autowired
private ApplicationContext applicationContext; @Transactional(rollbackFor = RuntimeException.class)
public void insertUser(User user) {
userMapper.insertUser(user);
throw new RuntimeException("");
} /**
* 内部调用新增方法
*/
public void insertMale(User user) {
user.setGender("male");
//这里使用上下文获取目标类实例
TestService testService = applicationContext.getBean(TestService.class);
testService.insertUser(user);
}
}

6.3.3 使用 AopContext

Aop 上下文采用 ThreadLocal 保存了代理对象,可以使用 Aop 上下文来进行目标方法的调用。

使用时候要在启动类上添加 exposeProxy = true 配置

  • 配置
@SpringBootApplication
//配置:导出代理对象到AOP上下文
@EnableAspectJAutoProxy(exposeProxy = true)
public class DemoApplication {
}
  • 使用
public class TestService {

    @Transactional(rollbackFor = RuntimeException.class)
public void insertUser(User user) {
userMapper.insertUser(user);
throw new RuntimeException("");
} /**
* 内部调用新增方法
*/
public void insertMale(User user) {
user.setGender("male");
//使用AOP上下文获取目标代理类
TestService testService = (TestService) AopContext.currentProxy();
testService.insertUser(user);
}
}

Spring事物入门简介及AOP陷阱分析的更多相关文章

  1. Spring事物管理简介 (转)

    一.事物1.什么是事物 事物指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败 2.事物的特性 原子性:事物是一个不可分割的工作单位,事物中的操作要么都发生,要么都不发生 一致性:事物前后数据 ...

  2. Spring的入门学习笔记 (AOP概念及操作+AspectJ)

    AOP概念 1.aop:面向切面(方面)编程,扩展功能不通过源代码实现 2.采用横向抽取机制,取代了传统的纵向继承重复代码 AOP原理 假设现有 public class User{ //添加用户方法 ...

  3. spring boot (入门简介 demo)

    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...

  4. Spring Boot入门简介-Maven配置

    一.简介 -- 简化Spring应用开发的一个框架: -- 整个Spring技术栈的一个大整合: -- J2EE开发的一站式解决方案. 二.背景: ① J2EE笨重的开发.繁多的配置.低下的开发效率. ...

  5. Spring(1) --入门(IoC,AOP)

    说说你对spring的理解? Spring框架是一个轻量级的企业级开发的一站式解决方案,所谓一站式解决方案就是可以基于Spring解决Java EE开发的所有问题.Spring框架主要提供了IoC容器 ...

  6. Spring框架入门之AOP

    Spring框架入门之AOP 一.Spring AOP简单介绍 AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented ...

  7. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  8. Spring Boot 入门(五):集成 AOP 进行日志管理

    本篇文章是接着 Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理写的,按照前面几篇博客的教程,可以搭建一个简单的项目,主要包含了 Pagehelper+MyBatis 分页 ...

  9. Spring Boot 入门详细分析

    推荐阅读: 我们为什么要学习 Spring Boot 我们搭建 Spring Boot 项目,可以使用 Spring 为我们提供的初始化网站,那个可能不太方便,今天呢,我们就来说说如何使用 IDEA ...

随机推荐

  1. 关于intouch/ifix嵌入视频控件并使用(海康,大华)

    2017年下半年项目开始接触利用intouch工控软件来进行项目二次开发.其中关于驱动的问题始终是上位机的重中之重,暂且不表(嘿嘿--),首先遇到的问题就是在弹窗中嵌入视频控件,监控设备的开停状态.经 ...

  2. PASS-单组目标值法的样本量计算

    临床试验的参数估计中,评价指标有确定的估计目标 ,临床试验目的需通过参数估计(含相应的可信区间估计)的方法证明评价指标不低于目标值时,可根据单组目标值法样本量公式计算. 例:欲证明器械A的诊断准确性非 ...

  3. 大数据开发-Go-新手常遇问题

    真正在工作中用Go的时间不久,所以也作为新手,总结了一些常见的问题和坑 Go 中指针使用注意点 // 1.空指针反向引用不合法 package main func main() { var p *in ...

  4. 单机版搭建kubernetes(K8s)

    准备 云原生的概念越来越火,忍不住去看了看kubernetes,初次接触,晕晕乎乎的,于是不管三七二十一,先搭建个单机版的再说(没钱买服务器,目前也懒得装虚拟机),跑起来也算是第一步吧.网上教程一顿搜 ...

  5. [数据结构]ODT(珂朵莉树)实现及其应用,带图

    [数据结构]ODT(珂朵莉树)实现及其应用,带图 本文只发布于博客园,其他地方若出现本文均是盗版 算法引入 需要一种这样的数据结构,需要支持区间的修改,区间不同值的分别操作. 一般的,我们会想到用线段 ...

  6. Servlet基本知识

    Servlet基本知识 1.IDEA创建第一个Servlet程序xing 这里说明如何使用 IDEA Ultimate 2020.1.3版本来新建第一个web程序.参考 MoonChasing 1.1 ...

  7. 【Java】jeesite使用学习

    初始配置环境及软件: 名称 版本 作用 Tomcat 7.0 微小型服务器,版本无所谓,装个Tomcat 9估计也没事 IntelliJ IDEA 2021.1.3 x64 2021.1.3 编译器, ...

  8. Windows下安装RocketMQ

    目录 前言 环境 具体操作 下载 环境变量配置 启动 关闭 生产.消费实例 RocketMQ Console 前言 项目中用到了延迟消息队列,记录下一win10下rocketmq的安装 环境 win1 ...

  9. centos8安装mysql8.0

    官网下载rpm地址 https://dev.mysql.com/downloads/repo/yum/ wget下载 wget https://repo.mysql.com//mysql80-comm ...

  10. mac Charles抓包

    手机配置http代理 1.配置iPhone或Android 的wifi配置.首先保证Mac电脑和手机是在同一个局域网内. 2.设置手机wifi配置,在HTTP代理中选择手选代理,服务器填写Mac的IP ...