> 文章列表 > Spring事务切面_传播属性(8)

Spring事务切面_传播属性(8)

Spring事务切面_传播属性(8)

目录

1. 传播属性

2. 案例分享

2.1 测试说明

2.2  Propagation.REQUIRED 演示

案例1:

案例2:

案例3:

案例4:

总结1:

案例5:

案例6: 特殊的传播属性 NESTED 错误使用

案例6的解决方案:

3. 带着问题看源码

3.1 类分析:

3.2 源码分析

3.2.1 事务创建

 3.2.2 事务的链式调用

源码case:

3.2.3 事务的回滚。

总结2:

3.2.4. 伪代码分析:


1. 传播属性

Spring特有一套处理事务处理的逻辑,而今天要讲的传播属性就是Spring独有的。下面对传播属性进行介绍:

PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常

PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

以上传播属性,使用最高频的是: PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED。其实,所谓的传播属性,就是控制Connection对数据库进行操作的。传统的JDBC连接数据库如下:

 Connection connection = null;try {connection = ConnectionUtil.getConnection();//开启事务/*** */connection.setAutoCommit(false);insertTest(connection);insertTest1(connection);connection.commit();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();try {connection.rollback();System.out.println("JDBC Transaction rolled back successfully");} catch (SQLException e1) {System.out.println("JDBC Transaction rolled back fail" + e1.getMessage());}} finally {if (connection != null) {try {selectAll(connection);connection.close();} catch (SQLException e) {e.printStackTrace();}}}

也就是说,每一次连接数据库进行操作,我们底层都需要生成Connection对象,通过Connection对象实际上进行数据库的连接操作。而在我们Spring的事务中,就是按照Spring的逻辑对Connection进行不同的封装调用而已。具体点说就是按照你在业务代码中配置的事务传播属性,进行不同逻辑的Connection生成和封装对象,然后进行数据库的连接。

2. 案例分享

2.1 测试说明

        首先,我们需要一个测试类和一个假设的业务类,但是这个业务类调用的是其他业务类的方法,他们都带有事务。

        测试类:我们会执行propagationTest方法:

package com.xuexi.jack.test;import com.xuexi.jack.bean.ComponentScanBean;
import com.xuexi.jack.pojo.ConsultConfigArea;
import com.xuexi.jack.pojo.ZgGoods;
import com.xuexi.jack.service.AccountService;
import com.xuexi.jack.service.area.AreaService;
import com.xuexi.jack.service.transaction.TransationService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;import java.util.HashMap;
import java.util.Map;public class TransactionTest {private ApplicationContext applicationContext;@Beforepublic void before() {applicationContext = new AnnotationConfigApplicationContext(ComponentScanBean.class);}@Testpublic void test1() {AreaService bean = applicationContext.getBean(AreaService.class);Map param = new HashMap();param.put("areaCode","1001");bean.queryAreaFromDB(param);}@Testpublic void addAreaTest() {AreaService bean = applicationContext.getBean(AreaService.class);ConsultConfigArea area = new ConsultConfigArea();area.setAreaCode("VV1");area.setAreaName("VV1");area.setState("1");bean.addArea(area);}@Testpublic void propagationTest() {String areaStr = "HN1";String goodsStr = "iphone 2";TransationService transationService = applicationContext.getBean(TransationService.class);ConsultConfigArea area = new ConsultConfigArea();area.setAreaCode(areaStr);area.setAreaName(areaStr);area.setState("1");ZgGoods zgGoods = new ZgGoods();zgGoods.setGoodCode(goodsStr);zgGoods.setGoodName(goodsStr);zgGoods.setCount(100);transationService.transation(area,zgGoods);}@Testpublic void accountServiceTest() {AccountService bean = applicationContext.getBean(AccountService.class);bean.queryAccount("d");}}

中转业务类,关注 transation 方法:

package com.xuexi.jack.service.transaction;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import com.xuexi.jack.pojo.ZgGoods;
import com.xuexi.jack.pojo.ZgTicket;
import com.xuexi.jack.service.area.AreaService;
import com.xuexi.jack.service.goods.GoodsService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {@AutowiredAreaService areaService;@AutowiredGoodsService goodsService;@AutowiredCommonMapper commonMapper;//开启了事务@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void transation(ConsultConfigArea area, ZgGoods zgGoods) {//try {areaService.addArea( area);goodsService.addGoods(zgGoods);/* }catch (Exception e) {}*/}//提交事务@Transactional@Overridepublic int getTicket() {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);} else {//继续抢((TransationService) AopContext.currentProxy()).getTicket();}return 0;}@Autowiredprivate TransactionTemplate transactionTemplate;@Overridepublic int getTicketModeOne() {Integer execute = transactionTemplate.execute(status -> {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);}return i;});if (execute == 0) {//继续抢getTicketModeOne();}return 0;}
}

 在上方的业务中转类中,我们设置了事务隔离属性为 propagation = Propagation.REQUIRED,并且它还按顺序,调用了另外2个类。下面是各种case分享:

2.2  Propagation.REQUIRED 演示

案例1:

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 REQUIRED,但是在addArea方法中抛异常。此时,2张表都无法插入数据。因为他们使用的是同一个Connection对象连接的数据库,而addArea方法抛异常,导致后面的方法无法被执行到。同时,因为主方法 transation 也有事务,因此他会返回到主方法处进行回滚。所以2张表都没有数据。

AreaServiceImpl:
package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);if(true) throw new RuntimeException("yic");/*   try {if (true) {throw new RuntimeException("111");}}catch (Exception e){}*/return 1;}
}
GoodsServiceImpl:
    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);//if(true) throw new RuntimeException("yic");/* try {if(true) throw new RuntimeException("yic");}catch (Exception e) { }*/}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

