深入Spring数据库事务管理
程序员文章站
2022-03-30 10:14:46
...
相关问题
一、@Transactional的失效问题
1.对于静态(static)方法和非public方法,注解@Transactional是失效的。
2.自调用,就是一个类的一个方法去调用自身另外一个方法的过程。如下:
@Autowired
private RoleDao roleDao;
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public int insertRole(Role role){
return roleDao.insertRole(role);
}
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public int insertRoleList(List<Role> roleList) {
int count = 0;
for (Role role:roleList) {
try {
//调用自身类的方法,产生自调用问题
insertRole(role);
count++;
}catch (Exception ex){
log.info(ex);
}
}
return count;
}
分析:角色插入两次都使用的是同一事务,也就是说在insertRole方法上标注的@Transactional失效了。原因在于AOP的实现原理,这里不再赘述。
解决:①使用两个服务类。【推荐】
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleDao roleDao;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public int insertRole(Role role) {
return roleDao.insertRole(role);
}
}
②从IOC容器中获取RoleService代理对象。【不推荐,从容器获取代理对象有侵入之嫌,我们需要依赖于SpringIOC容器】@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public int insertRoleList(List<Role> roleList) {
int count = 0;
//从容器中获取RoleService对象,实际是一个代理对象
RoleService roleService = ctx.getBean(RoleService.class);
for (Role role:roleList) {
try {
insertRole(role);
count++;
}catch (Exception ex){
log.info(ex);
}
}
return count;
}
二、典型错误用法
1.错误使用Service
场景:在一个Controller中插入两个角色,并且两个角色需要在同一个事务中处理。
错误代码:
public class RoleController {
@Autowired
private RoleService roleService;
public void errorUseServices(){
Role role = new Role();
role.setRoleName("role_name_");
role.setNote("note_");
roleService.insertRole(role);
Role role2 = new Role();
role2.setRoleName("role_name_2");
role2.setNote("note_2");
roleService.insertRole(role2);
}
}
分析:如果这个service标注用@Transactional,那么它就会启用一个事务,而一个service方法完成后,它就会释放该事务,所以前后两个insertRole是在两个不同的事务中完成的。这样如果第一个插入成功了,第二个插入失败了,就会使数据库数据库数据不完全同时成功或者失败,可能产生严重的数据不一致的问题。2.过长时间占用事务
场景:在插入角色后还需要操作一个文件,比如处理图片的上传之类的操作。
代码:
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public int insertRole(Role role) {
//return roleDao.insertRole(role);
int result = roleDao.insertRole(role);
//做一些与数据库无关的操作
doSomethingForFile();
return result;
}
分析:当insertRole方法结束后Spring才会释放数据库事务资源,也就是说要等到doSomethingForFile()方法执行完成后,返回result后才会关闭数据库资源。高并发情况下就会出现卡顿状态,甚至因得不到数据库资源而导致系统宕机。
解决:这个方法放在controller中执行
@RequestMapping("/addRole")
@ResponseBody
public Role addRole(Role role){
roleService.insertRole(role);
//做一些与数据库无关的操作
doSomethingForFile();
return role;
}
3.错误捕捉异常
场景:购买商品。其中ProductService是产品服务类,而TransactionService是记录交易信息,需求就是产品减库存和保存交易在同一个事务例,要么同时成功,要么同时失败。假设传播行为都是REQUIRED。
代码:
@Autowired
private ProductService productService;
@Autowired
private TransactionService transactionService;
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public int doTransaction(TransactionBean trans){
int result = 0;
try {
//介绍库存
productService.decreaseStock(trans.getProductId, trans.getQuantity());
//如果介绍库存成功则保存记录
if(result>0){
transactionService.save(trans);
}
}catch (Exception ex){
//自行处理异常代码
//记录异常日志
log.info(ex);
}
return result;
}
分析:这里的问题是方法已经存在异常了,由于开发者不了解Spring的事务约定,在两个操作方法里面加入了自己的try.catch...语句,就可能发生这样的结果:当减少库存成功了,但是保存交易信息时失败而发生了异常。由于加入的try.catch...语句,Spring在数据库事务所约定的流程中再也得不到任何异常信息了,此时Spring就会提交事务,啊这样就出现了库存减少,而交易记录却没有的糟糕情况。解决:在catch中自行抛出异常,这样在Spring的事务流程汇总,就会捕捉到这个异常,进行事务回滚。
@Autowired
private ProductService productService;
@Autowired
private TransactionService transactionService;
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public int doTransaction(TransactionBean trans){
int result = 0;
try {
//介绍库存
productService.decreaseStock(trans.getProductId, trans.getQuantity());
//如果介绍库存成功则保存记录
if(result>0){
transactionService.save(trans);
}
}catch (Exception ex){
//自行处理异常代码
//记录异常日志
log.info(ex);
//自行抛出异常,让Spring事务管理流程获取异常,进行事务管理
throw new RuntimeException(ex);
}
return result;
}
推荐阅读
-
spring+mybatis利用interceptor(plugin)兑现数据库读写分离
-
spring+mybatis利用interceptor(plugin)兑现数据库读写分离
-
深入探讨:PHP使用数据库永久连接方式操作MySQL的是与非
-
【MySQL】数据库事务深入分析
-
Spring+SpringMVC配置事务管理无效原因及解决办法详解
-
springboot使用spring-data-jpa操作MySQL数据库
-
Spring中校验器(Validator)的深入讲解
-
深入分析C#连接Oracle数据库的连接字符串详解
-
详解Spring Boot中使用Flyway来管理数据库版本
-
深入Spring Boot之ClassLoader的继承关系和影响