Java执行方法时设DB状态为in progress,异常时回滚状态的问题
问题分析
当前代码的核心问题在于使用独立线程更新"in progress"状态:
- 独立线程的事务会单独提交,当主线程发生异常时,该状态已经持久化到数据库,无法回滚。
getOne返回的是实体代理,若主线程事务结束后线程才执行,可能导致代理脱离上下文,引发保存失败。- 未对"some code..."部分的异常进行捕获,一旦抛出异常,状态会停留在"in progress"。
解决方案
1. 移除独立线程,统一事务/分事务处理
不要用独立线程更新状态,改为在主线程中处理。若需要"in progress"状态立即对其他请求可见,可使用独立事务更新该状态;若不需要即时可见,可放在同一事务中(但此时状态不会在部署过程中显示为"in progress")。
2. 捕获原始状态,异常时回滚
在更新为"in progress"前记录原始状态,一旦发生异常,将状态恢复为原始值。
修复后代码示例
假设使用Spring框架,我们可以通过@Transactional(propagation = Propagation.REQUIRES_NEW)创建独立事务更新状态:
第一步:创建状态更新的服务方法
@Service public class DeploymentService { private final ClientConfigRepository clientConfigRepository; public DeploymentService(ClientConfigRepository clientConfigRepository) { this.clientConfigRepository = clientConfigRepository; } // 独立事务更新状态,确保即时可见 @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateDeploymentStatus(Long clientId, String status) { ClientConfig clientConfig = clientConfigRepository.findById(clientId) .orElseThrow(() -> new IllegalArgumentException("Client config not found for id: " + clientId)); clientConfig.setDeploymentStatus(status); clientConfigRepository.save(clientConfig); } // 独立事务更新成功状态及时间 @Transactional(propagation = Propagation.REQUIRES_NEW) public void markDeploymentSuccess(Long clientId) { ClientConfig clientConfig = clientConfigRepository.findById(clientId) .orElseThrow(() -> new IllegalArgumentException("Client config not found for id: " + clientId)); clientConfig.setDeploymentStatus("deployed"); clientConfig.setLastDeployedDate(new Date()); clientConfigRepository.save(clientConfig); } }
第二步:重构主业务方法
@RestController public class DeploymentController { private final ClientConfigRepository clientConfigRepository; private final DeploymentService deploymentService; private final RestResponse restResponse; public DeploymentController(ClientConfigRepository clientConfigRepository, DeploymentService deploymentService, RestResponse restResponse) { this.clientConfigRepository = clientConfigRepository; this.deploymentService = deploymentService; this.restResponse = restResponse; } public ResponseEntity<?> deployConfigs(Long clientId) { // 获取原始状态 ClientConfig originalConfig = clientConfigRepository.findById(clientId) .orElseThrow(() -> new IllegalArgumentException("Client config not found for id: " + clientId)); String originalStatus = originalConfig.getDeploymentStatus(); try { // 设置为in progress(独立事务,即时生效) deploymentService.updateDeploymentStatus(clientId, "in progress"); // 执行核心部署逻辑 // some code.... // some code... // 部署成功,更新状态 deploymentService.markDeploymentSuccess(clientId); return new ResponseEntity<>(restResponse.createSuccessIdResponse( "Successfully completed Deployment of Configs.", clientId, ""), HttpStatus.OK); } catch (Exception e) { // 异常时回滚到原始状态 deploymentService.updateDeploymentStatus(clientId, originalStatus); return new ResponseEntity<>(restResponse.commonErrorResponse( "Deploy Config implementation failed", e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.toString()), HttpStatus.INTERNAL_SERVER_ERROR); } } }
额外注意事项
- 如果部署逻辑耗时较长,可考虑添加超时机制或定时任务清理停滞的"in progress"状态(比如超过一定时间未更新为"deployed"则自动回滚)。
- 避免使用
getOne,改用findById获取实体,防止代理脱离上下文导致的异常。 - 确保所有可能抛出异常的代码都被
try-catch覆盖,包括第三方服务调用、数据库操作等。
内容的提问来源于stack exchange,提问作者bikash pradhan
相关产品推荐
相关产品推荐

