集成Auditable的实体使用@PreUpdate注解触发StackOverflowError问题求解
问题根因
StackOverflow异常是@PreUpdate回调与数据库查询形成死循环导致:
- Hibernate执行实体更新前会触发
@PreUpdate回调 - 回调方法中调用了
tokenRegistryService、taxAdminSessionRepository的数据库查询逻辑 - Hibernate默认配置下,执行任何查询操作前会先flush当前持久化上下文的所有脏实体,flush过程又会再次触发
@PreUpdate回调,无限循环最终导致栈溢出
如果TokenRegistryEntity、TaxAdminSessionEntity本身也继承了集成@EntityListeners的Auditable父类,查询加载这些实体时会进一步触发回调,加重循环。
解决方案
方案1:查询时禁用自动flush(改造成本最低)
在相关Repository查询方法上添加QueryHint,设置FlushMode为COMMIT,避免查询前自动触发flush打断循环:
// TokenRegistryRepository 对应方法添加 @QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE, value = "COMMIT")) Optional<TokenRegistryEntity> findByUserIdAndStatus(Long userId, Status status); // TaxAdminSessionRepository 对应方法添加 @QueryHints(value = @QueryHint(name = org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE, value = "COMMIT")) Optional<TaxAdminSessionEntity> findByTokenAndUserIdAndAdminIdAndStatus(String token, Long userId, Long adminId, SessionStatus status);
方案2:将逻辑迁移到业务层(最符合规范)
JPA实体回调的设计定位是处理实体本身无外部依赖的字段赋值(如创建时间、更新时间),不适合在回调中执行数据库查询、依赖外部服务的逻辑:
- 删掉实体类中
@PrePersist、@PreUpdate的回调代码 - 在业务层调用实体save/update操作前,先调用
TaxAdminSessionServiceHelper.checkAdminSesstion()拿到结果,手动给实体的isBelongs字段赋值
该方案彻底避免了回调内的数据库操作,无循环风险,后续可维护性更高。
方案3:添加重入检测避免循环
如果一定要保留回调逻辑,可以用ThreadLocal做重入标记,避免同一线程重复触发回调:
private static final ThreadLocal<Boolean> CALLBACK_RUNNING = ThreadLocal.withInitial(() -> false); @PrePersist protected final void belongTo(){ if(CALLBACK_RUNNING.get()) return; try{ CALLBACK_RUNNING.set(true); this.isBelongs= TaxAdminSessionServiceHelper.checkAdminSesstion(); }finally { CALLBACK_RUNNING.remove(); } } @PreUpdate protected final void belongToUpdated(){ if(CALLBACK_RUNNING.get()) return; try{ CALLBACK_RUNNING.set(true); this.isBelongs= TaxAdminSessionServiceHelper.checkAdminSesstion(); }finally { CALLBACK_RUNNING.remove(); } }
内容的提问来源于stack exchange,提问作者Koshila
相关产品推荐
相关产品推荐

