文章目录
文章简介:
在写spring几个有趣的小甜点中的事务失效的时候被事务传播机制卡住了,觉得有必要学习整理一下事务的传播。
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
REQUIRED(默认是这个):
这个方法必须运行在事务中,如果事务存在,则会运行在当前事务中,否则会新开一个事务。
什么意思?从字面意思根本看不懂,举例最实在了。
controller
@RequestMapping("tran")
public void hello(){
Order order = new Order();
order.setName("order1");
userService.createOrder(order);
}
service
@Override
@Transactional
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
}
}
@Override
@Transactional(propagation= Propagation.REQUIRED)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
createOrder外层事务加了@Transactional,调用sendMsg,sendMsg事务传播方式为REQUIRED,如果createOrder外层事务存在,则sendMsg会运行createOrder事务中,这也就是为什么在sendMsg里面抛运行时异常会.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。解决方法在spring几个有趣的小甜点事务失效部分有详细解说。
上面是外层createOrder加了 @Transactional的情况,如果外层事务不加这个注解,那么内层事务sendMsg会自己开一个事务。此时的结果应该是订单表add进去了,短信表被回滚。
SUPPORTS:
这个方法不需要事务,但是如果存在事务,就在事务中运行。
createOrder没有事务,所以sendMsg也没有事务,最终的结果就是两张表都add进去了。
@Override
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
}
}
@Override
@Transactional(propagation= Propagation.SUPPORTS)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
如果给createOrder加事务注解,那么两个方法就都有事务了,而且是在同一个事务中运行,不出意外又是UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
MANDATORY:
该方法必须在事务中运行,如果没有事务,抛异常IllegalTransactionStateException
@Override
@Transactional
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
}
}
@Override
@Transactional(propagation= Propagation.MANDATORY)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
两个方法都有事务了,而且是在同一个事务中运行,不出意外又是UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
如果把createOrder改成这样
@Override
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
e.printStackTrace();//打印异常看看
}
}
会抛出IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’
REQUIRES_NEW:
该方法必须运行在新事务中
@Override
@Transactional
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
e.printStackTrace();
}
}
@Override
@Transactional(propagation= Propagation.REQUIRES_NEW)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
很明显无论createOrder加不加事务,sendMsg都是有自己的事务的,所以执行结果是order表add进去,msg回滚。非常符合业务需求。
NOT_SUPPORTED:
该方法不应该运行在事务中,如果当前存在事务,事务会被挂起。
@Override
@Transactional
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
e.printStackTrace();
}
}
@Override
@Transactional(propagation= Propagation.NOT_SUPPORTED)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
createOrder不加事务时没什么好说的,createOrder加事务时,这个事务会被挂起,所以相当于没加事务,所以两张表都加进去了。
NEVER
该方法不能运行在事务中,如果存在事务,抛异常,这个最简单,不说了。
NESTED
如果当前存在事务,则该方法会在嵌套事务中运行,该嵌套事务可以独立于当前事务进行提交回滚操作。
如果当前不存在事务,会新开一个事务
@Override
@Transactional
public void createOrder(Order order) {
orderMapper.save(order);
try {
UserService proxy= (UserService)AopContext.currentProxy();
proxy.sendMsg();
}catch (Exception e){
System.out.println("rollback");
e.printStackTrace();
}
}
@Override
@Transactional(propagation= Propagation.NESTED)
public void sendMsg(){
msgMapper.save(new Msg("你已经下单了"));
//调用短信网关
throw new RuntimeException();//运行时异常,编译时上层可以不捕捉
}
此时内层事务sendMsg会独立于外层事务进行事务的提交回滚,所以最终结果时order表add进去,msg被回滚。非常符合业务需求。
增强理解
例1,NOT_SUPPORTED
@Override
@Transactional
public void testTran() {
orderService.saveOrder();//保存订单
productService.saveProduct();//保存商品
}
@Override
public void saveOrder(){
Order order = new Order();
order.setOrderSn(2L);
order.setProductId(1);
order.setUserId(1);
order.setBuyNum(1);
orderMapper.save(order);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public void saveProduct(){
Product product = new Product();
product.setPrice(1);
product.setName("2121");
productMapper.insert(product);
int i = 1/0;
}
读者可以先判断下最终order和product表最终的数据状态?
执行流程如下:
结论:订单表回滚,商品表不回滚,not_supported会挂起事务,在执行完后会恢复事务
例2,REQUIRED_NEW
@Override
@Transactional
public void testTran() {
orderService.saveOrder();//保存订单
productService.saveProduct();//保存商品
int i = 1/0;
}
@Override
public void saveOrder(){
Order order = new Order();
order.setOrderSn(2L);
order.setProductId(1);
order.setUserId(1);
order.setBuyNum(1);
orderMapper.save(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void saveProduct(){
Product product = new Product();
product.setPrice(1);
product.setName("2121");
productMapper.insert(product);
}
结果:product不回滚,order回滚,因为saveProduct是一个新开事务,所以没有异常直接就提交了
例3,REQUIRED
REQUIRED作用是需要事务,没有就新开一个事务。
@Override
@Transactional
public void testTran() {
orderService.saveOrder();//保存订单
try{
productService.saveProduct();//保存商品
}catch(Exception e){
}
}
@Override
public void saveOrder(){
Order order = new Order();
order.setOrderSn(2L);
order.setProductId(1);
order.setUserId(1);
order.setBuyNum(1);
orderMapper.save(order);
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void saveProduct(){
Product product = new Product();
product.setPrice(1);
product.setName("2121");
productMapper.insert(product);
int i = 1/0;
}
结果是两个表都回滚,这个就有点奇怪,因为自始至终就只有一个testTran开启的事务,saveProduct也只是延用了testTran开启的事务,就算saveProduct抛了异常,但是testTran捕捉了,其实不应该回滚的吧?但是源代码不是这么处理的。