红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 未分类
  3. 正文

[DEBUG] 解决数据库乐观锁报错的问题

2026年3月26日 11点热度 0人点赞 0条评论

问题描述

开发的项目是 Spring+Hibernate+达梦数据库,debug 发现存在一处历史代码,使用数据库表作为序号池,生成序号:

 @Override
public Integer findNumberByKeyAndUpdateNumberByKey(String key) {
    String sql = "select id as id,key,MAX(number_value) as number_value,create_by as create_by,update_by as update_by,create_time as create_time,update_time as update_time,tenant as tenant,is_void as is_void,company_code as company_code,version as version,department_code as department_code from SCM_NUMBER_GENERATION where key = ?1";
    List parm = new ArrayList();
    parm.add(key);
    List<ScmNumberGeneration> scmNumberGeneration = scmNumberGenerationRepository.findByNativeSQL(ScmNumberGeneration.class, sql, parm);
    if (scmNumberGeneration.size()<1 || StringUtil.isEmpty(scmNumberGeneration.get(0).getNumberValue())){
        ScmNumberGeneration scmNumberGeneration1 = new ScmNumberGeneration();
        scmNumberGeneration1.setKey(key);
        scmNumberGeneration1.setNumberValue(1);
        scmNumberGenerationRepository.save(scmNumberGeneration1);
        return 1;
    }else{
        ScmNumberGeneration scmNumberGeneration1 = scmNumberGeneration.get(0);
        Integer num = scmNumberGeneration1.getNumberValue() + 1;
        scmNumberGeneration1.setNumberValue(num);
        scmNumberGenerationRepository.save(scmNumberGeneration1);
        return num;
    }
}

在这个项目里,所有的 JPA 实体类都使用 version 字段作为乐观锁,因此这里通过 Hibernate 保存序号池的代码,如果存在并发就会报错:

scmNumberGenerationRepository.save(scmNumberGeneration1);

可以通过测试用例用多线程进行模拟验证:

@Test
public void test() {
    // 使用多线程模拟并发进行测试
    final int threadNum = 5;
    ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
    CountDownLatch countDownLatch = new CountDownLatch(threadNum);
    for (int i = 0; i < threadNum; i++) {
        executorService.execute(() -> {
            try {
                TenantUtil.runTaskByTenant(TenantUtil.JTKJ_TENANT_ID, () -> {
                    Integer scmPayReqH = scmNumberGenerationService.findNumberByKeyAndUpdateNumberByKey("test");
                    System.out.println(scmPayReqH);
                });
            } finally {
                countDownLatch.countDown();
            }
        });
    }
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

与一般业务代码不同,序号池生成需要保证一定程度的并发,当然更好的做法是使用 Redis 实现序号池生成,但如果并发要求不高,也可以有限度地改造现有代码。

解决方案

这里简单地使用异常捕获-重试的方式简单解决:

@Override
public Integer findNumberByKeyAndUpdateNumberByKey(String key) {
    Integer number = retryFindNumberByKeyAndUpdateNumberByKey(key, 5);
    log.debug("序号池 SCM_NUMBER_GENERATION 获取序号[{}-{}]成功", key, number);
    return number;
}
​
private Integer retryFindNumberByKeyAndUpdateNumberByKey(String key, int retryCount) {
    if (retryCount <= 0) {
        log.error("序号池 SCM_NUMBER_GENERATION 生成序号[{}]失败,重试次数已用完", key);
        throw new GlobalBizException("序号生成出错");
    }
    log.debug("序号池 SCM_NUMBER_GENERATION 开始尝试获取序号[{}],剩余重试次数{}", key, retryCount);
    retryCount--;
    String sql = "select id as id,key,MAX(number_value) as number_value,create_by as create_by,update_by as update_by,create_time as create_time,update_time as update_time,tenant as tenant,is_void as is_void,company_code as company_code,version as version,department_code as department_code from SCM_NUMBER_GENERATION where key = ?1";
    List parm = new ArrayList();
    parm.add(key);
    List<ScmNumberGeneration> scmNumberGeneration = scmNumberGenerationRepository.findByNativeSQL(ScmNumberGeneration.class, sql, parm);
    if (scmNumberGeneration.size() < 1 || StringUtil.isEmpty(scmNumberGeneration.get(0).getNumberValue())) {
        ScmNumberGeneration scmNumberGeneration1 = new ScmNumberGeneration();
        scmNumberGeneration1.setKey(key);
        scmNumberGeneration1.setNumberValue(1);
        try {
            scmNumberGenerationRepository.save(scmNumberGeneration1);
            return 1;
        } catch (ObjectOptimisticLockingFailureException e) {
            return retryFindNumberByKeyAndUpdateNumberByKey(key, retryCount);
        }
    } else {
        ScmNumberGeneration scmNumberGeneration1 = scmNumberGeneration.get(0);
        Integer num = scmNumberGeneration1.getNumberValue() + 1;
        scmNumberGeneration1.setNumberValue(num);
        try {
            scmNumberGenerationRepository.save(scmNumberGeneration1);
            return num;
        } catch (ObjectOptimisticLockingFailureException e) {
            return retryFindNumberByKeyAndUpdateNumberByKey(key, retryCount);
        }
    }
}

The End.

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: debug
最后更新:2026年3月26日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号