案例2:

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 REQUIRED,但是在addArea方法中有异常并且自己捕获到了异常,不再往上抛异常。goodsService.addGoods(zgGoods)方法保持不变,此时执行测试方法,我们会发现,2张表都有数据。原因是异常被我们自己写的 try...catch 捕获并吞掉, spring并没有获取到异常信息。因此2张表会正常的插入数据

AreaServiceImpl:
package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);//if(true) throw new RuntimeException("yic");try {if (true) {throw new RuntimeException("111");}}catch (Exception e){}return 1;}
}
GoodsServiceImpl:
    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);//if(true) throw new RuntimeException("yic");/* try {if(true) throw new RuntimeException("yic");}catch (Exception e) { }*/}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

案例3:

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 REQUIRED,但是在addGoods方法中抛异常。此时,2张表都无法插入数据。因为他们使用的是同一个Connection对象连接的数据库,而addGoods方法抛异常会被主方法 transation 捕获到,因此他会返回到主方法处进行回滚。所以2张表都没有数据。

 AreaServiceImpl

package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);//if(true) throw new RuntimeException("yic");/*  try {if (true) {throw new RuntimeException("111");}}catch (Exception e){}*/return 1;}
}

GoodsServiceImpl

    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);if(true) throw new RuntimeException("yic");/* try {if(true) throw new RuntimeException("yic");}catch (Exception e) { }*/}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

案例4:

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 REQUIRED,但是在addGoods方法中有异常并且自己捕获到了异常,不再往上抛异常。执行测试用来,我们会发现,2张表都有数据。原因是异常被我们自己写的 try...catch 捕获并吞掉, spring并没有获取到异常信息。因此2张表会正常的插入数据

AreaServiceImpl:

package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);//if(true) throw new RuntimeException("yic");/*  try {if (true) {throw new RuntimeException("111");}}catch (Exception e){}*/return 1;}
}

 GoodsServiceImpl :

    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);//if(true) throw new RuntimeException("yic");try {if(true) throw new RuntimeException("yic");}catch (Exception e) { }}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

总结1:

1. 凡是异常被吞掉的,都不会执行回滚,数据会正常插入。 以后的案例中将不会再提到异常被自己捕获,但是不往上抛的情况,基本都是一样的逻辑、

2. 使用同一个Connection对象提交的数据,只要任何一处抛出异常 (一直往上抛,没有自己吞掉),那么会回滚这一次提交的所有数据。即使是不在同一张表,也会被回滚掉。 

3. 使用同一个Connection对象提交的数据,如果前面的方法抛出异常(一直往上抛,没有自己吞掉),那么后面的方法不会被执行。 以后的案例中将不会再提到此种情况,逻辑基本都是一样的。

