目录
1. 传播属性
Spring特有一套处理事务处理的逻辑,而今天要讲的传播属性就是Spring独有的。下面对传播属性进行介绍:
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
以上传播属性,使用最高频的是: PROPAGATION_REQUIRED、PROPAGATION_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;
@Before
public void before() {
applicationContext = new AnnotationConfigApplicationContext(ComponentScanBean.class);
}
@Test
public void test1() {
AreaService bean = applicationContext.getBean(AreaService.class);
Map param = new HashMap();
param.put("areaCode","1001");
bean.queryAreaFromDB(param);
}
@Test
public void addAreaTest() {
AreaService bean = applicationContext.getBean(AreaService.class);
ConsultConfigArea area = new ConsultConfigArea();
area.setAreaCode("VV1");
area.setAreaName("VV1");
area.setState("1");
bean.addArea(area);
}
@Test
public 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);
}
@Test
public 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 {
@Autowired
AreaService areaService;
@Autowired
GoodsService goodsService;
@Autowired
CommonMapper commonMapper;
//开启了事务
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
//try {
areaService.addArea( area);
goodsService.addGoods(zgGoods);
/* }catch (Exception e) {
}*/
}
//提交事务
@Transactional
@Override
public 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;
}
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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)
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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)
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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)
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.REQUIRED)
@Override
public 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)
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public 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)
@Override
public 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 {
@Autowired
AreaService areaService;
@Autowired
GoodsService goodsService;
@Autowired
CommonMapper commonMapper;
//开启了事务
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
//try {
areaService.addArea( area);
goodsService.addGoods(zgGoods);
/* }catch (Exception e) {
}*/
}
//提交事务
@Transactional
@Override
public 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;
}
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.NESTED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.NESTED)
@Override
public void addGoods(ZgGoods zgGoods) {
int i = commonMapper.addGood(zgGoods);
throw new RuntimeException("异常");
}
@Transactional(readOnly = true)
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public 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 {
@Autowired
AreaService areaService;
@Autowired
GoodsService goodsService;
@Autowired
CommonMapper commonMapper;
//开启了事务
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
try {
areaService.addArea( area);
goodsService.addGoods(zgGoods);
}catch (Exception e) {}
}
//提交事务
@Transactional
@Override
public 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;
}
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public 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());
@Autowired
private CommonMapper commonMapper;
@Autowired
AreaService areaService;
@Transactional(propagation = Propagation.NESTED)
@Override
public 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;
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
CommonMapper commonMapper;
@Transactional(propagation = Propagation.NESTED)
@Override
public void addGoods(ZgGoods zgGoods) {
int i = commonMapper.addGood(zgGoods);
throw new RuntimeException("异常");
}
@Transactional(readOnly = true)
@Override
public 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的那一套流程进行调用,最终调用到了事务切面。
贴出这个方法的全部源码,对字方法进行逐步分析:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
//获取事务属性类 AnnotationTransactionAttributeSource
TransactionAttributeSource 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, 我们拿不到连接数据库的信息。
@Override
protected 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)
@Override
public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
areaService.addArea( area);
goodsService.addGoods(zgGoods);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int addArea(ConsultConfigArea area) {
int i = commonMapper.addArea(area);
return 1;
}
@Transactional(propagation = Propagation.NESTED)
@Override
public 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)
@Override
public 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 transaction
if (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 early
if (!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 marker
if (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)
@Override
public 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()更新的数据不会被回滚。
伪代码的方式进行分析,是非常款速、简洁的,可以重点使用;