spring声明式事务(@Transactional)失效场景

181 阅读3分钟

声明式事务(@Transactional)

基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。但是声明式事务用不对在某些场景下容易失效。

代码 demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Override
    @Transactional
    public String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        return super.save(msLog).getLogId().toString();
    }
}

目标类没有被spring管理

demo

//@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Override
    @Transactional
    public String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        return super.save(msLog).getLogId().toString();
    }
}

因为@Transactional 声明式事务基于spring的aop实现的,LogDAOImpl没有被spring管理,就无法开始事务。

同一个类内方法调用

demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

     @Override
    public String save(){
        LogAggregateRoot log = new LogAggregateRoot();
        return this.saveLog(log);
    }

    @Override
    @Transactional
    public String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        return super.save(msLog).getLogId().toString();
    }
}

因为@Transactional 声明式事务基于spring的aop实现的,同一个类内部方法调用没有经过代理,外部调用方法save() -> ProxyLogDAOImpl.save(),方法save()在类内部调用方法saveLog(LogAggregateRoot log) -> LogDAOImpl.saveLog(LogAggregateRoot log),造成事务失效。

事务方法不是 public

demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Override
    @Transactional
    private String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        return super.save(msLog).getLogId().toString();
    }
}

spring进行事务代理时候获取方法的属性,但是非public的方法无法被获取到属性,不会开始事务,故事务失效。

异常被业务代码 catch

demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Override
    @Transactional
    private String saveLog(LogAggregateRoot log){
        try {
          MsLog msLog = new MsLog();
          msLog.setUserId(log.getUserId());
          return super.save(msLog).getLogId().toString();
        }
        catch (Exception e) {
          logger.error("LogDAOImpl exception", e);
        }
       return null;
    }
}

类的业务代码中直接把异常catch住了,Spring自然就 catch不到异常,因此事务回滚的逻辑就不会执行,事务就失效了。

rollbackFor属性配置错误

demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Override
    @Transactional
    private String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        throw new Exception("保存日志错误");
        return super.save(msLog).getLogId().toString();
    }
}

因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下 @Transactional(rollbackFor = Exception.class)

事务传播属性使用错误

demo

@Service
public class LogDAOImpl extends AbstracRepositoryImpl implements ILogDAO {

    @Lazy
    @Autowired
    private LogDAOImpl logDAOImpl;

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public String save(){
        LogAggregateRoot log = new LogAggregateRoot();
        return logDAOImpl.saveLog(log);
    }

    @Override
    public String saveLog(LogAggregateRoot log){
        MsLog msLog = new MsLog();
        msLog.setUserId(log.getUserId());
        return super.save(msLog).getLogId().toString();
    }
}

我们先对 Propagation类中 Spring事务传播机制进行总结: PROPAGATION_REQUIRED:要求使用事务,如果当前没有事务,则创建一个新的事务,如果当前存在事务,就加入该事务,该设置是默认也是最常用的设置。 PROPAGATION_SUPPORTS:支持使用事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。 PROPAGATION_MANDATORY:强制使用事务,如果当前存在事务,就加入该事务,如果当前不存在事务,则抛出异常。 PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。 PROPAGATION_NOT_SUPPORTED:不支持事务,如果当前存在事务,就把当前事务挂起。 PROPAGATION_NEVER:不允许使用事务,如果当前存在事务,则抛出异常。 PROPAGATION_NESTED:内嵌事务,如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。 上述 save()方法的事务传播机制是 Propagation.SUPPORTS,也就是当前方法有事务就假如事务,不存在事务则以非事务执行,因为 saveLog(LogAggregateRoot log)方法不存在事务,所以该方法就以非事务执行,因此事务失效。 此案例是典型的 Spring事务传播机制使用错误,我们只需要将 @Transactional(propagation = Propagation.SUPPORTS) 修改成 @Transactional(propagation = Propagation.REQUIRED),事务就可以生效了。

数据库不支持事务

re:mysql

MySQL 数据库的MyISAM 引擎不支持事务,而 InnoDB 引擎支持事务。