开发者

Spring事务管理之如何处理删除操作与事务回滚

开发者 https://www.devze.com 2025-04-10 10:54 出处:网络 作者: 码农阿豪@新空间
目录引言1. 问题背景2. 问题分析2.1 事务回滚机制2.2 removeByIds 返回 false 的原因3. 优化方案3.1 区分业务异常与技术异常3.2 显式声明事务回滚规则3.3 并发场景优化4. 完整优化代码5. 日志与监控建议6. 总结引言
目录
  • 引言
  • 1. 问题背景
  • 2. 问题分析
    • 2.1 事务回滚机制
    • 2.2 removeByIds 返回 false 的原因
  • 3. 优化方案
    • 3.1 区分业务异常与技术异常
    • 3.2 显式声明事务回滚规则
    • 3.3 并发场景优化
  • 4. 完整优化代码
    • 5. 日志与监控建议
      • 6. 总结

        引言

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

        1. 问题背景

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

        http://www.devze.com
        @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()))
                  php      .ma编程p(OpmMediaFlowControlConfirmEntity::getId)
                        .collect(Collectors.toList());
        
                if (!CollectionUtils.isEmpty(deleteList)) {
                    RollBackExcUtils.throwExc(confirmService.removeByIds(deleteList) ? 1 : 0);
                }
            }
        }
        

        问题现象:

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

        日志显示:

        com.middle.exception.CustomerException: 事务处理请求异常

        2. 问题分析

        2.1 事务回滚机制

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

        2.2 removeByIds 返回 false 的原因

        原因是否应回滚处理建议
        记录不存在记录日志,不抛异常
        并发冲突(已删除)记录日志,不抛异常
        数据库异常抛异常并回滚

        3. 优化方案

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

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

        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 显式声明事务回滚规则

        @Transactional(rollbackFor = {CustomerException.class, RuntimeException.class})
        public class FlowService {
            // ...
        }
        

        3.3 并发场景优化

        方案1:乐观锁重试

        @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
        public void deleteWithRetry(List<Long> ids) {
            TransactionAssert.assertSuccess(confirmService.removeByIds(ids));
        }
        

        方案2:悲观锁查询

        List<OpmMediaFlowControlConfirmEntity> list = confirmService.listWithLock(
            QueryUtils.eq(OpmMediaFlowControlConfirmEntity::getFlowId, flowId)
        );
        

        4. 完整优化代码

        @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.ass编程客栈ertSuccess(success);
            }
        }
        
        // 事务断言工具类
        public class TransactionAssert {
            public static void assertSuccess(boolean condition) {
                if (!condition) {
                    throw new CustomerException(SupResultCode.CODE_900000, "操作失败,请重试");
                }
            }
        }
        

        5. 日志与监控建议

        在关键位置添加日志:

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

        6. 总结

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

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

        到此这篇关于Spring事务管理之如何处理删除操作与事务回滚的文章就介绍到这了,更多相关Spring事务管理内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

        0

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        关注公众号