什么是事务?
概念
事务定义
事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。
事务特点
原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
一致性:数据不会因为事务的执行而遭到破坏
隔离性:一个事务的执行,不受其他事务的干扰,即并发执行的事务之间互不干扰
持久性:一个事务一旦提交,它对数据库的改变就是永久的。
原文链接:https://blog.csdn.net/agonie201218/article/details/136189743
不同事务隔离级别下会有什么问题?
事务并发可能产生那些问题?
1 脏读:一个事务读到了另一个事务未提交的数据
2 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发事务中的多次查询结果不一致,数据中的值不一致。
3 虚读 /幻读:一个事务读到了另一个事务已经插入(insert)的数据。导致事务中多次查询的结果不一致,比如一个事务向表中插入了一条数据,这个时候另一个事务读取到了这条数据这就是幻读,实际上不应该读到这条数据。
4 丢失更新,举个例子比如事务T1,T2都读取了表中的某一行数据,事务T1对一个表的数据做了更新更为值A,事务T1提交以后,T2也对这个数据进行了修改改为B并提交,这个时候在事务T1里面查询这个数据得到的值是B,T1对数据的修改A被丢失了。
导致这个问题的根本原因就是并发问题,这两个事务可以同时对这个数据进行修改
事务的隔离级别
1 read uncommitted 读未提交【RU】,一个事务读到另一个事务没有提交的数据
存在:3个问题(脏读、不可重复读、幻读)。
2 read committed 读已提交【RC】,一个事务读到另一个事务已经提交的数据
存在:2个问题(不可重复读、幻读)。
解决:1个问题(脏读)
3 repeatable read:可重复读【RR】,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交
解决:3个问题(脏读、不可重复读、幻读)Mysql默认的隔离级别
4 serializable 串行化,同时只能执行一个事务,相当于事务中的单线程
解决:3个问题(脏读、不可重复读、幻读)
怎么使用Spring 事务?事务怎么传播?
直接看这篇文章,写的很好:https://blog.csdn.net/agonie201218/article/details/136189743
事务什么场景下会失效?
可以看这篇文章:
https://blog.csdn.net/mccand1234/article/details/124571619
这里直对3进行补充说明:
场景1:
/**
* 本类的方法申明事务 而被调用的本类方法也申明了事务 会不会生效?
* 结果为: 结果为生效 事务进行了回滚
* 特别强调: 调用本地方法呐
*/
@Transactional(rollbackFor = RuntimeException.class)
public void testRollBack6() {
this.testRollBackLocal();
throw new RuntimeException();
}
@Transactional(rollbackFor = RuntimeException.class)
public void testRollBackLocal() {
int i = this.spuMapper.deleteById("10000017243800");
throw new RuntimeException();
}
场景2:
/**
* 本类的方法申明事务 而被调用的本类方法没有申明事务 会不会生效?
* 结果为: 结果为生效 事务进行了回滚
* 特别强调: 调用本地方法呐
*/
@Transactional(rollbackFor = RuntimeException.class)
public void testRollBack6() {
this.testRollBackLocal();
throw new RuntimeException();
}
public void testRollBackLocal() {
int i = this.spuMapper.deleteById("10000017243800");
throw new RuntimeException();
}
参考文章:
https://blog.csdn.net/agonie201218/article/details/136189743
https://blog.csdn.net/mccand1234/article/details/124571619
Spirng 事务源码探索
首先让我们从ProxyTransactionManagementConfiguration开始,我们可以看到这里面注册了3个Bean对象,我们分别在这三个方法上打一个断点,我们会发现,断点首先停在了下面这个Bean:
这个Bean干了什么事情呢?
我们关注两个地方,一个是构造函数中的true,看到了吧这里就指定了我们的事务只能在public方法上生效。
然后我们视线来到下面这两个变量,这两个变量是由值从哪里来的呢?嘿嘿,看到了吧,原来是静态代码块来的,很明显我们的工程中是有
我们看到这个结果:
所以说这个Bean执行构造函数的时候为我们创建了JtaTransactionAnnotationParser,这个类里面有下面这个方法:
这不就看到了我们亲爱的@Transactional 注解了,然后我们继续看parseTransactionAnnotation:
好家伙里面的内容是真不少啊,但是仔细一看是不是发现好熟悉呀,这不就是咱们在@Transactional 注解里可以配置的属性嘛。那除了根据这个变量给我添加了JtaTransactionAnnotationParser,我们看一下前面,别忘了还给我们添加了一个SpringTransactionAnnotationParser哟,这个里面也是做了注解元素据解析,我们后面再来看看到底用的是哪个呢?
好我们继续走,发现断点到下面来了:
这里又干了什么事情,我们可以看到,创建了一个拦截器,然后把我们上面的transactionAttributeSource通过set进行了注入。好继续走,断点来到了这个Bean,合理的不得了吧,前面两个Bean都在则会个Bean注册的时候用到了。
好我们发现if中有个enableTx这玩意儿:
纳尼让我看看哪里来的?索达寺内,原来是在父类里面进行赋值的:
从这个注解拿一些属性,但是这个注解又在哪里用到了呢?做了怎样的配置?找啊找,好我们找到了TransactionAutoConfiguration里面有这个一段代码,害原来是SpringBoot帮我们做了呀。
然后我们并没有配置,所以搜采用了注解中的默认值,这里就可以发现是留了属性我们配置的哈:
好了到这里第三个Bean,BeanFactoryTransactionAttributeSourceAdvisor创建了,然后我们看看里面定义的这个切面,TransactionAttributeSourcePointcut:
在进行匹配的时候,我们注册的第一个Bean就派上用场了AnnotationTransactionAttributeSource,这块最终会去调用TransactionAnnotationParser 去解析元数据,那到底用的是那个?
看看,断点进入到这里来了,所以用的是SpringTransactionAnnotationParser。
到这里我们可以得出结论,当被 @Transactional注解标注的时候,如何能够拦截,是基于AOP去做的,我们在使用@Transactional标注的方法时已经被代理了,这里涉及到AOP什么时机创建代理对象的问题,这里关注事务的源码分析,不在这里进行说明了。
我们继续向后看,竟然被拦截了,那么实现事务的关键就在于这个拦截器帮我们干了什么。
TransactionInterceptor父类有这也一个方法invokeWithinTransaction这个方法特别的长:
上面进行了事务的创建,并开启事务。下面我们注意一些下图中的三个红框,分别是堆栈调用和开启事务的代码。
如果发生了异常则是在这个方法里面进行处理的:
最后提交事务,是在这里
这里需要注意一些事务提交时机:
- 方法正常结束:如果方法正常执行完毕(即没有抛出任何未被捕获的异常),那么事务会在方法返回之后提交。
- 方法抛出异常:如果方法执行过程中抛出了一个未被捕获的异常,并且这个异常不在事务配置的 @Transactional 注解的 rollbackFor 属性中被排除,那么事务将会回滚,不会提交。
- 手动控制:如果你使用编程式事务管理,可以通过手动控制事务的提交和回滚。