Spring JPA @PreUpdate监听器触发无限循环问题求助
UserGroup更新监听器无限循环与双向关联错误解决方案
一、@PreUpdate无限循环问题
原因
在@PreUpdate监听器中调用Service查询时,EntityManager执行getResultList()会触发Hibernate自动flush机制——当前会话中存在待更新的UserGroup脏数据,flush操作会再次触发@PreUpdate注解方法,形成无限循环。
解决方案
修改查询逻辑,避免触发自动flush:
方案1:临时修改Flush模式
在DAO的查询方法中,临时将flush模式设置为COMMIT,查询完成后恢复原有模式,阻止查询时自动触发flush:
public class UserGroupDAOImpl implements UserGroupDAO { @PersistenceContext private EntityManager entityManager; private final UserGroupMapper mapper; public List<UserGroupDTO> get(UserGroupDTO userGroupDTO) { try { // 保存原有flush模式 FlushModeType originalFlushMode = entityManager.getFlushMode(); // 设置为COMMIT模式,查询时不自动flush entityManager.setFlushMode(FlushModeType.COMMIT); CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<UserGroup> criteriaQuery = criteriaBuilder.createQuery(UserGroup.class); Root<UserGroup> root = criteriaQuery.from(UserGroup.class); List<Predicate> predicates = buildPredicates(userGroupDTO, criteriaBuilder, root); criteriaQuery.where(predicates.toArray(new Predicate[0])); TypedQuery<UserGroup> query = entityManager.createQuery(criteriaQuery); PaginationUtil.applyPagination(query, userGroupDTO); List<UserGroup> userGroups = query.getResultList(); // 恢复原有flush模式 entityManager.setFlushMode(originalFlushMode); return mapper.map(userGroups); } catch (Exception e) { throw new CommonException(e.getMessage()); } } }
方案2:使用独立EntityManager查询(可选)
如果项目允许,创建新的EntityManager实例执行查询,完全脱离当前会话上下文,从根源避免触发flush。
二、双向关联"Shared reference"错误
原因
双向关联中未正确维护父/子实体的引用关系,比如设置parentGroup时未同步更新childGroups集合,反之亦然,导致Hibernate检测到重复引用冲突。
解决方案
在实体类中添加双向关联维护方法,确保关联操作时双向同步:
public class UserGroup { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name", nullable = false, unique = true) private String groupName; @Column(name = "description",length = 1025) private String groupDescription; @Column(name = "qualified_name") private String qualifiedName; @ManyToOne @JoinColumn(name = "parent_id") private UserGroup parentGroup; @OneToMany(mappedBy = "parentGroup", cascade = CascadeType.ALL, orphanRemoval = true) private Set<UserGroup> childGroups = new HashSet<>(); // 维护双向关联的添加方法 public void addChildGroup(UserGroup child) { childGroups.add(child); child.setParentGroup(this); } // 维护双向关联的移除方法 public void removeChildGroup(UserGroup child) { childGroups.remove(child); child.setParentGroup(null); } // 重写setParentGroup,确保双向同步 public void setParentGroup(UserGroup parentGroup) { if (this.parentGroup != null) { this.parentGroup.getChildGroups().remove(this); } this.parentGroup = parentGroup; if (parentGroup != null) { parentGroup.getChildGroups().add(this); } } // 返回不可修改集合,避免外部直接操作破坏关联 public Set<UserGroup> getChildGroups() { return Collections.unmodifiableSet(childGroups); } // 其他Getter/Setter保持不变 }
补充:@Transient标记未生效说明
你之前尝试的@Transient private boolean isUpdating = false无效,是因为@PreUpdate触发时Hibernate会加载实体快照,临时标记不会被持久化,也无法在监听器多次调用中保持状态,改用flush模式修改方案可彻底解决循环问题。
内容的提问来源于stack exchange,提问作者Joshith Reddy
相关产品推荐
相关产品推荐

