【问题标题】:Exclude private method execution from transaction rollback从事务回滚中排除私有方法执行
【发布时间】:2021-12-10 20:53:13
【问题描述】:

我的项目中有一个服务方法,可以这样简化:

@Transactional
public void myTransactionalMethod(){
    try {
        soSomethingAndSaveOnDB(); // private method
    }catch (Exception exception){
        saveSomethingOnDBWhenAnErrorOccurs(); // private method
        throw exception;
    }
}

该方法是事务性的,并且正在执行一些可能导致抛出异常的主要逻辑。如果出现这种情况,则必须将保存在数据库中的所有数据恢复到以前的状态。

但我还需要在catch 块中处理抛出的异常,并在数据库中写入一些额外的数据来跟踪错误。

我的实现的问题是最终的throw exception 指令导致事务回滚并阻止创建与错误跟踪相关的数据。

我想在这种情况下“禁用”事务回滚,以便在我的数据库中正确记录错误。

注意我正在重新抛出异常以将其传播到控制器并将其转换为 HTTP 错误。

保留soSomethingAndSaveOnDB()saveSomethingOnDBWhenAnErrorOccurs() 方法private,这可能是一个很好的解决方案?

【问题讨论】:

  • 是否需要从catch 块中抛出异常尚不清楚。无论如何,此时您已经完成了对数据库的登录。
  • 你在saveSomethingOnDBWhenErrorOccurs上尝试过Propagation.REQUIRES_NEW吗?
  • 然而,如果你必须抛出,那么抛出一个不同的异常,比如AnotherException,它可以使用@Transactional( dontRolbackOn = AnotherException.class )被“排除”。
  • @SreeKumar 登录到数据库已经完成是对的,但我还需要将异常传播到控制器并将其转换为 HTTP 错误。所以;是的,我需要重新抛出异常。谢谢
  • @Marc 正如我在问题中指定的那样, saveSomethingOnDBWhenErrorOccurs 是一个私有方法,声明式事务处理不应该在私有方法上工作。

标签: java spring spring-boot spring-transactions


【解决方案1】:

这似乎更像是一种解决方法而不是解决方案,但目前是我按照 cmets 中的建议找到的最佳方法。

我这样修改了我的方法:

@Transactional(noRollbackFor = { ExceptionWrapper.class })
public void myTransactionalMethod(){
    try {
        soSomethingAndSaveOnDB(); // private method
    }catch (Exception exception){
        saveSomethingOnDBWhenAnErrorOccurs(); // private method
        throw new ExceptionWrpper(exception);
    }
}

然后我定义了一个这样的类:

public class ExceptionWrapper extends RuntimeException {
    private final RuntimeException wrappedThrowable;
}

最后在我的控制器中我捕获了新的异常类型,这样:

try {
    myTransactionalMethod();
}catch(ExceptionWrapper ew){
    throw ew.getWrappedThrowable();
}

【讨论】:

    【解决方案2】:

    我想到了解决这个问题的几种方法:

    1. 使用TransactionTemplate 和PROPAGATION_REQUIRES_NEW 范围在saveSomethingOnDBWhenAnErrorOccurs 方法中以编程方式处理事务。这样保存错误日志将在单独的事务中完成。 例如:
        private final TransactionTemplate transactionTemplate;
    
        private void saveSomethingOnDBWhenAnErrorOccurs(){
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            transactionTemplate.execute(status-> repository.saveErrorLog());
        }
    
    1. 使用自注入或ApplicationContext调用日志记录方法。但是这种方式需要公开saveSomethingOnDBWhenAnErrorOccurs 方法。例如,假设你的班级叫MyService,那么:
        @Autowired
        private SomeRepository someRepository;
        @Autowired
        private ApplicationContext applicationContext;
    
        @Transactional
        public void myTransactionalMethod(){
            try {
                soSomethingAndSaveOnDB(); // private method
            }catch (Exception exception){
                LoggingData loggingData = ...
                applicationContext.getBean(MyService.class).saveSomethingOnDBWhenAnErrorOccurs(someRepository, loggingData); // private method
                throw new ExceptionWrpper(exception);
            }
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public Document saveSomethingOnDBWhenAnErrorOccurs(SomeRepository someRepository, LoggingData loggingData) {
            return someRepository.save(document);
        }
    
    1. 使用额外的抽象层,例如:
    ┌────────────┐
    │ Controller │
    └──────┬─────┘
           │
           │
     ┌─────▼─────┐
     │ MyService │
     └─────┬─────┘
           │
           │           ┌────────────────────────────────────────────────────┐
           │ OnError   │@Transactional(propagation=Propagation.REQUIRES_NEW)│
           └──────────►│ErrorLogging.saveSomethingOnDBWhenAnErrorOccurs     │
                       └────────────────────────────────────────────────────┘
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-03
      • 2013-06-01
      • 2013-05-05
      • 1970-01-01
      • 1970-01-01
      • 2012-07-15
      相关资源
      最近更新 更多