Spring Boot并发请求致状态表数据错乱的解决方案问询
并发问题解决方案
针对你遇到的同一项目出现多条IS_LATEST=TRUE记录的并发问题,结合你的约束条件(无法修改表结构、不能用存储过程、无法访问前端),以下是几个可行的服务端解决方案:
方案1:修正悲观锁的作用范围,锁定目标行
之前的@Lock可能未作用在正确的查询节点上。需要在查询当前项目最新记录时就加上悲观写锁,确保事务期间其他线程无法修改该行,直到当前事务提交。
代码实现
- 在JPA Repository中添加带锁的查询方法:
public interface YourEntityRepository extends JpaRepository<YourEntity, Long> { // 查询并锁定当前项目的最新记录 @Lock(LockModeType.PESSIMISTIC_WRITE) Optional<YourEntity> findByProjectIdAndIsLatestTrue(Long projectId); // 更新该项目所有最新记录为非最新 @Modifying @Query("UPDATE YourEntity e SET e.isLatest = false WHERE e.projectId = :projectId AND e.isLatest = true") int updateIsLatestToFalseByProjectId(@Param("projectId") Long projectId); }
- Service层用事务包裹完整流程,并设置合适的隔离级别:
@Service public class YourService { private final YourEntityRepository repository; public YourService(YourEntityRepository repository) { this.repository = repository; } @Transactional(isolation = Isolation.REPEATABLE_READ) public void createNewLatestRecord(Long projectId, YourEntity newEntity) { // 先锁定当前最新记录,阻塞其他线程的修改操作 repository.findByProjectIdAndIsLatestTrue(projectId); // 将该项目所有旧的最新记录置为非最新 repository.updateIsLatestToFalseByProjectId(projectId); // 插入新的最新记录 newEntity.setProjectId(projectId); newEntity.setIsLatest(true); repository.save(newEntity); } }
方案2:数据库显式行锁(针对SQL Server)
如果使用SQL Server,可在更新语句中添加UPDLOCK,HOLDLOCK提示,强制锁定符合条件的行,且持有锁直到事务结束,避免操作交错。
代码实现
修改Repository的更新方法为原生SQL:
@Modifying @Query(value = "UPDATE your_table SET IS_LATEST = 0 WHERE PROJECT_ID = :projectId AND IS_LATEST = 1 WITH (UPDLOCK, HOLDLOCK)", nativeQuery = true) int updateIsLatestToFalseByProjectId(@Param("projectId") Long projectId);
Service层保持@Transactional(isolation = Isolation.REPEATABLE_READ)包裹完整流程即可。
方案3:分布式锁(多实例部署场景)
如果你的应用是多实例部署,本地悲观锁和数据库行锁无法跨实例生效,必须用分布式锁确保同一时间只有一个线程操作同一个项目的记录。以下是基于Redis的实现示例:
代码实现
@Service public class YourService { private final YourEntityRepository repository; private final StringRedisTemplate redisTemplate; public YourService(YourEntityRepository repository, StringRedisTemplate redisTemplate) { this.repository = repository; this.redisTemplate = redisTemplate; } @Transactional(isolation = Isolation.REPEATABLE_READ) public void createNewLatestRecord(Long projectId, YourEntity newEntity) { String lockKey = "project:lock:" + projectId; // 获取分布式锁,设置5秒超时避免死锁 Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 5, TimeUnit.SECONDS); if (locked == null || !locked) { throw new RuntimeException("操作过于频繁,请稍后重试"); } try { // 执行更新与插入逻辑 repository.updateIsLatestToFalseByProjectId(projectId); newEntity.setProjectId(projectId); newEntity.setIsLatest(true); repository.save(newEntity); } finally { // 无论操作成功与否,释放锁 redisTemplate.delete(lockKey); } } }
方案4:事务内二次检查(乐观锁思路)
在插入新记录前,再次检查该项目是否存在IS_LATEST=TRUE的记录,若存在则回滚事务。该方案适合并发量不高的场景,无需额外锁机制,但可能出现操作失败,需返回错误提示用户重试。
代码实现
@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) public void createNewLatestRecord(Long projectId, YourEntity newEntity) { // 更新旧的最新记录 int updatedRows = repository.updateIsLatestToFalseByProjectId(projectId); // 二次检查是否有其他线程已插入新的最新记录 boolean existsLatest = repository.existsByProjectIdAndIsLatestTrue(projectId); if (existsLatest) { throw new RuntimeException("已有最新记录,请重试"); } // 插入新的最新记录 newEntity.setProjectId(projectId); newEntity.setIsLatest(true); repository.save(newEntity); }
内容的提问来源于stack exchange,提问作者null-point-exceptional
相关产品推荐
相关产品推荐

