Spring事务
一、@Transactional
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,@Transactional注解就是申明式的。首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败。在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个
代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有
出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
二、事务的隔离级别
一个事务与其他事务的隔离的程度成为隔离级别,以下是四种隔离级别,依次是变高的,隔离级别越高,数据的一致性就越好,但也不是越高就越好,因为越高对于性能的花销就越大。开发中常用的隔离级别是读已提交。以下是一些并发事务常见问题:
事务的四大特性分别是:原子性、一致性、隔离性、持久性
幻读和不可重复读都是在同一个事务中多次读取了其他事务已经提交的事务的数据导致每次读取的数据不一致,所不同的是不可重复读读取的是同一条数据,而幻读针对的是一批数据整体的统计(比如数据的个数)
1.读未提交:READ UNCOMMITTED(可以读到未提交的,可能会发生脏读)
2.读已提交:READ CONNITTED(可以避免脏读)
3.可重复读:REPEATABLE READ(可以避免不可重复读)
4.串行化:SERIALIZABLE(可以避免幻读)
以MYSQL数据库来分析四种隔离级别
第一种隔离级别:Read uncommitted(读未提交)
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据
同一个数据,一个事务写,另一个事务可以读。
解决了更新丢失,但还是可能会出现脏读
第二种隔离级别:Read committed(读提交)
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
同一个数据,一个事务写,另一个事务不可以读。一个事务读,另一个事务可以读写。
解决了更新丢失和脏读问题
第三种隔离级别:Repeatable read(可重复读取)
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读锁”和“排他写锁”实现。
幻读产生
//行级排他锁
select * from test for update
//行级共享锁,注意可能会发生死锁
select * from test lock in share mode
行级共享锁
同一个数据,一个事务读,另一个事务可以读,一个事务写,另一个事务既不可以读也不可以写。
解决了更新丢失、脏读、不可重复读、但是还会出现幻读
第四种隔离级别:Serializable(可序化)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
解决了更新丢失、脏读、不可重复读、幻读(虚读)
脏读
不可重复读
幻读
幻读
事务隔离级别
事务隔离级别
如果数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,这时候以Spring配置的为准,如果Spring设置的隔离级别数据库不支持,则效果取决于数据库。
mysql默认的事务处理级别是'REPEATABLE-READ',也就是可重复读
Oracle默认系统事务隔离级别是READ COMMITTED,也就是读已提交
SQL Server默认系统事务隔离级别是READ COMMITTED,也就是读已提交。
三、spring事务传播机制
spring事务传播机制是指,多个事务方法相互调用时,事务如何在这些方法间传播。方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么由两个方法所定义的事务传播类型决定。
七种传播机制
NESTED的重点是作为调用方的一个嵌套。
NESTED和REQUIRES_NEW的区别:REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。 在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。
NESTED和REQUIRED的区别:REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响。
事务的传播属性通过@Transactional注解中对属性propagation=" "来进行配置,默认是“REQUIRED”是运行原来的事务,还有一个常用的是"REQUIRED_NEW"开启新的事务,举例来说,购买东西是一个事务,购买每一本书又是一个事务,在购买事务中调用购买书,假如余额只有100块,买两本书,一本是50,一本是60。这样的话默认的就是在原来的事务,钱不足是不会执行成功,REQUIRED_NEW就会开启新事务,则会买一本书。
四、spring事务什么时候会失效
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是AOP不起作用了!
常见情况有如下几种:
1、发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身!解决方法很简单,让那个this变成UserService的代理类即可!
2、方法不是public的
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可 以开启 AspectJ 代理模式。
3、数据库不支持事务
4、没有被spring管理
5、异常被吃掉,事务不会回滚(或者抛出的异常没有被定义,默认为RuntimeException)