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
相关产品推荐
相关产品推荐

