我有一个依赖 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?
}
我找不到将数据库回滚反映到与我的用例匹配的外部服务的方法,有什么帮助吗?
搜索:传奇设计模式
在这种情况下,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 在异常时删除实体。
我发现这个解决方案使用 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 中的方法也是事务性的一样工作。