案例5:

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 REQUIRED_NEW,但是在addGoods方法中抛异常。此时 areaService.addArea( area)正常插入数据。因为他们每次都新生成一个事务对象,并且持有新的Connection对象。回滚只是针对同一个Connection提交的SQL而言。因此,addArea正常插入数据。

AreaServiceImpl :

package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);//if(true) throw new RuntimeException("yic");/*  try {if (true) {throw new RuntimeException("111");}}catch (Exception e){}*/return 1;}
}
 GoodsServiceImpl :
    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);if(true) throw new RuntimeException("yic");/* try {if(true) throw new RuntimeException("yic");}catch (Exception e) { }
*/}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

案例6: 特殊的传播属性 NESTED 错误使用

 areaService.addArea( area) 和 goodsService.addGoods(zgGoods)方法的事务传播属性都是 NESTED,但是在addGoods方法中抛异常。此时 areaService.addArea( area)也无法插入数据。NESTED是设置回滚点进行回滚的,如果我们抛异常,addGoods方法插入的数据就会被回滚掉。

但是,为什么前面的addArea插入的数据也会被回滚掉呢?因为addGoods 是被另一个事务方法 transation调用的,如果我们不处理异常,那么异常就会一直往上抛。因为NESTED 不会创建新的Connection对象,也就是说它和transation方法使用同一个Connection对象,异常一直往上抛,会被 transation 方法给捕获到并且给回滚掉。而 transation方法中也调用了addArea 方法,所以会被整体回滚掉,2张表都没有数据。

TransationServiceImpl :

package com.xuexi.jack.service.transaction;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import com.xuexi.jack.pojo.ZgGoods;
import com.xuexi.jack.pojo.ZgTicket;
import com.xuexi.jack.service.area.AreaService;
import com.xuexi.jack.service.goods.GoodsService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {@AutowiredAreaService areaService;@AutowiredGoodsService goodsService;@AutowiredCommonMapper commonMapper;//开启了事务@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void transation(ConsultConfigArea area, ZgGoods zgGoods) {//try {areaService.addArea( area);goodsService.addGoods(zgGoods);/* }catch (Exception e) {}*/}//提交事务@Transactional@Overridepublic int getTicket() {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);} else {//继续抢((TransationService) AopContext.currentProxy()).getTicket();}return 0;}@Autowiredprivate TransactionTemplate transactionTemplate;@Overridepublic int getTicketModeOne() {Integer execute = transactionTemplate.execute(status -> {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);}return i;});if (execute == 0) {//继续抢getTicketModeOne();}return 0;}
}

AreaServiceImpl :

package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.NESTED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);return 1;}
}

GoodsServiceImpl :

    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.NESTED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);throw new RuntimeException("异常");}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

案例6的解决方案:

想要按照回滚点进行回滚,那么异常是必须要抛出的,但是不能一直往上抛,否则会被整体回滚掉。因此,我们可以在addGoods方法中正常抛异常,想别的办法去依旧保留下之前addArea 方法插入的数据。

方法1:前面说过,回滚、提交都是针对同一个Connection对象的。那么,给addArea方法使用 REQUIRES_NEW 传播属性,这样他们2个就会使用不同的Connection对象了。

package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);return 1;}
}

方法2: 上一个解决方法并不好,因为如果有很多个方法,难到每一次出问题就要去修改传播属性值吗。其实,在addGoods方法中抛异常,但是在上一层方法 transation 中去捕获异常并吞掉异常,一样可以解决这个问题:

