首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Spring事务管理实战:如何优雅处理删除操作与事务回滚

Spring事务管理实战:如何优雅处理删除操作与事务回滚

作者头像
用户8589624
发布2025-11-15 17:46:26
发布2025-11-15 17:46:26
650
举报
文章被收录于专栏:nginxnginx

Spring事务管理实战:如何优雅处理删除操作与事务回滚

引言

在实际开发中,事务管理是保证数据一致性的核心机制之一。特别是在涉及数据库删除操作时,如何正确处理删除失败、并发冲突等场景,是每个开发者需要面对的挑战。本文将通过一个实际案例,详细分析Spring事务中的删除操作与回滚机制,并提供优化方案。


1. 问题背景

在某个广告流量控制系统中,我们需要删除无效的媒体广告位放量记录。核心代码如下:

代码语言:javascript
复制
@Service
@Transactional
public class FlowTransactionalUtil {
    @Autowired
    private OpmMediaFlowControlConfirmService confirmService;

    public void deleteMediaFlowOnline(List<OpmMediaFlowControlEntity> list, long flowId) {
        List<Long> mediaAdList = list.stream()
                .map(OpmMediaFlowControlEntity::getMediaAdId)
                .collect(Collectors.toList());

        List<Long> deleteList = confirmService.list(QueryUtils.eq(OpmMediaFlowControlConfirmEntity::getFlowId, flowId))
                .stream()
                .filter(e -> !mediaAdList.contains(e.getMediaAdId()))
                .map(OpmMediaFlowControlConfirmEntity::getId)
                .collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(deleteList)) {
            RollBackExcUtils.throwExc(confirmService.removeByIds(deleteList) ? 1 : 0);
        }
    }
}

问题现象:

removeByIds() 返回 false(删除失败或未删除记录)时,抛出 CustomerException 并回滚事务。

日志显示:

代码语言:javascript
复制
com.middle.exception.CustomerException: 事务处理请求异常

2. 问题分析

2.1 事务回滚机制

Spring 默认在遇到 未捕获的 RuntimeExceptionError 时回滚事务。如果 CustomerException 不是 RuntimeException 的子类,需要显式声明 @Transactional(rollbackFor = CustomerException.class)

2.2 removeByIds 返回 false 的原因

原因

是否应回滚

处理建议

记录不存在

记录日志,不抛异常

并发冲突(已删除)

记录日志,不抛异常

数据库异常

抛异常并回滚


3. 优化方案

3.1 区分业务异常与技术异常

优化 deleteFlowConfirm 方法,避免因“记录不存在”等合法场景触发回滚:

代码语言:javascript
复制
private void deleteFlowConfirm(long flowId, List<OpmMediaFlowControlConfirmEntity> controlConfirms) {
    List<Long> list = controlConfirms.stream()
            .filter(e -> e.getFlowId().equals(flowId))
            .map(OpmMediaFlowControlConfirmEntity::getId)
            .collect(Collectors.toList());

    if (!CollectionUtils.isEmpty(list)) {
        boolean isSuccess = confirmService.removeByIds(list);
        if (!isSuccess) {
            // 检查是否真的存在待删除记录
            long actualExistCount = confirmService.count(
                QueryUtils.in(OpmMediaFlowControlConfirmEntity::getId, list)
            );
            if (actualExistCount > 0) {
                throw new CustomerException("删除失败,请重试或联系管理员");
            } else {
                log.warn("尝试删除不存在的记录: flowId={}, ids={}", flowId, list);
            }
        }
    }
}
3.2 显式声明事务回滚规则
代码语言:javascript
复制
@Transactional(rollbackFor = {CustomerException.class, RuntimeException.class})
public class FlowService {
    // ...
}
3.3 并发场景优化
方案1:乐观锁重试
代码语言:javascript
复制
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void deleteWithRetry(List<Long> ids) {
    TransactionAssert.assertSuccess(confirmService.removeByIds(ids));
}
方案2:悲观锁查询
代码语言:javascript
复制
List<OpmMediaFlowControlConfirmEntity> list = confirmService.listWithLock(
    QueryUtils.eq(OpmMediaFlowControlConfirmEntity::getFlowId, flowId)
);

4. 完整优化代码

代码语言:javascript
复制
@Service
@Transactional(rollbackFor = CustomerException.class)
public class FlowService {
    @Autowired
    private OpmMediaFlowControlConfirmService confirmService;

    public void deleteMediaFlowOnline(List<OpmMediaFlowControlEntity> list, long flowId) {
        List<Long> mediaAdList = list.stream()
                .map(OpmMediaFlowControlEntity::getMediaAdId)
                .collect(Collectors.toList());

        // 直接条件删除(避免查询-删除竞态条件)
        boolean success = confirmService.deleteByFlowIdAndExcludedAdIds(flowId, mediaAdList);
        TransactionAssert.assertSuccess(success);
    }
}

// 事务断言工具类
public class TransactionAssert {
    public static void assertSuccess(boolean condition) {
        if (!condition) {
            throw new CustomerException(SupResultCode.CODE_900000, "操作失败,请重试");
        }
    }
}

5. 日志与监控建议

在关键位置添加日志:

代码语言:javascript
复制
try {
    flowService.deleteMediaFlowOnline(list, flowId);
} catch (CustomerException e) {
    log.error("删除失败 - flowId: {}, 错误: {}", flowId, e.getMessage());
    throw e;
}

6. 总结

  1. 明确事务回滚条件:确保异常类型正确触发回滚。
  2. 区分业务异常与技术异常:避免因合法场景(如记录不存在)误触发回滚。
  3. 优化并发操作:使用锁机制或重试策略减少冲突。
  4. 增强日志记录:便于快速定位问题。

通过以上优化,可以显著提升系统的健壮性和可维护性。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring事务管理实战:如何优雅处理删除操作与事务回滚
    • 引言
    • 1. 问题背景
    • 2. 问题分析
      • 2.1 事务回滚机制
      • 2.2 removeByIds 返回 false 的原因
    • 3. 优化方案
      • 3.1 区分业务异常与技术异常
      • 3.2 显式声明事务回滚规则
      • 3.3 并发场景优化
    • 4. 完整优化代码
    • 5. 日志与监控建议
    • 6. 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档