spring事务为何失效?正确的spring事务使用方式

12 篇文章 15 订阅
4 篇文章 0 订阅

概述

我们知道spring事务分声明式事务编程式事务;编程式事务是由用户手动去开启事务并手动提交或回滚,一般不用,声明式事务是由用户将一段业务逻辑声明为事务,由spring容器代理完成,用spring事务注解@Transactional即可声明,但是事务注解@Transactional使用不当会使事务失效,下面我们就来盘点spring声明式事务的正确使用方式


定义一个controller,并注入service来调用事务方法

@Controller
@RequestMapping("brand")
public class BrandController {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandController.class);
    @Autowired
    private BrandService brandService;
	// 新增品牌
    @PostMapping
    public ResponseEntity<Void> saveBrand(Brand brand, @RequestParam("cids") List<Long> cids) {
        if (StringUtils.isBlank(brand.getName()) || StringUtils.isBlank(brand.getLetter().toString())
                || CollectionUtils.isEmpty(cids)) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
        brandService.saveBrand(brand, cids);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
 }

1. @Transactional注解的方法只有被public修饰才有效,其他修饰符无效

如下代码,controller层调用service层的saveBrand方法,由于saveBrand是被protected修饰的,所以当15行发生异常,11行和13行新增数据并不会回滚

@Service
public class BrandService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandService.class);
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private CategoryDao categoryDao;
    
    @Transactional
    protected void saveBrand(Brand brand, List<Long> cids) {
        int count = brandDao.insertBrand(brand);
        if (count > 0) {
            categoryDao.insertCategoryAndBrand(cids, brand.getId());
        }
       	......	// 假如此处发生异常
    }
}

解决办法:将protected修饰符改成public

2. 在当前类其他没有@Transactional注解的方法中调用@Transactional注解的方法

如下代码,当第19行发生异常时,17行和10行新增数据入库并不会回滚,事务没生效;其原因是因为@Transactional注解声明式事务时基于AOP实现的,AOP底层是通过代理模式实现的,也就是说事务回滚机制是存在方法代理类的方法增强逻辑中;以下代码在saveBrand方法调用this的事务方法saveCategory并不会通过代理类,所以事务并未生效

@Service
public class BrandService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandService.class);
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private CategoryDao categoryDao;
    
    public void saveBrand(Brand brand, List<Long> cids) {
        int count = brandDao.insertBrand(brand);
       	this.saveCategory(brand, cids, count);
    }
    
	@Transactional
	public void saveCategory(Brand brand, List<Long> cids, int count) {
		if (count > 0) {
           	categoryDao.insertCategoryAndBrand(cids, brand.getId());
        }
       	......	// 假如此处发生异常
	}
}

解决方法:需要在同一个事务的入库操作都写在同一个方法中,如下

@Service
public class BrandService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandService.class);
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private CategoryDao categoryDao;
    
    @Transactional
    public void saveBrand(Brand brand, List<Long> cids) {
        int count = brandDao.insertBrand(brand);
		if (count > 0) {
           	categoryDao.insertCategoryAndBrand(cids, brand.getId());
        }
       	......	// 假如此处发生异常
	}
}

3. 异常处理导致事务失效

如下代码,当16行发生异常时,12行和14行的入库操作并未回滚,原因是因为我们对异常进行了捕获,并未代理类执行回滚操作

@Service
public class BrandService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandService.class);
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private CategoryDao categoryDao;

    @Transactional
    public void saveBrand(Brand brand, List<Long> cids) {
        try {
            int count = brandDao.insertBrand(brand);
            if (count > 0) {
                categoryDao.insertCategoryAndBrand(cids, brand.getId());
            }
            ......	// 假如此处发生异常
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

**解决办法:**手动设置事务回滚,在catch中实现

	@Transactional
    public void saveBrand(Brand brand, List<Long> cids) {
        try {
            int count = brandDao.insertBrand(brand);
            if (count > 0) {
                categoryDao.insertCategoryAndBrand(cids, brand.getId());
            }
            ......	// 假如此处发生异常
        } catch (Exception e) {
            e.printStackTrace();
            // 手动设置事务回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

或者在@Transactional注解中声明事务回滚支持的异常类型

 @Transactional(rollbackFor = Exception.class)

另外,直接抛出异常也是有效的

	@Transactional
    public void saveBrand(Brand brand, List<Long> cids) throws Exception {
        int count = brandDao.insertBrand(brand);
        if (count > 0) {
            categoryDao.insertCategoryAndBrand(cids, brand.getId());
        }
    }

4. 关于嵌套事务回滚机制

有时候我们会遇到一个事务方法中会涉及到多张表更新操作,这可能涉及到多事务操作,如以下代码
,在BrandService事务方法中saveBrand引入了CategoryService事务方法insertCategory,由于我们捕获了异常,如果13行发生异常,理论上应该是13行的insertCategory事务回滚,saveBrand事务不回滚。但是实际上整个saveBrand事务都回滚了,这是因为嵌套事务中,任意一个子事务发生异常回滚,整个主事务下的所有事务都回滚,这是嵌套事务的特别之处

public class BrandService {
    private static final Logger LOGGER = LoggerFactory.getLogger(BrandService.class);
    @Autowired
    private BrandDao brandDao;
    @Autowired
    private CategoryService categoryService;

    @Transactional
    public void saveBrand(Brand brand, List<Long> cids) {
        try {
            int count = brandDao.insertBrand(brand);
            if (count > 0) {
                categoryService.insertCategory(brand.getId(), cids);
            }
        } catch (Exception e) {
            LOGGER.error("error", e);
        }
    }
}
public class CategoryService {
    @Autowired
    private CategoryDao categoryDao;

    @Transactional
    public void insertCategory(Long bid, List<Long> cids) {
        categoryDao.insertCategoryAndBrand(cids, bid);
        ......	// 假如此处发生异常   
    }
}

解决办法: 如果我们子事务回滚不影响主事务的提交,可以声明子事务的传播策略,如下


@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertCategory(Long bid, List<Long> cids) {
    categoryDao.insertCategoryAndBrand(cids, bid);
    ......	// 假如此处发生异常   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值