你好,我是miniluo,今天我和你聊聊Spring声明式事务不生效的坑。
下面就让我和你一起学习有哪些几种情况下Spring声明式事务不生效的坑。
没有正确理解@Transactional注解
你是否曾经写过和下文类似的代码?
@Service("productService")
public class ProductServiceImpl implements IProductService {
@Override
public void createProduct(Product product) {
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
//请求内部方法创建产品
product.setProductName("Spring声明式事务");
executeCreateProduct(product);
}
@Transactional
private void executeCreateProduct(Product product){
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
}
product.create();
//模拟出现异常期待事务回滚
int zero = 1/0;
}
}
经过测试,我们发现抛出了异常,可是产品还是被创建了。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,这么说我们修改为public方法应该有效果。改完后再尝试发现产品还是被创建。这里不得不说下@Transactional生效必须通过代理过的类从外部调用目标方法才能生效。知道这个原因后,我们把ProductServiceImpl以自身的方式注入,调整代码如下:
@Service("productService")
public class ProductServiceImpl implements IProductService {
@Autowired
private ProductServiceImpl self;
@Override
public void createProduct(Product product) {
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
product.setProductName("Spring声明式事务");
self.executeCreateProduct(product); //请求内部方法创建产品
}
@Transactional
public void executeCreateProduct(Product product){
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
}
product.create();
int zero = 1/0; //模拟出现异常期待事务回滚
}
}
再次测试,我们发现产品并没有被创建,说明事务已生效,因为self 是由 Spring 通过 CGLIB 方式增强过的类。这种方式虽然能解决,但是并不建议这么做,而应该把@Transactional写在接口的实现方法上,建议把查询和更新(新增)分开。
异常处理不当导致事务失效
情况一:异常无法从方法传播出去,导致事务失效
@Service("productService")
@Slf4j
public class ProductServiceImpl implements IProductService {
@Override
@Transactional
public void createProduct(Product product) {
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
product.setProductName("Spring声明式事务");
try {
executeCreateProduct(product); //请求内部方法创建产品
}catch (Exception ex){
log.error("创建产品失败",ex);
}
}
private void executeCreateProduct(Product product){
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
}
product.create();
int zero = 1/0; //模拟出现异常期待事务回滚
}
}
这种写法在实际编码中常犯的错误,若要使其生效,只需在catch中增加
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
情况二:受检异常导致事务不生效
@Service("productService")
@Slf4j
public class ProductServiceImpl implements IProductService {
@Override
@Transactional
public void createProduct(Product product)
throws IOException {
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
product.setProductName("Spring声明式事务");
executeCreateProduct(product); //请求内部方法创建产品
}
private void executeCreateProduct(Product product)
throws IOException {
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
}
product.create();
Files.readAllLines(Paths.get("transaction-not-rollback"));
}
}
或许有这么个场景,这些代码并非你写的,你并不知道外层或里面的业务逻辑实现,改动可能会有影响存量功能;这个时候我们该怎么办呢?其实也不难,@Transactional已经为我们给出了解决方案,只需将@Transactional调整为
@Transactional(rollbackFor = Exception.class)
至此,我和你一起回顾了平时编码过程中常见的坑,下面再一起来看看关于事务传播性的坑。
事务传播性
我们一起来看下面的两段代码,第一段是实现创建产品和产品上架。
@Service("productService")@Slf4j
public class ProductServiceImpl implements IProductService {
@Autowired
private IOfferService offerService;
/**
* 期望:产品的创建并不会因上架失败导致回滚
* @param product
*/
@Override
@Transactional
public void createProduct(Product product){
//此处可能会实现一些业务逻辑代码,判断是否满足创建产品的条件
product.setProductName("Spring声明式事务");
executeCreateProduct(product); //请求内部方法创建产品
if(Status.UPSHELVE.equals(product.getStatus())){
offerService.productUpShelve(product);//创建产品后,产品随即上架
}
}
private void executeCreateProduct(Product product) {
if(Objects.isNull(product)){
throw new RuntimeException("无法创建空产品");
}
product.create();
}
}
第二段是产品上架的实现。
@Service("offerService")
public class OfferServiceImpl implements IOfferService {
@Override
@Transactional
public void productUpShelve(Product product) {
product.upShelve();
throw new RuntimeException("上架失败");
}
}
我们的期望是产品的创建成功与否不应该受到上架业务代码影响,我们测试后发现,产品上架失败,导致创建产品回滚;这是什么原因呢?这是因为创建产品的事务和产品上架的事务是同一个事务,所以上架失败自然会导致创建产品回滚。了解事务传播性的同学都知道,我们只需要在产品上架的方法配置事务的传播行为即可,告诉@Transactional,我是要自己独立事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
好了,关于Spring声明式事务常踩的3个坑已经和你一起学习完,期待对你有所帮助和启发。
思考和讨论
1、文中我们通过注入自己的方式解决了事务不生效的情况,请问能否使用this来完成呢?this.executeCreateProduct(product);
2、说到事务的传播行为,除了我所说的之外,还有哪些呢?
欢迎留言与我分享和指正!也欢迎你把这篇文章分享给你的朋友或同事,一起交流。
感谢您的阅读,我们下节再见!
关注我们的公众号,不定时分享技术、管理、业务等不同领域的文章与您一起学习交流。也欢迎投稿:xiaouoron@foxmail.com
来源:oschina
链接:https://my.oschina.net/u/933229/blog/3213669