Spring事物入门简介及AOP陷阱分析
转载请注明出处: 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 声明式事务是指依托注解 @Transactional 和 AOP 功能,在其方法两端添加事务的操作,实现对被注解修饰方法的增强。
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陷阱分析的更多相关文章
- Spring事物管理简介 (转)
一.事物1.什么是事物 事物指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败 2.事物的特性 原子性:事物是一个不可分割的工作单位,事物中的操作要么都发生,要么都不发生 一致性:事物前后数据 ...
- Spring的入门学习笔记 (AOP概念及操作+AspectJ)
AOP概念 1.aop:面向切面(方面)编程,扩展功能不通过源代码实现 2.采用横向抽取机制,取代了传统的纵向继承重复代码 AOP原理 假设现有 public class User{ //添加用户方法 ...
- spring boot (入门简介 demo)
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过 ...
- Spring Boot入门简介-Maven配置
一.简介 -- 简化Spring应用开发的一个框架: -- 整个Spring技术栈的一个大整合: -- J2EE开发的一站式解决方案. 二.背景: ① J2EE笨重的开发.繁多的配置.低下的开发效率. ...
- Spring(1) --入门(IoC,AOP)
说说你对spring的理解? Spring框架是一个轻量级的企业级开发的一站式解决方案,所谓一站式解决方案就是可以基于Spring解决Java EE开发的所有问题.Spring框架主要提供了IoC容器 ...
- Spring框架入门之AOP
Spring框架入门之AOP 一.Spring AOP简单介绍 AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented ...
- JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架
1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...
- Spring Boot 入门(五):集成 AOP 进行日志管理
本篇文章是接着 Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理写的,按照前面几篇博客的教程,可以搭建一个简单的项目,主要包含了 Pagehelper+MyBatis 分页 ...
- Spring Boot 入门详细分析
推荐阅读: 我们为什么要学习 Spring Boot 我们搭建 Spring Boot 项目,可以使用 Spring 为我们提供的初始化网站,那个可能不太方便,今天呢,我们就来说说如何使用 IDEA ...
随机推荐
- Cookie、Session、JWT在koa中的应用及实现原理
目录 Cookie 重要属性 实现原理 cookie签名实现原理 注意事项 Session 实现原理 JWT 使用方式 组成 实际应用 实现原理 前端存储方式 cookie session local ...
- 跟Waf斗智斗勇的一天
差点心态爆炸 幸亏整出来了... 最近快放寒假了..临近高考不到最后一星期绝对不学习.. 挖下SRC 这家自带的waf头疼死我了 想几次Fuzz全都撞壁了 然后发现了这家waf的规则 信息搜集不说了 ...
- Linux-shell编程经验记录
Linux-shell编程经验总结 1.接收用户输入 #读取用户输入并且将输入保存到input变量中 read -p "请输入:" input #也可以先输出信息再进行读取,这里的 ...
- python爬虫:了解JS加密爬取网易云音乐
python爬虫:了解JS加密爬取网易云音乐 前言 大家好,我是"持之以恒_liu",之所以起这个名字,就是希望我自己无论做什么事,只要一开始选择了,那么就要坚持到底,不管结果如何 ...
- Java协程编程之Loom项目尝鲜
前提 之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容.Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://ope ...
- 基于RT1052 Aworks 内存扩容记录(一)
本文主要是通过迁移的思维,记录本人初次使用周立功的Aworks框架进行BSP开发 1. 首先阅读原理图 内存容量由32M扩容至64M. 2. 再则比较两颗芯片的参数 通过比较32M和64M SDRAM ...
- Asp.Net Core Razor页面中使用echarts展示图形
Asp.Net Core Razor页面中使用echarts展示图形 要在Razor页面中使用echarts显示图形,主要问题点在于如何将数据传递给js文件. 1,下载安装echarts库文件 首先引 ...
- NOIP 模拟 $19\; \rm u$
题解 \(by\;zj\varphi\) 二维差分的题目 维护两个标记,一个向下传,一个向右下传: 对于每次更新,我们可以直接更新 \((r,c)+s,(r+l,c)-s\) ; \((r,c+1)- ...
- NOIP 模拟 $12\; \text{简单的区间}$
题解 签到题 求区间和为 \(k\) 的倍数的区间,我们可以转化为求左右两个端点,其前缀和相等 对于区间最大值,我们可以把其转化为一个值,它能向左,向右扩展的最远边界,一个单调栈即可 我们设一个值 \ ...
- Charles 抓包 Client SSL handshake failed - Remote host closed connection during handshake
Charles 抓包 https 报错: Client SSL handshake failed - Remote host closed connection during handshake # ...