TransationServiceImpl:
package com.xuexi.jack.service.transaction;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import com.xuexi.jack.pojo.ZgGoods;
import com.xuexi.jack.pojo.ZgTicket;
import com.xuexi.jack.service.area.AreaService;
import com.xuexi.jack.service.goods.GoodsService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;import java.util.HashMap;
import java.util.List;
import java.util.Map;@Service("transationServiceImpl")
public class TransationServiceImpl implements TransationService {@AutowiredAreaService areaService;@AutowiredGoodsService goodsService;@AutowiredCommonMapper commonMapper;//开启了事务@Transactional(propagation = Propagation.REQUIRED)@Overridepublic void transation(ConsultConfigArea area, ZgGoods zgGoods) {try {areaService.addArea( area);goodsService.addGoods(zgGoods);}catch (Exception e) {}}//提交事务@Transactional@Overridepublic int getTicket() {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);} else {//继续抢((TransationService) AopContext.currentProxy()).getTicket();}return 0;}@Autowiredprivate TransactionTemplate transactionTemplate;@Overridepublic int getTicketModeOne() {Integer execute = transactionTemplate.execute(status -> {//1、获取锁List<ZgTicket> zgTickets = commonMapper.queryTicketById("12306");Map lockmap = new HashMap();lockmap.put("ticketId", "12306");lockmap.put("version", zgTickets.get(0).getVersion());int i = commonMapper.updateLock(lockmap);if (i > 0) {//抢票ZgTicket zgTicket = zgTickets.get(0);zgTicket.setTicketCount(2);int i1 = commonMapper.updateTicket(zgTicket);}return i;});if (execute == 0) {//继续抢getTicketModeOne();}return 0;}
}
AreaServiceImpl
package com.xuexi.jack.service.area;import com.xuexi.jack.dao.CommonMapper;
import com.xuexi.jack.pojo.ConsultConfigArea;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;//@PropertySource("classpath:config/core/core.properties")
@Service
public class AreaServiceImpl implements AreaService {private Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate CommonMapper commonMapper;@AutowiredAreaService areaService;@Transactional(propagation = Propagation.NESTED)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);return 1;}
}
GoodsServiceImpl:
    package com.xuexi.jack.service.goods;import com.xuexi.jack.dao.CommonMapper;import com.xuexi.jack.pojo.ZgGoods;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import java.util.List;@Servicepublic class GoodsServiceImpl implements GoodsService {@AutowiredCommonMapper commonMapper;@Transactional(propagation = Propagation.NESTED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);throw new RuntimeException("异常");}@Transactional(readOnly = true)@Overridepublic List<ZgGoods> queryAll() {return commonMapper.queryAll();}}

3. 带着问题看源码

3.1 类分析:

上一篇介绍了@Bean方法的实例化,接下来我们看一下事务支持类ProxyTransactionManagementConfiguration都干了什么事情,

