深入Spring数据库事务管理

相关问题

一、@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;
}













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值