概述
我们知道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);
...... // 假如此处发生异常
}