保存时通过ConstraintValidator调用JPA Repository方法为何触发HH000099断言失败?
问题分析与解决方案
咱们先来拆解下你遇到的这个Hibernate断言错误的核心原因:
当执行save操作时,你的Area实体还处于瞬时(transient)状态——因为用了IDENTITY生成策略,id要到数据库插入时才会赋值,此时这个未持久化的实体已经被加入到Hibernate的Session中了。
为什么findById能正常运行,count()却报错?
findById(1L).get()是针对已存在的主键查询,Hibernate默认行为里,这类主键查询会直接访问数据库(或从一级缓存获取),不会触发Session的自动Flush操作,自然不会处理那个id为null的瞬时实体,所以没报错。- 而
count()、findAll()这类全表扫描式的查询,Hibernate为了保证结果准确性,会自动触发Session的Flush——也就是尝试把Session里所有未持久化的实体同步到数据库。这时候它发现你的Area实体id为null,就抛出了AssertionFailure,因为Hibernate不允许id为null的实体被Flush。
解决方案
这里有几个可行思路,你可以根据场景选择:
1. 让校验查询在独立事务中执行
给isValid方法加上@Transactional(propagation = Propagation.REQUIRES_NEW)注解,让查询在新事务中运行,和当前保存操作的Session隔离,就不会触发当前Session的Flush了:
@Component // 确保校验器被Spring管理,事务注解才会生效 public class CountValidator implements ConstraintValidator<Counter, String> { private AreaRepository areaRepository; public CountValidator(AreaRepository areaRepository) { this.areaRepository = areaRepository; } @Override public void initialize(Counter constraint) {} @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public boolean isValid(String value, ConstraintValidatorContext context) { boolean isValid = false; try { Area area = areaRepository.findById(1L).get(); long count = areaRepository.count(); isValid = count < 3; } catch (Exception e) { System.out.println(e); } return isValid; } }
2. 修改查询的Flush模式,避免自动Flush
直接用EntityManager创建查询,设置FlushModeType.COMMIT,让查询不会触发Session的Flush:
@Component public class CountValidator implements ConstraintValidator<Counter, String> { private AreaRepository areaRepository; @PersistenceContext private EntityManager em; public CountValidator(AreaRepository areaRepository) { this.areaRepository = areaRepository; } @Override public void initialize(Counter constraint) {} @Override public boolean isValid(String value, ConstraintValidatorContext context) { boolean isValid = false; try { Area area = areaRepository.findById(1L).get(); // 自定义查询并设置Flush模式 long count = em.createQuery("SELECT COUNT(a) FROM Area a", Long.class) .setFlushMode(FlushModeType.COMMIT) .getSingleResult(); isValid = count < 3; } catch (Exception e) { System.out.println(e); } return isValid; } }
3. 调整校验时机,避开Session Flush触发点
你可以把校验逻辑放到@PrePersist注解的方法中,或者在调用save之前手动执行校验,让校验和保存操作处于不同的Session上下文,避免Flush冲突。
额外提醒
你的校验逻辑是检查Area表总记录数是否小于3,要注意并发场景的问题——比如多个请求同时保存时,可能出现校验通过但最终插入后超过3条的情况。如果需要严格控制数量,建议用数据库层面的约束或者分布式锁来保障。
内容的提问来源于stack exchange,提问作者Vader
相关产品推荐
相关产品推荐

