You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JPA并发插入时Address字段自增异常问题及解决方案咨询

JPA并发插入时Address字段重复递增问题的解决方法

问题根源

你的代码存在竞态条件:两个并发线程同时执行查询获取当前Address最大值,此时它们拿到的数值完全相同,接着各自将新记录的Address设为该值+1并保存,最终导致两条新记录的Address重复。

可行解决方案

1. 数据库层面实现自增(推荐)

让数据库直接负责Address字段的自增逻辑,彻底规避应用层的并发问题。
修改实体类配置,让Address使用数据库自增策略(以MySQL为例):

@Entity
public class Department {
    @Id
    @GeneratedValue
    private Integer id;
    @Version
    private long version;
    // 配置数据库自增,确保每次插入自动递增
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer address;
    private String name;

    // 构造器、Getter、Setter方法
}

插入新记录时无需手动设置Address,数据库会自动分配递增的数值。

2. 悲观锁控制并发读取

在查询当前最大值时加写锁,确保同一时间只有一个线程能获取并更新,避免并发冲突。
首先在Repository中定义带悲观锁的查询方法:

public interface DepartmentRepo extends JpaRepository<Department, Integer> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Department findTopByOrderByAddressDesc();
}

然后修改saveEmployee方法:

@Transactional
public void saveEmployee() {
    // 查询时加写锁,其他线程需等待当前事务提交后才能读取
    Department lastDept = repo.findTopByOrderByAddressDesc();
    Department newDept = new Department();
    newDept.setAddress(lastDept.getAddress() + 1);
    newDept.setName("test2");
    repo.save(newDept);
}

3. 乐观锁结合重试机制

利用@Version字段实现乐观锁,当出现版本冲突时自动重试操作,直到成功。
修改saveEmployee方法,加入重试逻辑:

@Transactional
public void saveEmployee() {
    int maxRetry = 3;
    while (maxRetry > 0) {
        try {
            Department lastDept = repo.findTopByOrderByAddressDesc();
            Department newDept = new Department();
            newDept.setAddress(lastDept.getAddress() + 1);
            newDept.setName("test2");
            repo.save(newDept);
            return;
        } catch (OptimisticLockingFailureException e) {
            maxRetry--;
            if (maxRetry == 0) {
                throw new RuntimeException("重试次数耗尽,无法完成插入");
            }
            // 短暂休眠后重试
            try {
                Thread.sleep(100);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

当两个线程同时操作导致版本冲突时,其中一个会触发异常,通过重试可以获取最新的最大值重新计算Address。

4. 原生SQL原子插入

直接通过数据库原生SQL完成"获取最大值+插入"的原子操作,保证操作的不可分割性。

@Autowired
private EntityManager entityManager;

@Transactional
public void saveEmployee() {
    String sql = "INSERT INTO department (address, name) SELECT COALESCE(MAX(address), 0) + 1, ? FROM department";
    entityManager.createNativeQuery(sql)
            .setParameter(1, "test2")
            .executeUpdate();
}

这种方式将逻辑放在数据库端执行,由数据库保证原子性,彻底避免竞态问题。


内容的提问来源于stack exchange,提问作者Thejas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 16:10:56