1、先来说一说事务是什么,在Spring中事务指的是对数据库进行增删改查的一系列过程,它具有以下几种特性:
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
2、 Spring事务的核心流程图如下:
3、Spring隔离级别
在Spring中,多个事务并发执行,隔离级别的话,主要是分为5种,分别有着不同的代表,且看下表:
ISOLATION_DEFAULT | PlatformTranscationManager种默认的隔离级别,代表使用数据库的默认事务隔离级别。 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,一个事务未提交前的结果可以被其他事务获取,可能会造成脏读、不可重复度、幻读的问题。 |
ISOLATION_READ_COMMITTED | 一个事务再提交前不可以被其他事务获取结果,不会造成脏读,可能会造成不可重复读、幻读等问题。 |
ISOLATION_REPEATABLE_READ | 可以防止脏读、不可重复读问题,但不会解决幻读问题。 |
ISOLATION_SERIALIZABLE | 花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。 |
下面来介绍一下上边的名词:
- ( Dirty reads)脏读:指的是事务A读取到事务B的数据后,事务B可能会因为一些原因失败而进行回滚,导致事务A获取的值是不正确的值。
- (non-repeatable reads)不可重复读:比如事务A中有两次读取某一全局变量total的值,第一次读取为100,然后事务B对 total的值改成了200,事务A第二次读取total的值为200,造成事务A的数据混乱。
- (phantom reads)幻读数据:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
4、Spring事务的7种传播行为:
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
PROPAGATION_REQUIRED | 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 |
PROPAGATION_SUPPORTS | 如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。 |
PROPAGATION_MANDATORY | 如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。 |
PROPAGATION_REQUIRED_NEW | 总是开启新的事务。如果已有事务,则挂起已存在的事务。 |
PROPAGATION_NOT_SUPPORTED | 总是非事务的执行,并挂起当前所有的事务。 |
PROPAGATION_NEVER | 总是非事务的执行,如果存在事务则抛出异常。 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
下边来对其中几种进行详细叙述:
1、PROPAGATION_REQUIRED,如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
public class Test{
//PROPAGATION_REQUIRED
public void fun1(){
...
fun2();
}
//PROPAGATION_REQUIRED
public void fun2(){
...
}
}
单独执行fun2相当于如下:
...
main{
Connection c = null;
try{
c.getConnection();
con.setAutoCommit(false);
fun2();
c.commit();
}catch(RuntimeException e){
c.rollback();
}finally{
c.close();
}
}
...
由于当前没有事务存在,故会创建一个新的事务去执行fun2();
执行fun1时,也会调用fun2,因此fun2方法会加入到fun1的事务中去执行:
...
main{
Connection c = null;
try{
c.getConnection();
con.setAutoCommit(false);
fun1();
c.commit();
}catch(RuntimeException e){
c.rollback();
}finally{
c.close();
}
}
...
2、PROPAGATION_SUPPORTS,如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。
public class Test{
//PROPAGATION_REQUIRED
public void fun1(){
...
fun2();
}
//PROPAGATION_SUPPORTS
public void fun2(){
...
}
}
单独调用fun2时,如果当前没有事务可用也不会开启事务;如果有可用事务,fun2会加入到调用他的事务中去,就比如fun1执行时会调用fun2,因此此时fun2加入了fun1的事务中。
3、PROPAGATION_MANDATORY,如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。
public class Test{
//PROPAGATION_REQUIRED
public void fun1(){
...
fun2();
}
//PROPAGATION_MANDATORY
public void fun2(){
...
}
}
单独执行fun2方法会抛出异常,但是通过fun1去调用fun2,则不会抛出异常。
4、 PROPAGATION_REQUIRED_NEW,总是开启新的事务。如果已有事务,则挂起已存在的事务。需要使用JtaTransactionManager作为事务管理器。
public class Test{
//PROPAGATION_REQUIRED
public void fun1(){
System.out.println("AAA_Begin");
fun2();
System.out.println("AAA_End");
}
//PROPAGATION_REQUIRED_NEW
public void fun2(){
...
System.out.println("BBB");
}
}
调用方法fun1时,相当于如下:
...
main{
TransactionManager tm = null;
try{
tm = getTranscationManager();
tm.begin();
Transaction ts1 = tm.getTransaction();
System.out.println("AAA_Begin");
tm.suspend();
try{
tm.begin();
TransactionManager ts2 = tm.getTranscationManager();
fun2();
ts2.commit();
}catch(RuntimeException e){
ts2.rollback();
}finally{
//释放资源
}
tm.resume(ts1);
System.out.println("AAA_End");
ts1.commit();
}catch(RuntimeException e){
tm.rollback();
}finally{
//释放资源
}
}
...
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。
5、PROPAGATION_NESTED,表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。
public class Test{
//PROPAGATION_REQUIRED
public void fun1(){
...
System.out.println("AAA");
fun2();
}
//PROPAGATION_NESTED
public void fun2(){
...
System.out.println("BBB");
}
}
单独执行fun2则会开启新的事务,去支持fun2。
调用fun1方法,相当于如下:
...
main{
Connection c = null;
try{
c.getConnection();
con.setAutoCommit(false);
fun1();
savepoint = c.setSavepoint();
try{
fun2();
}catch(RuntimeException e){
c.rollback(savepoint);
}
c.commit();
}catch(RuntimeException e){
c.rollback();
}finally{
c.close();
}
}
...
这里可以看到,在执行fun2之前,会使用savepoint去保留当前的执行状态,如果fun2执行失败,则回滚到savepoint的状态,而且fun2并没有执行commit方法,如果后续代码执行失败,则会回滚到起始状态。由此可见,外部事务回滚会影响到内层事务,而内层事务的回滚不会影响到外层事务。
参考自:https://www.cnblogs.com/csniper/p/5536633.html、https://www.cnblogs.com/yixianyixian/p/8372832.html