/** Copyright 2002-2017 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.transaction.annotation;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.transaction.config.TransactionManagementConfigUtils;
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;/*** {@code @Configuration} class that registers the Spring infrastructure beans* necessary to enable proxy-based annotation-driven transaction management.** @author Chris Beams* @since 3.1* @see EnableTransactionManagement* @see TransactionManagementConfigurationSelector*/
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {/** 明显是创建事务切面实例* BeanFactoryTransactionAttributeSourceAdvisor** */@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();advisor.setTransactionAttributeSource(transactionAttributeSource());//设置通知类advisor.setAdvice(transactionInterceptor());if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {return new AnnotationTransactionAttributeSource();}/** 创建事务advice* TransactionInterceptor* */@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor() {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource());//事务管理器要跟数据源挂钩,所以需要自己定义if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}

1. 在我们调用 transactionAdvisor方法的时候,我们生成了advisor对象

2. 在advisor对象中,我们通过advisor.setTransactionAttributeSource(transactionAttributeSource()) 的调用,生成了事务注解属性类,负责生成包装事务属性的包装类并进行注入

3. 在advisor对象中,我们通过advisor.setAdvice(transactionInterceptor()), 还生成了advice类,也就是最小单元的advisor, 负责具体的拦截、增强的类

4. 最后给advisor设置优先级,确保优先执行

3.2 源码分析

Spring的事务是AOP技术实现的,因此也会走AOP的那一套流程进行调用,最终调用到了事务切面。

 

贴出这个方法的全部源码,对字方法进行逐步分析:

@Nullableprotected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.//获取事务属性类 AnnotationTransactionAttributeSourceTransactionAttributeSource tas = getTransactionAttributeSource();//获取方法上面有@Transactional注解的属性final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);//获取事务管理器final PlatformTransactionManager tm = determineTransactionManager(txAttr);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.//火炬传递retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exception//事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}//事务提交commitTransactionAfterReturning(txInfo);return retVal;}else {final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);try {return invocation.proceedWithInvocation();}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}}}

 这里面的方法都很重要,比如 TransactionAttributeSource实例的获取,PlatformTransactionManager的获取都值得着重去了解,但是今天的核心是事务传播属性的使用与底层分析。因此,事务的创建、拦截、回滚是本文的核心。

3.2.1 事务创建

 进入这个方法

来到核心方法处:

下面针对这个方法,逐步进行代码分析:

1.  Object transaction = doGetTransaction() 获取数据源对象。其实,每次进来都是 new了一个包装数据源的对象。第一次进来,是没有DataSource数据源对象的,因此, ConnectionHolder为null, 我们拿不到连接数据库的信息。

	@Overrideprotected Object doGetTransaction() {//管理connection对象,创建回滚点,按照回滚点回滚,释放回滚点DataSourceTransactionObject txObject = new DataSourceTransactionObject();//DataSourceTransactionManager默认是允许嵌套事务的txObject.setSavepointAllowed(isNestedTransactionAllowed());//obtainDataSource() 获取数据源对象,其实就是数据库连接块对象ConnectionHolder conHolder =(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());txObject.setConnectionHolder(conHolder, false);return txObject;}

2、 第一次进入,其实核心就是设置连接数据库的信息 

看看具体设置了写什么东西:注意一下 newTransaction这个参数,这是数据回滚的依据,这里默认是true

 概括起来就是,本文一开头常用的3个传播属性,第一次进入的时候都会执行相同的操作:

a. 挂起之前的事务,核心就是将之前存储的Connection对象设置为空,并进行数据库还原操作,具体看        SuspendedResourcesHolder suspendedResources = suspend(null) 代码:

b. doBegin(transaction, definition) 开启事务。其实就是获取数据库连接信息,就是我们配置的信息

 

c. 关闭数据库的自动提交功能,

d. 搜集DataSource 和 Connection的映射放入map中。

2.  返回到 createTransactionIfNecessary 方法中继续往下走, 此时我们我们依旧获取到了连接数据库的信息,当前事务的状态(是不是新事务)等信息。

3. 最后,就是线程绑定,放入到ThreadLocal中:

 

 3.2.2 事务的链式调用

 到这一步,就是事务的链式调用了。

源码case:

 @Transactional(propagation = Propagation.REQUIRED)@Overridepublic void transation(ConsultConfigArea area, ZgGoods zgGoods) {areaService.addArea( area);goodsService.addGoods(zgGoods);}
    @Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic int addArea(ConsultConfigArea area) {int i = commonMapper.addArea(area);return 1;}
   @Transactional(propagation = Propagation.NESTED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);throw new RuntimeException("异常");}

1、 我们在调用 transation 方法的时候, 由于它使用了 @Transactional(propagation = Propagation.REQUIRED)注解并且它是第一次调用,我们会在调用实际的transation方法之前先进入代理类,进行事务相关处理。 由于是第一次次进入,我们会生成一个事务,并且事务的 newTransation为true。

2. 调用 retVal = invocation.proceedWithInvocation() 进行火炬传递,也就是链式调用。

3. 我们会进入AreaServiceImpl的代理类,也就是再次把事务的相关流程再走一遍。由于在addArea方法的时候,我们使用了注解 @Transactional(propagation = Propagation.REQUIRES_NEW)。也就是说,挂起之前的事务并生成一个全新的事务,newTransation为true。

4. 调用 retVal = invocation.proceedWithInvocation() 进行火炬传递,继续链式调用。

5. 我们会进入GoodsServiceImpl 的代理类,也就是再次把事务的相关流程再走一遍。由于在addGoods方法的时候,我们使用了注解 @Transactional(propagation = Propagation.NESTED)。他会生成一个回滚点。针对 NESTED 这种传播属性传播属性并且是同一个线程再次创建事务对象,我们会把 newTransation设置为FALSE

6. 最后按顺序依次调用 

TransationServiceImpl.transation() --> areaService.addArea( area) --> goodsService.addGoods(zgGoods);

针对同一个线程,反复通过链式调用创建事务的过程。刚刚说了第一次创建事务,newTransation为true。 针对第2 、3、4...... 次的创建事务。我们会执行执行以下流程:

我们会根据不同的传播属性,执行不同的业务逻辑。核心就是处理连接数据库的connection对象实例。

如果是 传播属性是 PROPAGATION_REQUIRES_NEW,我们会完全在创建一个新的、独立的事务。newTransation为true

如果是 PROPAGATION_NESTED,我们继续使用之前的事务,也就是在调用transation 方法的时候创建的事务。newTransation为 false

3.2.3 事务的回滚。

由于我们执行最后一个方法的时候抛了异常信息:

 @Transactional(propagation = Propagation.NESTED)@Overridepublic void addGoods(ZgGoods zgGoods) {int i = commonMapper.addGood(zgGoods);throw new RuntimeException("异常");}

我们会进行异常的处理逻辑,处理完以后接着往上抛异常。

如何处理回滚的呢?

 

 最终的回滚方法如下:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {try {boolean unexpectedRollback = unexpected;try {triggerBeforeCompletion(status);//按照嵌套事务按照回滚点回滚if (status.hasSavepoint()) {if (status.isDebug()) {logger.debug("Rolling back transaction to savepoint");}status.rollbackToHeldSavepoint();}//都为PROPAGATION_REQUIRED最外层事务统一回滚else if (status.isNewTransaction()) {if (status.isDebug()) {logger.debug("Initiating transaction rollback");}doRollback(status);}else {// Participating in larger transactionif (status.hasTransaction()) {if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {if (status.isDebug()) {logger.debug("Participating transaction failed - marking existing transaction as rollback-only");}doSetRollbackOnly(status);}else {if (status.isDebug()) {logger.debug("Participating transaction failed - letting transaction originator decide on rollback");}}}else {logger.debug("Should roll back transaction but cannot - no transaction available");}// Unexpected rollback only matters here if we're asked to fail earlyif (!isFailEarlyOnGlobalRollbackOnly()) {unexpectedRollback = false;}}}catch (RuntimeException | Error ex) {triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);throw ex;}triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);// Raise UnexpectedRollbackException if we had a global rollback-only markerif (unexpectedRollback) {throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");}}finally {cleanupAfterCompletion(status);}}

1、首先就是判断右没有回滚点,如果有回滚点就按照回滚点进行回滚;

2.  如果没有回滚点,判断newTransation 是否为true,为true则直接回滚。

3.  既没有回滚点,newTransation 也为FALSE,走默认逻辑,最后接着往上抛异常;找到上一层调用方法,继续仅需1、2步逻辑判断,直到回滚为止。

4. 因为回滚是基于Connection进行的,如果部分子方法已经插入数据,并且传播属性为PROPAGATION_NEW, 那么这个子方法插入的数据不会回滚。

总结2:

  @Transactional(propagation = Propagation.REQUIRED)@Overridepublic void transation(ConsultConfigArea area, ZgGoods zgGoods) {areaService.addArea( area);goodsService.addGoods(zgGoods);}

本文的测试case中, transation方法使用的是REQUIRED,  addArea方法使用的是REQUIRED_NEW,  addGoods方法使用的是NESTED。 

addGoods抛异常,代码会按照回滚点进行回滚,然后接着往上抛异常;transation方法捕获到异常并且newTransation 为true,所以会直接回滚所有使用相同Connection对象进行数据库操作的子方法,即全员回滚。

但是,addArea方法的传播属性为REQUIRED_NEW, 它用于独立的Connection对象,与transation方法中持有Connection对象不是同一个,因此在transation捕获到异常并且调用它的connection进行回滚的时候,addArea方法插入的数据不会被回滚掉。


3.2.4. 伪代码分析:

try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.//火炬传递retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exception//事务回滚completeTransactionAfterThrowing(txInfo, ex);throw ex;}

其实核心伪代码就是以上这些。

假设 存在3个处理异常的一个类,并且这个类具有3个不同的实例a 、 b、 c, 那么久可以改写成这样:

        try {a.transation()}catch (Throwable ex) {completeTransactionAfterThrowing(txInfo, ex);throw ex;}
        try {b.addArea();}catch (Throwable ex) {completeTransactionAfterThrowing(txInfo, ex);throw ex;}
        try {c.addGoods();}catch (Throwable ex) {completeTransactionAfterThrowing(txInfo, ex);throw ex;}

a.transation()内部调用了b.addArea()  和 c.addGoods(); 

c.addGoods() 内部抛异常进行回滚,继续往上抛异常;

a.transation()捕获到异常,也进行回滚,此时回滚掉所有字方法更新的数据,接着往上抛异常;

由于回滚用到的是相同的connection对象,b.addArea()生成的事务中的connection不同于a.transation()生成的connection,因此b.addArea()更新的数据不会被回滚。

伪代码的方式进行分析,是非常款速、简洁的,可以重点使用;