java.lang.IllegalStateException: @ManyToMany关系中同一实体有多个表示,共3个实体。

74

我有三个具有多对多关系的实体:

角色实体:

@Entity
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer roleID;
    private String roleName;
    private String description;

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
    @JoinTable(name = "role_permission", joinColumns = {@JoinColumn(name = "role_id")}, inverseJoinColumns = {@JoinColumn(name = "permission_id")})
    private Set<Permission> permissions = new LinkedHashSet<Permission>();
}

权限实体:

@Entity
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int permissionID;
    private String permissionName;
    private String description;

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
    @JoinTable(name = "permission_functionality", joinColumns = {@JoinColumn(name = "permission_id")}, inverseJoinColumns = {@JoinColumn(name = "functionality_id")})
    private Set<Functionality> functionalities = new LinkedHashSet<>();
}

功能实体:

@Entity
public class Functionality {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String name;
}

我做了以下事情:

  1. 我创建了3个功能:

    Functionality1, Functionality2, Functionality3
    
    然后创建了2个权限:
  2. Permission1 with Functionality1, Functionality2
    
    Permission2 with Functionality2, Functionality3
    
    然后创建一个角色:
    Role1 with Permission1 and Permission2 
    
    我得到了以下异常:

    java.lang.IllegalStateException:正在合并同一实体的多个表示[com.persistence.entity.admin.Functionality#1]。分离的:[com.persistence.entity.admin.Functionality@4729256a]; 分离的:[com.persistence.entity.admin.Functionality@56ed25db]

15个回答

62

通过删除Permission实体上的CascadeType.MERGE,修复了它。


8
尝试使用Hibernate级联注释而不是JPA注释,例如@Cascade(CascadeType.SAVE_UPDATE)将替换{CascadeType.PERSIST,CascadeType.MERGE}。请查看此文章:JPA和Hibernate注释常见错误 - Mate Šimović
5
如果我想合并现有实体,这会产生什么影响?如果我删除CascadeType.MERGE会发生什么? - El Mac
是的,有同样的问题,如果我们删除MERGE级联会发生什么? - securecurve
我想我知道为什么。这意味着如果实体被合并,相关的实体也会被合并,但在这种情况下并非如此,因为我们只想创建一个新角色,但我们不想创建/更新权限。 - securecurve
如果我没有使用Hibernate呢? - KareemJ

31

正确的解决方案是升级到 hibernate 4.2.15 / 4.3.6 或更高版本,并将以下行添加到您的 persistence.xml 文件中:

<property name="hibernate.event.merge.entity_copy_observer" value="allow"/>


17
此属性适用于整个持久化上下文,仅应用于测试目的。如果将其设置为“allow”或“log”,Hibernate将按顺序合并两个分离的实体。但是,合并的顺序未定义,这可能会导致意外的数据损坏。正确的解决方案是修复实体关系。 - VHS
4
如果我删除CascadeType.MERGE会发生什么,你能解释一下吗?我在各处都看到它作为答案,但我敢打赌这将是一个带有副作用的破坏性更改。 - El Mac
如果没有使用 CascadeType.MERGE,当你保存一个包含其他对象引用的对象时,还需要手动存储该其他对象;相反,使用 CascadeType.MERGE,Hibernate 会自动完成这个过程。所以你是正确的,这可能会导致其他问题。 - marcotama

26

检查您的equals和hashCode方法,确保它们一致且正确定义。例如,当计算hashCode时,我复制并错误地粘贴了另一个类,这导致对象永远不等于自身 :(。


2
非常好的答案!谢谢你!IntelliJ有一个生成equalshashCode的过程,您可以在官网上查看。 - Aleksandar
1
非常有帮助!我正在使用Lombok的@Data注解来生成所有额外的方法,当改为Getter Setter而不是@Data时,问题已经解决了。谢谢。 - SP5RFD
父实体或分离实体的equalshashCode - KareemJ
那...真的一点儿区别都没有,伙计。 - undefined

22

和其他人一样,我基于 HHH-9106 的答案,但是因为我正在使用基于 Java 注释的 Spring Boot,所以我必须在 application.properties 中使用以下内容:

spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow

15

对我来说没问题... :D (Spring Boot 1.4.1.RELEASE) - udoline

7
我可以通过替换

来修复它。
cascade = CascadeType.All

使用

casecade={CascadeType.PERSIST,CascadeType.REMOVE}

0

当我们在HashSet中有多个相同类型的对象时,可能会出现错误,这可能是由于不正确的哈希函数引起的。HashSet根据两个对象之间的哈希函数检查对象的相等性。

调试方法

尝试打印HashSet,您将看到多个相同类型的对象。

解决方案::#

  • 在定义一对多关系时使用HashSet。
  • 避免使用列表。
  • 确保您的哈希函数应该是正确的。

0

关于Hibernate,请参见这里的解决方法 HHH-9106


6
请在目标网站无法访问或永久关闭的情况下引用最相关的部分。参见如何撰写好的答案 - ByteHamster
或者,如果我的网络阻止了某些CSS文件,那么该网站将完全无法阅读。 - Stephan
该链接涉及到 hibernate.event.merge.entity_copy_observer 属性,该属性已经被其他答案所覆盖(在撰写本回答时)。 - GuiRitter

0
在我的情况下,将获取操作和保存操作移动到同一个@Transactional块中解决了问题。

0

如果您删除级联合并,您将无法在更新父项时更新实体。 如果您不想更新子项,则情况将是正确的,但是如果您想在更新父项时更新子项,删除合并将仅不显示错误,但问题仍将存在。

我的情况:我遇到了相同的异常,但我需要Cascade.merge。搜索后,我发现第一次创建实体时使用的某些值已被更新并从代码中删除,但在数据库中它们仍然存在,并且当从数据库中删除它们时也会按预期工作。

澄清情况: 假设我有一个枚举('ACTIVE','NOT_ACTIVE','SUSPENDED'),并且使用此枚举的字段是键,当更新代码并删除NOT_ACTIVE时,我们应该使用新值enum('ACTIVE','SUSPENDED')更改数据库表


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接