SpringBoot @ManyToMany报detached entity passed to persist异常
问题根因
抛出org.hibernate.PersistentObjectException: detached entity passed to persist的核心原因是User实体上privileges关联的级联配置不符合业务场景:
- 你给
@ManyToMany配置了CascadeType.PERSIST,该级联的语义是保存新User时,会对所有关联的Privilege对象也执行persist持久化操作 - 你传入的
adminPriv集合里的Privilege是已经提前持久化到数据库、且不在当前持久化上下文的游离(detached)对象,本身已经存在主键ID,不是未持久化的临时态实体,Hibernate尝试对这类已存在的对象执行persist操作时就会抛出异常 - 你代码中关联的Organization没有触发同类异常,是因为
@ManyToOne关联没有配置任何级联规则,Hibernate不会尝试级联持久化Organization对象,只要传入的Organization携带正确ID就能正常完成关联。
另外你的Privilege实体存在一个方法命名错误:定义的public void getUsers(final Set<User> users)本质是setter方法,方法名错写为getUsers,虽然不是本次异常的诱因,但会给后续使用埋下隐患。
修复方案
- 方案1(推荐,适配权限类预存基础数据的场景):调整级联配置,去掉
CascadeType.PERSIST,仅保留CascadeType.MERGE即可。权限属于提前初始化的基础数据,新增用户时不需要级联创建权限,仅需要在关联更新时做实体合并,修改后的注解配置如下:
@ManyToMany(cascade = CascadeType.MERGE) @JoinTable(name = "user_to_privileges", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {@JoinColumn(name = "privilege_id", referencedColumnName = "id", nullable = false)}) private Set<Privilege> privileges= new HashSet<>();
- 方案2(不修改级联配置的适配方式):给User关联权限前,先在当前持久化上下文中查询对应权限,把游离态的Privilege转为当前Session管理的托管态对象再做关联:
Set<Privilege> managedPrivileges = new HashSet<>(); for (Privilege priv : adminPriv) { // 从当前持久化上下文获取托管的权限实例 managedPrivileges.add(privilegeRepository.getById(priv.getId())); } user.setPrivileges(managedPrivileges);
- 编码规范注意:给用户关联权限时优先调用你已经实现的
addPrivileges()方法逐个添加,该方法会同步维护双向关联的两端(同时给Privilege的users集合添加当前User),可以避免后续出现关联不生效、中间表脏数据的问题;同时把Privilege类中命名错误的getUsers(Set<User> users)修改为setUsers(Set<User> users),符合JavaBean规范避免后续调用出错。
内容的提问来源于stack exchange,提问作者Discipulos
相关产品推荐
相关产品推荐

