使用 Spring Boot 处理事务回滚的建议

问题描述 投票:0回答:2

我有一个依赖 2 个数据源的 Spring Boot 应用程序:第三方 HTTP API 和 PostgresSQL 数据库。使用 Spring Data JPA 查询数据库,我已经为实体和相应的存储库设置了所有类。 有一种具有事务方法的服务,如下所示:

@Service
public class MyService {
 
    @Autowired
    private EntityRepository entityRepository;
    
    @Autowired
    private ApiService apiService;

    @Transactional
    public Entity createEntity(EntityInputData data) {
        this.apiService.createEntity(data); // HTTP call
        Entity entity = new Entity(data);
        return this.entityRepository.save(entity);
    }
}

据我了解,如果在执行

createEntity
期间抛出任何异常,由于
Entity
注释,
@Transactional
对象将不会持久化在数据库中。但是,如果最终未创建实体,则无法阻止或恢复 API 调用。

我尝试在

createEntity
中添加 try/catch,但我注意到提交事务时抛出的异常不会被捕获(即由于 PostgresSQL 错误),因为它们是在方法实际执行后抛出的:

    @Transactional
    public Entity createEntity(EntityInputData data) {
        string apiId = this.apiService.createEntity(entity); // HTTP call
        try {
            Entity entity = new Entity();
            return this.entityRepository.save(entity);
        } catch (Exception e) {
            // Not called if exception is thrown while committing transaction
            this.apiService.deleteEntity(apiId);
            throw e;
        }
    }

如果我将 try/catch 移到方法调用周围,那么我可以捕获这些异常。问题是我没有第三方 API 中处理回滚的上下文信息。

try {
    myService.createEntity(data);
} catch (Exception e) {
    // How do I call my API to tell it to rollback whatever has been created?
}

我找不到将数据库回滚反映到与我的用例匹配的外部服务的方法,有什么帮助吗?

java spring postgresql spring-boot jpa
2个回答
0
投票

搜索:传奇设计模式

在这种情况下,saga 编排似乎是最简单的方法。基本思想是将

MyService
分成单独的类,例如

// package private 
@Component
class LocalEntityHandler {

    @Transactional
    public Entity createEntity(EntityInputData data) {
        Entity entity = new Entity();
        return this.entityRepository.save(entity);
        }
    }
}

// package private 
@Component
class RemoteEntityHandler {

    public String createEntity(EntityInputData data) {
        return this.apiService.createEntity(entity);
    }

    public void deleteEntity(String apiId) {
        return this.apiService.createEntity(entity);
    }
}

然后

MyService
成为流程每个步骤的协调者:

@Service
public class MyService {
    private final LocalEntityHandler local;
    private final RemoteEntityHandler remote;
   // constructor omitted

    public Entity createEntity(EntityInputData data) {
        var apiId = remote.createEntity(data);  // track the context
        try {
            return local.createEntity(data);
        } catch (Exception e) {
            remote.delete(apiId);  // use the tracked context to clean up
            throw e;
        }
    }
}

并调用远程 API 在异常时删除实体。


0
投票

我发现这个解决方案使用 TransactionalEventListener 似乎可以正常工作。只需在调用 API 服务后发布一个事件,然后仅在事务中止时捕获该事件。由于我可以将所需的任何数据传递给事件,因此我可以轻松地再次调用我的服务来恢复更改:

我的服务:

@Service
public class MyService {
 
    @Autowired
    private EntityRepository entityRepository;
    
    @Autowired
    private ApiService apiService;

    @Transactional
    public Entity createEntity(EntityInputData data) {
        this.apiService.createEntity(data); // HTTP call
        Entity entity = new Entity(data);
        return this.entityRepository.save(entity);
    }
}

API服务:

@Service
public class ApiService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void createEntity(EntityInputData data) {
        // Do HTTP call
        ApiEvent event = new ApiEvent(/* Whatever data was returned by the API */);
        this.eventPublisher.publishEvent(event);
    }

    public void deleteEntity(data) {
        // Do HTTP call
    }
}

然后声明一个用于处理事件的组件:

@Component
public class ApiEventHandler {

    @Autowired
    private ApiService apiService;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    void onCreateFailed(ApiEvent event) {
        apiService.deleteEntity(event.data)
    }
}

这个想法是只有在发生回滚时才会处理该事件。另外,我不再需要处理 try/catch,一切都像我的 ApiService 中的方法也是事务性的一样工作。

© www.soinside.com 2019 - 2024. All rights reserved.