事务传播机制
文章目录
事务隔离级别
事务与事务在并发进行的时候就叫作隔离级别,在数据库没有提交数据的时候,你更新的数据是在日志中进行更新的,只有在提交之后,数据才从日志中把数据更新到数据库里面。总的意思就是将多个事务隔离开来,每个事物都不能访问或者打断其他事务的操作过程中的状态。
读未提交
目前主流的关系型数据库例如mysql、Oracle都是基于文件系统进行数据存储的,即数据是持久化到文件系统的,但基于文件系统的随机IO读写是非常慢的,故数据库都会引入内存池,完成对磁盘数据的缓存,提高读写性能。
内存池是对所有线程共享的,也就是对所有的数据库事务是共享的,所谓的未提交,指的是事务未提交,但此时数据已经存储到了共享内存中,数据已经进入到了数据库服务器中,所以是可见的。
脏读,是指的最终的效果,其实在数据库中对应读未提交数据隔离级别。
当事务1,2需要访问同一行数据,事务2从数据库读取到的数据是事务1未提交的数据,但当事务1回滚,导致事务2读取到数据并不是最终存储在数据库中的数据,导致所谓的脏读发生。
概述
- 事务传播行为:由一个事务传播行为修饰的方法被嵌套进另一个方法时事务如何传播
- spring在TransactionDefintion规定了7中事务传播行为
- spring框架特有的事务增强行为
- Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的
- 真正的数据库层的事务提交和回滚是通过bin log或者redo log实现的
获取连接 Connection con = DriverManager.getConnection()
开启事务con.setAutoCommit(true/false);
执行CRUD
提交事务/回滚事务 con.commit() / con.rollback();
关闭连接 conn.close();
七种事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。共享同一个connect,jdbc的connect.commit、connect.rollback,一起提交,一起回滚 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。内部方法的事务性完全依赖于最外层的事务 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | (子事务)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
外围方法未开启事务
也就是调用方法未开启事务
同一类中
非同一类中
- (非同一类)内部方法开启事务使用**
Propagation.REQUIRED
**则内部方法会开启新的单独事务,且互不影响 - 在外围方法未开启事务的情况下**
Propagation.NESTED
和Propagation.REQUIRED
**作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰
外围方法开启事务
也就是调用方法开启事务
同一类中
非同一类中
- 内部方法加入外围事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。
- 在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰
- 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚
- 外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。
- 外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。
- 证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务
REQUIRED,REQUIRES_NEW,NESTED异同
- NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。
- NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。
总结
场景使用
- 对于非严格要求记录的信息,例如普通日志记录,可以使用**
Propagation.NOT_SUPPORTED
**修饰,此方法抛出异常也不影响其他事务 - unchecked (that is, it extends the
java.lang.RuntimeException
class) 官方解释未捕获异常定义 - spring默认只对RuntimeException和Error做捕捉,并回滚,其他的异常,直接提交
- 典型错误:如果serviceA的methodA调用serviceB的methodB方法,serviceB中直接trycatch了DAO操作的异常同时没有抛出,那么serviceB将被认为事务中没有异常发生,间接导致methodA事务正常提交,导致脏数据产生
注意:考虑到spring的事务传播机制,我们再对代码进行try catch时,应该多一些思考,事务传播往往和异常有着千丝万缕的关系。check exception不用多说了,主要是对于runtimeException,一定要慎重。 比如,在最内层catch异常,会对整个事务的原子性造成污染,即try的DAO可能执行失败了,但是外层方法的事务还是提交了。 当存在需要单独开辟事务的场景时,需要考虑其他的事务传播属性