欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

深入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;
}