Spring事务

一、什么是事务的传播?

简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播。

二、Spring事务传播类型Propagation介绍

在Spring中对于事务的传播行为定义了七种类型分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED
现有两个方法A和B,方法A执行会在数据库ATable插入一条数据,方法B执行会在数据库BTable插入一条数据,伪代码如下:

//将传入参数a存入ATable pubilc void A(a){
    insertIntoATable(a);    
}
//将传入参数b存入BTable public void B(b){
    insertIntoBTable(b);
}

无事务

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}

public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

a1数据成功存入ATable表,b1数据成功存入BTable表,而在抛出异常后b2数据存储就不会执行,也就是b2数据不会存入数据库,这就是没有事务的场景。

REQUIRED(Spring默认的事务传播类型)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:
数据库没有插入新的数据,数据库还是保持着执行testMain方法之前的状态,没有发生改变。testMain上声明了事务,在执行testB方法时就加入了testMain的事务(当前存在事务,则加入这个事务),在执行testB方法抛出异常后事务会发生回滚,又testMain和testB使用的同一个事务,所以事务回滚后testMain和testB中的操作都会回滚,也就使得数据库仍然保持初始状态

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:
数据a1存储成功,数据b1和b2没有存储。由于testMain没有声明事务,testB有声明事务且传播行为是REQUIRED,所以在执行testB时会自己新建一个事务(如果当前没有事务,则自己新建一个事务),testB抛出异常则只有testB中的操作发生了回滚,也就是b1的存储会发生回滚,但a1数据不会回滚,所以最终a1数据存储成功,b1和b2数据没有存储

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:
a1,b1存入数据库,b2没有存入数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),则在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。

MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:
a1存储成功,而b1和b2没有存储。b1和b2没有存储,并不是事务回滚的原因,而是因为testMain方法没有声明事务,在去执行testB方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以testB方法里的内容就没有执行。

REQUIRES_NEW

创建一个新事务,如果存在当前事务,则挂起该事务,即在执行时,不论当前是否存在事务,总是会新建一个事务,所谓挂起就是当前事务要等到新事务执行完才能继续执行,两个事务是独立的。

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
    throw Exception;     //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

结果:
这种情形的执行结果就是a1没有存储,而b1和b2存储成功,因为testB的事务传播设置为REQUIRES_NEW,所以在执行testB时会开启一个新的事务,testMain中发生的异常时在testMain所开启的事务中,所以这个异常不会影响testB的事务提交,testMain中的事务会发生回滚,所以最终a1就没有存储,而b1和b2就存储成功了。

NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation =Propagation.NOT_SUPPORTED)
public void testB(){
    B(b1);  //调用B入参b1
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2
}

结果:
a1和b2没有存储,而b1存储成功。testMain有事务,而testB不使用事务,所以执行中testB的存储b1成功,然后抛出异常,此时testMain检测到异常事务发生回滚,但是由于testB不在事务中,所以只有testMain的存储a1发生了回滚,最终只有b1存储成功,而a1和b1都没有存储

NEVER

不使用事务,如果当前事务存在,则抛出异常

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

结果:
该场景执行,直接抛出事务异常,且不会有数据存储到数据库。由于testMain事务传播类型为REQUIRED,所以testMain是运行在事务中,而testB事务传播类型为NEVER,所以testB不会执行而是直接抛出事务异常,此时testMain检测到异常就发生了回滚,所以最终数据库不会有数据存入。

NESTED

如果当前事务存在,则在嵌套事务中执行,NESTED是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。
在NESTED情况下父事务回滚时,子事务也会回滚
子事务回滚,父事务不受影响

@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1
    testB();    //调用testB
    throw Exception;     //发生异常抛出
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
    B(b1);  //调用B入参b1
    B(b2);  //调用B入参b2
}

结果:
所有数据都不会存入数据库,因为在testMain发生异常时,父事务回滚则子事务也跟着回滚了

面试题

spring事务的原理?
spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
那么,我们一般使用JDBC操作事务的时候,代码如下
(1)获取连接 Connection con = DriverManager.getConnection()
(2)开启事务con.setAutoCommit(true/false);
(3)执行CRUD
(4)提交事务/回滚事务 con.commit() / con.rollback();
(5)关闭连接 conn.close();
使用spring事务管理后,我们可以省略步骤(2)和步骤(4),就是让AOP帮你去做这些工作。

spring什么情况下进行事务回滚?
1.事务只会对Error与RuntimeException及其子类这些异常,做出回滚。一般的Exception这些Checked异常不会发生回滚
2.当所拦截的方法有指定异常抛出,事务才会自动进行回滚,异常被捕获则不会回滚

spring事务什么时候失效?
1.数据库引擎不支持事务
2.没有被 Spring 管理
3.方法不是 public 的
4.自身调用问题
5.异常被捕获
6.异常类型错误

spring的事务隔离和数据库的事务隔离是一个概念么?
是的,如果spring定义的隔离级别和数据库的不一样,则以spring定义的为准

spring事务控制放在service层,在service方法中一个方法调用service中的另一个方法,默认开启几个事务?
考验spring的事务PROPAGATION_REQUIRED级别的传播行为,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行。

参考:
带你读懂Spring 事务——事务的传播机制
面试官:来,讲讲spring事务有哪些坑?

你可能感兴趣的