首页 > 解决方案 > 在多个资源请求的情况下还原更改的最佳实践是什么?

问题描述

我有一种方法可以对与文件系统和数据库交互的某些服务进行多次调用。问题是我实际上不知道如何正确实施错误处理,以防中途出现问题。好吧,就我而言,Spring 会使用@Transactional注释恢复所有数据库更改。但是文件系统呢?目前我想到的一切都是这样的:

public void myMethod() throws MyMethodException{
    try {
        doFirstThingWithDatabase();
        doSecondThingWithDatabase();
        doFirstThingWithFileSystem();
        doSecondThingWithFileSystem();
    } catch(FirstDatabaseThingException e) {
        logger.error(e.getMessage());
        throw new MyMethodException(e);
    } catch(SecondThingWithDatabaseException(e) {
        logger.error(e.getMessage());
        revertFirstDatabaseChange();
        throw new MyMethodException(e);
    } catch(FirstFSThingException e) {
        logger.error(e.getMessage());
        revertFirstDatabaseChange();
        revertSecondDatabaseChange(); //and what if first reversion is impossible and also throws an exception?
        throw new MyMethodException(e);
    } catch(SecondFSThingException e) {
        logger.error(e.getMessage);
        revertFirstDatabaseChange();
        revertSecondDatabaseChange();
        revertFirstFSChange();
        throw new MyMethodException(e);
    }

}

但这对我来说似乎很难看,因为我重复了一些代码。这种情况的最佳做法是什么?

标签: javaspringexception

解决方案


您可以使用 Spring 事件和事件侦听器。

在 DB 服务中,在执行可能引发 DB 异常的语句之前,发布一个事件,TransactionalEventListener如果出现事务回滚,则允许执行特定的回滚处理。
这可能看起来像:

@Transactional
public void doFirstThingWithDatabase(Foo foo) {
    applicationEventPublisher.publishEvent(new FooInsertionEvent(foo));
    fooRepo.save(foo);
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void rollBackFooInsertion(FooInsertionEvent fooInsertionEvent) { 
    String info = fooInsertionEvent.getAnyRequiredInfo();
    // use that info to make the DB in the correct state
}

这样,与该数据库异常相关的自定义逻辑回滚将在没有任何捕获的情况下执行。对第二个数据库异常案例应用相同的想法。

最后,您的代码可能如下所示:

public void myMethod() throws MyMethodException{
    try {
        doFirstThingWithDatabase(); // processed by the listener
        doSecondThingWithDatabase();  // processed by the listener
        doFirstThingWithFileSystem();  // processed below when doSecondThingWithFileSystem() fails
        doSecondThingWithFileSystem(); // no required
    } catch(SecondFSThingException e) {
        logger.error(e.getMessage);
        revertFirstFSChange();
        throw new MyMethodException(e);
    }
    catch(Exception e) {
        logger.error(e.getMessage);
        throw new MyMethodException(e);
    }

}

推荐阅读