JPA Repository无法查询自身保存的数据问题排查与解决
问题与解决方案
问题描述
通过以下代码实现创建OveroptimisationIdentifierEntity实体的功能:首次调用createIdentifier方法时,实体能成功保存到数据库;但再次调用时,因date字段的唯一约束触发DataIntegrityViolationException,在异常处理逻辑中调用findFirstByOrderByDateDesc或findAll方法,均无法查询到数据库中已存在的数据。
Entity类
@Entity @NoArgsConstructor @AllArgsConstructor @Builder @Data public class OveroptimisationIdentifierEntity { @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator") @Getter @Column(name = "id", columnDefinition = "VARCHAR(255)") private UUID id; @CreationTimestamp @Column(unique = true) private LocalDate date; }
Repository接口
import java.util.UUID; import java.util.Optional; import java.time.LocalDate; public interface OveroptimisationIdentifierRepository extends JpaRepository<OveroptimisationIdentifierEntity, UUID> { OveroptimisationIdentifierEntity findFirstByOrderByDateDesc(); Optional<OveroptimisationIdentifierEntity> findByDate(LocalDate date); }
创建方法(原代码)
public OveroptimisationIdentifierEntity createIdentifier() { OveroptimisationIdentifierEntity identifier = OveroptimisationIdentifierEntity.builder().build(); try { overoptimisationIdentifierRepository.save(identifier); } catch (DataIntegrityViolationException e) { // Date must be unique. Find the tuple with the current date, delete it, and save the entity anew. OveroptimisationIdentifierEntity currentIdentifier = overoptimisationIdentifierRepository.findFirstByOrderByDateDesc(); // Breakpoint. arsenkinOveroptimisationIdentifierRepository.delete(currentIdentifier); overoptimisationIdentifierRepository.save(identifier); } return identifier; }
原因分析
- 事务回滚标记:当
save触发唯一约束异常时,当前事务会被Hibernate标记为回滚状态,后续所有该事务内的数据库操作都会受影响,最终整个事务会回滚。 - 一级缓存限制:同一事务内的查询优先从Hibernate一级缓存(事务快照)取数据,而失败的
save操作并未将实体纳入缓存;同时事务已标记回滚,不会去数据库拉取最新数据,导致查不到已存在记录。 - 异常后操作无效:异常处理中的删除、保存操作会随事务回滚被撤销,无法真正修改数据库。
解决方案
方案1:提前查询规避异常(推荐)
在保存前先检查当天是否已有实体,存在则删除后再保存,从根源避免异常:
public OveroptimisationIdentifierEntity createIdentifier() { LocalDate today = LocalDate.now(); Optional<OveroptimisationIdentifierEntity> existingEntity = overoptimisationIdentifierRepository.findByDate(today); OveroptimisationIdentifierEntity newIdentifier = OveroptimisationIdentifierEntity.builder().build(); existingEntity.ifPresent(overoptimisationIdentifierRepository::delete); return overoptimisationIdentifierRepository.save(newIdentifier); }
方案2:新事务中处理异常逻辑
若必须通过捕获异常处理,需将查询、删除操作放在新事务中执行,脱离原事务的回滚影响:
// 在Service层新增独立方法,配置新事务 @Transactional(propagation = Propagation.REQUIRES_NEW) public void deleteLatestIdentifier() { OveroptimisationIdentifierEntity latest = overoptimisationIdentifierRepository.findFirstByOrderByDateDesc(); if (latest != null) { overoptimisationIdentifierRepository.delete(latest); } } // 修改原创建方法 @Transactional public OveroptimisationIdentifierEntity createIdentifier() { OveroptimisationIdentifierEntity newIdentifier = OveroptimisationIdentifierEntity.builder().build(); try { return overoptimisationIdentifierRepository.save(newIdentifier); } catch (DataIntegrityViolationException e) { deleteLatestIdentifier(); return overoptimisationIdentifierRepository.save(newIdentifier); } }
注意:需确保
deleteLatestIdentifier方法通过Spring容器调用(避免自调用导致事务注解失效),事务管理器配置正常。
内容的提问来源于stack exchange,提问作者Kifsif
相关产品推荐
相关产品推荐

