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

 更新时间:2025年04月09日 08:27:07   作者:码农阿豪@新空间  
在实际开发中,事务管理是保证数据一致性的核心机制之一,本文将通过一个实际案例,详细分析Spring事务中的删除操作与回滚机制,并提供优化方案,需要的可以参考下

引言

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

1. 问题背景

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

@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 并回滚事务。

日志显示:

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

2. 问题分析

2.1 事务回滚机制

Spring 默认在遇到 未捕获的 RuntimeException 或 Error 时回滚事务。如果 CustomerException 不是 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.assertSuccess(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事务管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现读写文件功能的代码分享

    Java实现读写文件功能的代码分享

    这篇文章主要为大家详细介绍了如何利用Java语言实现读写文件功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-08-08
  • 解决Springboot中Feignclient调用时版本问题

    解决Springboot中Feignclient调用时版本问题

    这篇文章主要介绍了解决Springboot中Feign client调用时版本问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Java程序顺序结构中逻辑控制语句详解流程

    Java程序顺序结构中逻辑控制语句详解流程

    在程序开发的过程之中一共会存在有三种程序逻辑:顺序结构、分支结构、循环结构,对于之前所编写的代码大部分都是顺序结构的定义,即:所有的程序将按照定义的代码顺序依次执行
    2021-10-10
  • Java中的HttpServletRequestWrapper用法解析

    Java中的HttpServletRequestWrapper用法解析

    这篇文章主要介绍了Java中的HttpServletRequestWrapper用法解析,HttpServletRequest 对参数值的获取实际调的是org.apache.catalina.connector.Request,没有提供对应的set方法修改属性,所以不能对前端传来的参数进行修改,需要的朋友可以参考下
    2024-01-01
  • 一文搞懂Java常见的三种代理模式(静态代理、动态代理和cglib代理)

    一文搞懂Java常见的三种代理模式(静态代理、动态代理和cglib代理)

    Java中常见的三种代理模式是静态代理模式、动态代理模式和CGLIB代理模式,本文就来给大家详细的讲解一下这三种代理模式,感兴趣的小伙伴跟着小编一起来看看吧
    2023-08-08
  • springboot单元测试依赖踩坑记录

    springboot单元测试依赖踩坑记录

    这篇文章主要介绍了springboot单元测试依赖踩坑记录及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • springboot整合阿里云oss上传的方法示例

    springboot整合阿里云oss上传的方法示例

    这篇文章主要介绍了springboot整合阿里云oss上传的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • SpringBoot中操作Redis及工具类的封装详解

    SpringBoot中操作Redis及工具类的封装详解

    在我们项目开发中总是免不了会使用缓存,Redis现在基本是我们公司中非常常见的缓存方案,包括在用户token的缓存,热点信息的缓存等,这篇文章主要讲讲在SpringBoot项目中如何去操作Redis,及最后工具类的封装
    2023-05-05
  • Java开发中为什么要使用单例模式详解

    Java开发中为什么要使用单例模式详解

    单例对于大家来说并不陌生,但是在什么时候用单例呢?为什么要用呢?本文就带大家了解一下为什么要使用单例,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • java如何用Processing生成马赛克风格的图像

    java如何用Processing生成马赛克风格的图像

    这篇文章主要介绍了如何用java如何用Processing生成马赛克风格的图像,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03

最新评论