那么不知道你 对于Spring支持的常用数据库事务传播属性和隔离级别了解得怎么样呢?要不要一起复习复习了:grin:
很喜欢一句话:“八小时内谋生活,八小时外谋发展”
共勉:woman: :computer:
描述:进来先看看风景啦,要相信会有光的哦
对于数据库事务ACID(原子性、一致性、隔离性、持久性)性质我想大家都是知道的,这里就不写了:grin:
我们都知道用事务是为了保证数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
但是如果一个方法嵌套关联着其他方法了,这该怎么算呢?当前方法及关联方法都有事务呢,或者只是其中某几个有事务,该用谁的呢?
事务的传播行为:一个方法运行在一个开启了事务的方法上时,当前方法是使用原来的事务还是开启一个新的事务。
通过 @Transaction注解中propagation来设置事务传播行为。其中
事务传播行为总共有以下七种:
下面写了一个小demo来让理解更加快捷一些哈。
注意:account表中 balance字段是设置为无符号的(即不能为负数)。
项目就是普通Spring项目
模拟的是买书的一个过程,账户余额不足,但是一次买多本的情况,一起付款。
在其中再测试事务传播行为的不同,来看数据的变化。
初始代码:
mapper层代码
测试一:默认事务传播行为
我们在 void checkout(int userId, List isbns) 和void purchase(int userId, int isbn)上都加了@Transactional
目前账户为 100元,两本书的价格分别为 60和50 ,因为我们的付款过程是 使用循环 购买的,你说我们会买到一本还是一本都买不到呢?
答案当然是一本都买不到,因为 @Transactional注解 ,默认事务的传播属性是:REQUIRED,即业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。所以实际上void purchase(int userId, int isbn)其实和调用它的方法用的同一个事务。简单画个图:
测试二:测试 -->REQUIRES_NEW属性
其他代码未改变,仅在 purchase上的注解上加了点东西@Transactional(propagation = Propagation.REQUIRES_NEW).
REQUIRES_NEW: 不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
你说说答案和上面是一样的么?:grinning:
答案是不一样的, 测试一 我们实际上用的就是checkout上的事务,并没有用到 purchase 的事务,从图上也能看出来。
测试二它的事务传播属性 用 图来讲是这样的啦:
所以是可以买到一本书的。
还有很多,意思都解释过了,没有一一测完了。
假设现在有A和B 两个事务 并发执行。
1)脏读:一个事务读取到另一事务未提交的更新新据
2)不可重复读: 同一事务中,多次读取同一数据返回的结果有所不同(针对的update *** 作)
3)幻读:一个事务读取到另一事务已提交的insert数据(针对的insert *** 作)
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题.
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱
在代码中,我们可以通过
数据库提供了4种隔离级别:
注: 模拟并发情况。
1) 测试一下 mysql 的默认隔离级别:
测试代码特别简单,但因为我是手动模拟,得打断点、debug启动,
当执行完第一个 double bookPrice = bookShopMapper.getBookPriceByIsbn(isbn)语句时,应该去mysql 修改一下书的价格,这样看一下结果。
这个时候再接着执行。看输出什么。
最后的结果仍然是50、50。因为mysql的默认事务隔级别是可重复读,意思在这同一个事务中,可以重复读。
注:因为这是直接修改数据库,其 *** 作行为并不可取,此处只是为了模拟。其结果有时也非一定准确。
在使用Spring时 大部分会用到他的声明式事务 简单的在配置文件中进行一些规则配置 利用Spring的AOP功能就能轻松搞定事务问题 这里面就涉及到一个事务的传播属性问题Propagation 它在TransactionDefinition接口中定义 以供PlatfromTransactionManager使用 PlatfromTransactionManager是spring事务管理的核心接口
TransactionDefinition
public interface TransactionDefinition {
int getPropagationBehavior()
int getIsolationLevel()
int getTimeout()
boolean isReadOnly()
}
getTimeout()方法 它返回事务必须在多少秒内完成
isReadOnly() 事务是否只读 事务管理器能够根据这个返回值进行优化 确保事务是只读的
getIsolationLevel()方法返回事务的隔离级别 事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
在TransactionDefinition接口中定义了五个不同的事务隔离级别 ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别 使用数据库默认的事务隔离级别 另外四个与JDBC的隔离级别相对应 ISOLATION_READ_UNMITTED 这是事务最低的隔离级别 它充许别外一个事务可以看到这个事务未提交的数据 这种隔离级别会产生脏读 不可重复读和幻像读
在TransactionDefinition接口 *** 有 种选项可用
PROPAGATION_REQUIRED 支持当前事务 如果当前没有事务 就新建一个事务 这是最常见的选择
PROPAGATION_SUPPORTS 支持当前事务 如果当前没有事务 就以非事务方式执行
PROPAGATION_MANDATORY 支持当前事务 如果当前没有事务 就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务 如果当前存在事务 把当前事务挂起
PROPAGATION_NOT_SUPPORTED 以非事务方式执行 *** 作 如果当前存在事务 就把当前事务挂起
PROPAGATION_NEVER 以非事务方式执行 如果当前存在事务 则抛出异常
PROPAGATION_NESTED 支持当前事务 新增Savepoint点 与当前事务同步提交或回滚
现在结合一个实例 应用以上各种传播属性来进行说明 首先声明两个bean ServiceA和ServiceB 其中ServiceB被引用
ServiceA {
void methodA() {
thodB()
}
}
ServiceB {
void methodB() {
}
}
接下来 我们就一一分析下
PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里 那么就起一个新的事务 比如说 thodB的事务级别定义为PROPAGATION_REQUIRED 那么由于执行thodA的时候 thodA已经起了事务 这时调用thodB thodB看到自己已经运行在thodA 的事务内部 就不再起新的事务 而假如thodA运行的时候发现自己没有在事务中 他就会为自己分配一个事务 这样 在thodA或者在thodB内的任何地方出现异常 事务都会被回滚 即使thodB的事务已经被 提交 但是thodA在接下来fail要回滚 thodB也要回滚
PROPAGATION_SUPPORTS
如果当前在事务中 即以事务的形式运行 如果当前不再一个事务中 那么就以非事务的形式运行
PROPAGATION_MANDATORY
必须在一个事务中运行 也就是说 他只能被一个父事务调用 否则 他就要抛出异常
PROPAGATION_REQUIRES_NEW
比如我们设计thodA的事务级别为PROPAGATION_REQUIRED thodB的事务级别为PROPAGATION_REQUIRES_NEW 那么当执行到thodB的时候 thodA所在的事务就会挂起 thodB会起一个新的事务 等待thodB的事务完成以后 他才继续执行 他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了 因为thodB是新起一个事务 那么就是存在 两个不同的事务 如果thodB已经提交 那么thodA失败回滚 thodB是不会回滚的 如果thodB失败回滚 如果他抛出的异常被thodA捕获 thodA事务仍然可能提交
PROPAGATION_NOT_SUPPORTED
当前不支持事务 比如thodA的事务级别是PROPAGATION_REQUIRED 而thodB的事务级别是PROPAGATION_NOT_SUPPORTED 那么当执行到thodB时 thodA的事务挂起 而他以非事务的状态运行完 再继续thodA的事务
PROPAGATION_NEVER
不能在事务中运行 假设thodA的事务级别是PROPAGATION_REQUIRED 而thodB的事务级别是PROPAGATION_NEVER 那么thodB就要抛出异常了
PROPAGATION_NESTED
理解Nested的关键是savepoint 他与PROPAGATION_REQUIRES_NEW的区别是 PROPAGATION_REQUIRES_NEW另起一个事务 将会与他的父事务相互独立 而Nested的事务和他的父事务是相依的 他的提交是要等和他的父事务一块提交的 也就是说 如果父事务最后回滚 他也要回滚的 而Nested事务的好处也是他有一个savepoint
ServiceA {
void methodA() {
try {
thodB()
} catch (Exception e) {
thodC()
}
}
}
lishixinzhi/Article/program/Java/ky/201311/28910
title: spring——AOP与事务.md
date: 2020-07-14 13:10:16
categories: [Spring]
tags: [AOP,事务]
toc: true
先列出源码中比较重点的几个类:
1、<aop:before method="before" pointcut-ref="myMethods"/>包装成一个advisor
2、AspectJAwareAdvisorAutoProxyCreator,当实例化所有bean都会执行到AspectJAwareAdvisorAutoProxyCreator类
它会检测bean是否advisor以及advice存在,如果有就说明这个bean有切面,有切面那么就会生成代理
3、jdk的代理,bean里面的所有advisor加入到proxyFactory。
4、jdkDynamicProxy invoke,拿到bean里面的所有Interceptor,会循环proxyFactory里面的所有advisor
里面有advice,里面的advice有两种类型,要么是advice,要么是MethodInterceptor类型的
5、当代理对象调用方式,是一个MethodInterceptor类型的类的链式调用过程,直到容器的大小和索引一致的时候调用JoinPoint目标方法
before:this.advice.before(),invocation.processd()
装配参数,切面里面before方法的method对象,method.getParamterTypes()[0]
最终会把advice封装成MethodInterceptor类型的对象
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。
每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责切点所设定的查询条件,找到对应的连接点。其实确切地说,不能称之为查询连接点,因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。
增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。
增强逻辑的织入目标类。如果没有AOP,目标业务类需要自己实现所有逻辑,而在AOP的帮助下,目标业务类只实现那些非横切逻辑的程序逻辑,而性能监视和事务管理等这些横切逻辑则可以使用AOP动态织入到特定的连接点上。
引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入是将增强添加对目标类具体连接点上的过程。AOP像一台织布机,将目标类、增强或引介通过AOP这台织布机天衣无缝地编织到一起。根据不同的实现技术,AOP有三种织入的方式:
a、编译期织入,这要求使用特殊的Java编译器。
b、类装载期织入,这要求使用特殊的类装载器。
c、动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。
切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。
advisor: pointCut advice
一类功能的增强
around方法里面代码切面
事务切面
缓存切面
日志切面
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。是数据库 *** 作的最小工作单元,是作为单个逻辑工作单元执行的一系列 *** 作;这些 *** 作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的 *** 作集合(工作逻辑单元)。
大致流程形如
数据库事务拥有几大特性:
事务的四大特性:
事务是数据库的逻辑工作单位,事务中包含的各 *** 作要么都做,要么都不做
事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
一个事务的执行不能其它事务干扰。即一个事务内部的 *** 作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它 *** 作或故障不应该对其执行结果有任何影响。
个人理解,事务在Spring中是借助AOP技术来实现的,可以作为AOP中的一个事务切面。spring源码对事务的处理逻辑,自己研究吧!
ORM框架中以Mybatis为例,事务处理就是用到了一个类Transaction,部分源码如下
可以看出Transaction管理的就是一个connection,而connection我们很清楚是与用户会话挂钩的。
那么关系就是Transaction管理Connection ,而connection与 用户session一对一存在。
在springBoot中,只需要加入POM就可以了,配合注解使用即可。
接下来就是事务的控制了。
首先事务有几大传播属性:
其中最常见的,用得最多就 PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、 PROPAGATION_NESTED 这三种。事务的传播属性是 spring 特有的,是 spring 用来控制方法事务的一种手段,说直白点就是用来控制方法是否使用同一事务的一种属性,以及按照什么规则回滚的一种手段。
下面用代码演示这三种属性的机制:
事务的默认属性就是required,通过Transactional.java中的Propagation propagation() default Propagation.REQUIRED 可以看出。
这种情况就是事务1,事务2 都加入到了事务0中。不管是1,2哪个事务抛出异常,事务0都会回滚。数据添加会失败。
这种情况就是:
事务0(required) {
事务1 (REQUIRES_NEW)
事务2
}
此时。
情况a:
1、如果只是事务2出现了异常,那么事务1会提交,事务2加入到事务0中会回滚。
2、如果只是事务1出现了异常,那么事务1会回滚,向上层事务0抛异常,事务2会加入到事务0中,这时都会回滚。
情况b:
如果事务1,事务2都是REQUIRES_NEW传播属性。那么结果就是:
1、如果事务1,抛出了异常,那么事务2是不会执行的,那么事务0必然回滚。
2、如果事务2,抛出异常,那么事务1会提交,表中会有数据。事务2有异常回滚并抛出,事务0回滚。
NESTED属性其实就是创建了回滚点,有异常时,会回滚到指定的回滚点。
在这通过代码测试,出现一种情况是,无论事务1,事务2哪个有异常,数据都不会插入成功,原因是,不论是事务1还是事务2都会向事务0抛出异常,事务0捕获到异常后,执行rollback()方法,这就 *** 作成了,事务的全部回滚。
如果想要事务1和事务2 想要根据自己的回滚点回滚,那么事务0必须自己处理异常,不让spring捕获到这个异常,那么就满足了。把代码改成这种:
Jack大佬提供了,伪代码分析法。
按照Spring源码的事务处理逻辑,伪代码大致为:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)