【问题标题】:@Transactional with handling error and db-inserts in catch block (Spring Boot)@Transactional 在 catch 块中处理错误和 db-inserts (Spring Boot)
【发布时间】:2022-01-01 05:50:26
【问题描述】:

我想在发生错误的情况下回滚数据的事务,同时将错误写入db。

我无法处理事务性注释。

以下代码会产生运行时错误 (1/0),但仍会将数据写入数据库。并将数据写入错误表。

我尝试了几种变体,并在 StackOverflow 中遵循了类似的问题,但没有成功。

谁有提示,怎么办?

@Service
public class MyService{

       @Transactional(rollbackFor = Exception.class)
        public void updateData() {
            try{
                processAndPersist();    // <- db operation with inserts
                int i = 1/0; // <- Runtime error
            }catch (Exception e){
                persistError()
                trackReportError(filename, e.getMessage());
            }
        }
    
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void persistError(String message) {
            persistError2Db(message); // <- db operation with insert
        }

【问题讨论】:

    标签: spring-data-jpa spring-transactions


    【解决方案1】:

    您需要在updateData() 方法中抛出异常来回滚事务。并且不能同时回滚persistError()事务。

    @Transactional(rollbackFor = Exception.class)
            public void updateData() {
                try{
                    processAndPersist();    // <- db operation with inserts
                    int i = 1/0; // <- Runtime error
                }catch (Exception e){
                    persistError()
                    trackReportError(filename, e.getMessage());
                    throw ex; // if throw error here, will not work
    
                }
            }
    

    仅仅抛出一个错误不会有帮助,因为persistError() 将拥有与updateData() 相同的事务。因为persistError() 是使用this 引用调用的,而不是对代理的引用。

    解决方案

    1. 使用自引用。
    2. 使用自注入Spring self injection for transactions
    3. persistError() 的呼叫移到updateData() 之外(和事务)。从persistError() 中删除@Transactional(它将不起作用)并在persistError2Db() 中使用Repository 的事务。
    4. persistError() 移动到单独的服务界面。在这种情况下,它将使用代理调用。
    5. 不要使用声明性事务(带有@Transactional 注释)。使用程序化事务管理手动设置事务边界https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s06.html

    还请记住,persistError() 也会产生错误(而且很有可能会产生错误)。

    使用自我参考

    你可以使用MyService的自引用来进行事务,因为你可以调用的不是MyServiceImpl的方法,而是Spring代理的方法。

    @Service
    public class MyServiceImpl implements MyService {
    
        public void doWork(MyService self) {
            DataEntity data = loadData();
    
            try {
                self.updateData(data);
            } catch (Exception ex) {
                log.error("Error for dataId={}", data.getId(), ex);
                self.persistError("Error");
                trackReportError(filename, ex);
            }
        }
    
        @Transactional
        public void updateData(DataEntity data) {
            persist(data);    // <- db operation with inserts
        }
    
        @Transactional
        public void persistError(String message) {
            try {
                persistError2Db(message); // <- db operation with insert
            } catch (Exception ex) {
                log.error("Error for message={}", message, ex);
            }
        }
    }
    
    public interface MyService {
    
        void doWork(MyService self);
        
        void updateData(DataEntity data);
    
        void persistError(String message);
    
    }
    
    

    使用

    MyService service = ...;
    service.doWork(service);
    

    【讨论】:

    • 感谢您的帮助。我完全按照您描述的方式进行了尝试,但出现“javax.persistence.TransactionRequiredException:执行更新/删除查询”错误。如果没有自引用,就没有其他更简单的方法吗?对我来说,这看起来“容易出错”。
    • @mcflysoft 你需要从 MyService service = ... 的 Spring 上下文中获取服务。另一种方法是将 persistError() 移动到单独的服务中。最好不要将其称为原始交易。
    • @mcflysoft 你是从哪里得到这个异常的?
    • @mcflysoft 我已经更新了我的答案。
    猜你喜欢
    • 2019-02-26
    • 2013-01-21
    • 1970-01-01
    • 1970-01-01
    • 2020-10-08
    • 2023-01-10
    • 1970-01-01
    • 2019-11-12
    • 2017-06-13
    相关资源
    最近更新 更多