Hibernate - 拥有的实体实例不再引用具有 cascade=”all-delete-orphan” 的集合

Posted

技术标签:

【中文标题】Hibernate - 拥有的实体实例不再引用具有 cascade=”all-delete-orphan” 的集合【英文标题】:Hibernate - A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance 【发布时间】:2011-08-01 01:02:46 【问题描述】:

我在尝试更新我的实体时遇到以下问题:

"A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance".

我有一个父实体,它有一些子实体的Set<...>。当我尝试更新它时,我会将所有引用设置为此集合并设置它。

以下代码代表我的映射:

@OneToMany(mappedBy = "parentEntity", fetch = FetchType.EAGER)
@Cascade( CascadeType.ALL, CascadeType.DELETE_ORPHAN )
public Set<ChildEntity> getChildren() 
    return this.children;

我只尝试清理 Set<..>,根据此:How to "possible" solve the problem 但它不起作用。

如果您有任何想法,请告诉我。

谢谢!

【问题讨论】:

@mel3kings,您提供的链接已失效。 @Opal sysdotoutdotprint.com/technologies/java/12 在移除元素时尝试使用可变集合。例如,如果manyotherList&lt;T&gt;,则不要使用something.manyother.remove(other)。使许多其他可变的,例如 ArrayList&lt;T&gt; 并使用 orphanDelete = true 有一个看起来非常相似的错误:hibernate.atlassian.net/browse/HHH-9940 重现它的代码:github.com/abenneke/sandbox/tree/master/… 链接“如何“可能”解决问题”不再可用 【参考方案1】:

截至 2021 年和 Spring Boot 2.5,它帮助我在声明字段时立即对其进行初始化:

@OneToMany(mappedBy="target",fetch= FetchType.EAGER,cascade = CascadeType.ALL, orphanRemoval = true)
private List<TargetEntity> targets = new ArrayList<>();

【讨论】:

【参考方案2】:

我在父实体中使用这些注释时遇到了类似的问题:

@Cascade( CascadeType.ALL, CascadeType.DELETE_ORPHAN )

错误地,我试图在数据库中保存一个空父对象并正确设置我的实体对象的值解决了我的错误。因此,请检查您是否愚蠢地设置了错误的值或试图在数据库中保存空对象。

【讨论】:

【参考方案3】:

我通过这样做修复了:

1.清除现有的子列表,以便将它们从数据库中删除

parent.getChildren().clear();

2。将上面创建的新子列表添加到现有列表中

parent.getChildren().addAll(children);

希望这篇文章能帮助您解决错误

【讨论】:

【参考方案4】:

从 [Intellij Idea] 版本 2020.3 批量运行测试时,spring-boot 2.4.1 出现此问题。从 IntelliJ 一次仅运行一个测试或从命令行运行测试时,不会出现此问题。

可能是 Intellij 缓存问题?

跟进:

使用 maven-surefire-plugin reuseForks true 运行测试时会出现问题。使用reuseForks false 将提供快速修复,但测试运行时间将显着增加。因为我们正在重用 fork,所以数据库上下文可能会由于运行其他测试而变脏 - 之后没有清理数据库上下文。显而易见的解决方案是在运行测试之前清理数据库上下文,但最好的解决方案应该是在每次测试之后清理数据库上下文(解决原始问题的根本原因)。在您的测试方法上使用 @Transactional 注释将保证您的数据库更改在测试方法结束时回滚。请参阅有关事务的 Spring 文档:https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-tx。

【讨论】:

【参考方案5】:

我遇到了同样的问题,但它是在 set 为 null 的时候。只有在 Set 集合中,在 List 中才能正常工作。你可以尝试使用hibernate注解@LazyCollection(LazyCollectionOption.FALSE)代替JPA注解fetch = FetchType.EAGER.

我的解决方案: 这是我的配置,工作正常

@OneToMany(mappedBy = "format", cascade = CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
private Set<Barcode> barcodes;

@OneToMany(mappedBy = "format", cascade = CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
private List<FormatAdditional> additionals;

【讨论】:

【参考方案6】:

有一个看起来非常相似的错误:https://hibernate.atlassian.net/browse/HHH-9940。

以及重现它的代码:https://github.com/abenneke/sandbox/tree/master/hibernate-null-collection/src/test

有两种可能的解决方法:

集合被初始化为空集合(而不是 null)

orphanRemoval 设置为 false

示例 - 是:

@OneToMany(cascade = CascadeType.REMOVE,
        mappedBy = "jobEntity", orphanRemoval = true)
private List<JobExecutionEntity> jobExecutionEntities;

成为:

@OneToMany(cascade = CascadeType.REMOVE,
        mappedBy = "jobEntity")
private List<JobExecutionEntity> jobExecutionEntities;

【讨论】:

集合初始化为空集合对我有用【参考方案7】:

所有这些答案都没有帮助我,但我找到了另一个解决方案。

我有一个实体 A,其中包含一个实体 B 的列表。实体 B 包含一个实体 C 的列表。

我试图更新实体 A 和 B。它成功了。但是在更新实体 C 时,我收到了上述错误。在实体 B 中,我有这样的注释:

@OneToMany(mappedBy = "entity_b", cascade = [CascadeType.ALL] , orphanRemoval = true)
var c: List<EntityC>?,

我只是删除了orphanRemoval 并且更新成功了。

【讨论】:

【参考方案8】:

在我的例子中,它是从多个线程同时访问一个 Hibernate Session。 我有 Spring Boot Batch 和 RepositoryItemReader 实现,我通过页面请求获取大小为 10 的实体。

例如我的实体是:

@Entity
class JobEntity 
    @ManyToOne(fetch = FetchType.LAZY)
    private GroupEntity group;


@Entity
class GroupEntity 
    @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    private Set<Config> configs; 

批处理:reader -&gt; processor -&gt; writer 在一个事务中。

在该实体配置中,GroupEntity 可以逃逸到其他线程:

进入读取部分的第一个线程获取大小为 10 的 JobEntity 页面(RepositoryItemReader#doRead),这些项目包含一个共享的 GroupEntity 对象(因为它们都指向相同的组 id)。然后它需要第一个实体。下一个阅读部分的线程会从这个页面一个接一个地获取 JobEntity,直到这个页面被耗尽。

所以现在线程可以访问与 JobEntity 实例相同的 GroupEntity 实例,即不安全的多线程访问一个 Hibernate Session

【讨论】:

【参考方案9】:
@OneToMany(mappedBy = 'parent', cascade= CascadeType.ALL, orphanRemoval = true)
List<Child> children = new ArrayList<>();

当我将子对象添加到现有的子对象列表时,我遇到了同样的错误。

childService.saveOrUpdate(child);
parent.addToChildren(child);
parentService.saveOrUpdate(parent);

解决我的问题的是:

child = childService.saveOrUpdate(child);

现在孩子的其他细节也恢复了,而且效果很好。

【讨论】:

【参考方案10】:

这与之前的答案相反,当我的 setter 函数看起来像这样时,我遇到了完全相同的错误:“不再引用具有 cascade=”all-delete-orphan” 的集合......” /p>

public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) 
    if( this.taxCalculationRules == null ) 
        this.taxCalculationRules = taxCalculationRules_;
     else 
        this.taxCalculationRules.retainAll(taxCalculationRules_);
        this.taxCalculationRules.addAll(taxCalculationRules_);
    

然后我改成简单版就消失了:

public void setTaxCalculationRules(Set<TaxCalculationRule> taxCalculationRules_) 
    this.taxCalculationRules = taxCalculationRules_;

(休眠版本 - 尝试了 5.4.10 和 4.3.11。花了几天时间尝试各种解决方案,然后回到 setter 中的简单分配。现在很困惑为什么会这样。)

【讨论】:

【参考方案11】:

我在使用 JSON 发布请求更新实体时遇到了这个问题。 当我更新没有关于孩子的数据的实体时发生错误,即使没有孩子。 添加

"children": [],

到请求正文解决了问题。

【讨论】:

【参考方案12】:

我的与 Spring Boot 完全不同! 对我来说,这不是因为设置了集合属性。

在我的测试中,我试图创建一个实体,但在另一个未使用的集合中出现此错误!

经过这么多尝试,我刚刚在测试方法上添加了 @Transactional 并解决了它。不要没有原因。

【讨论】:

测试方法上的@Transactional注解意味着数据库上下文将在测试方法结束时回滚,无论其结果如何。【参考方案13】:

这可能是由hibernate-enhance-maven-plugin 引起的。当我启用 enableLazyInitialization 属性时,这个异常开始发生在我的惰性集合上。我正在使用休眠 5.2.17.Final。

注意这两个休眠问题:

https://hibernate.atlassian.net/browse/HHH-10708 https://hibernate.atlassian.net/browse/HHH-11459

【讨论】:

使用 5.5.7 仍然会发生。当要合并的实体从 Web 会话反序列化时会出现最终问题,例如当将 jsf 前端的更改合并到应该进行合并的后端服务时,将 enableLazyInitialization 设置为 false 可以解决此错误,但缺点是也无法使用 LazyInitialization 功能,这有点烦人【参考方案14】:

我正在使用 Spring Boot 并且在集合中遇到了这个问题,尽管没有直接覆盖它,因为我使用 custom serializer and deserializer 为同一个集合声明了一个额外字段,以便提供对前端更友好的表示数据:

  public List<Attribute> getAttributes() 
    return attributes;
  

  public void setAttributes(List<Attribute> attributes) 
    this.attributes = attributes;
  

  @JsonSerialize(using = AttributeSerializer.class)
  public List<Attribute> getAttributesList() 
    return attributes;
  

  @JsonDeserialize(using = AttributeDeserializer.class)
  public void setAttributesList(List<Attribute> attributes) 
    this.attributes = attributes;
  

似乎即使我没有覆盖集合我自己,反序列化还是在幕后完成了它,同样触发了这个问题。解决方案是更改与反序列化器关联的设置器,以便它清除列表并添加所有内容,而不是覆盖它:

  @JsonDeserialize(using = AttributeDeserializer.class)
  public void setAttributesList(List<Attribute> attributes) 
    this.attributes.clear();
    this.attributes.addAll(attributes);
  

【讨论】:

【参考方案15】:

我使用了@user2709454 方法,并进行了小幅改进。

public class User 
    private Set<Role> roles;

    public void setRoles(Set<Role> roles) 
        if (this.roles == null) 
            this.roles = roles;
         else if(this.roles != roles)  // not the same instance, in other case we can get ConcurrentModificationException from hibernate AbstractPersistentCollection
            this.roles.clear();
            if(roles != null)
                this.roles.addAll(roles);
            
        
    

【讨论】:

【参考方案16】:

小心

BeanUtils.copyProperties(newInsum, insumOld,"code");

这个方法也打破了休眠。

【讨论】:

【参考方案17】:

当我设置parent.setChildren(new ArrayList&lt;&gt;()) 时,我得到了A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance。当我改成parent.getChildren().clear()后,问题就解决了。

查看更多详情:HibernateException - A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance。

【讨论】:

【参考方案18】:

方法:

public void setChildren(Set<SonEntity> aSet) 
    this.sonEntities = aSet;

如果 parentEntity 被分离,如果我们更新它,它会再次起作用。 但是,如果实体没有从每个上下文中分离出来(即查找和更新操作在同一个事务中),则以下方法有效。

public void setChildren(Set<SonEntity> aSet) 
    //this.sonEntities = aSet; //This will override the set that Hibernate is tracking.
    this.sonEntities.clear();
    if (aSet != null) 
        this.sonEntities.addAll(aSet);
    

【讨论】:

@Skuld 我有一个类似的问题,我应用了你的解决方案(在 setter 方法中,我清除了子集合 - this.children.clear() - 我添加了新的子 - this.children。添加所有(儿童))。此更改没有解决我的问题。我仍然得到“拥有实体实例不再引用具有 cascade="all-delete-orphan" 的集合”异常。你知道为什么吗?非常感谢! @ovdsrn 对不起,这不是我的答案,我只是整理了答案的格式,原作者(kmmanu)也许可以帮助你(或者你可能想开始一个如果您的情况与此处提出的原始问题不同,则新问题)祝您好运 这很好用,但当子组件包含在嵌套的休眠组件中并且组件在其实体中为空时,效果就不太好了。然后你会得到同样的错误。这是因为子实体的所有者是根实体,而不是被设为 null 的组件......这样,组件永远不允许变为 null,而是应该导致转发到孩子...至少,我不知道更好的解决方案...这是一种集合 clear() 构造,然后通过 destroy() 在组件上... 更多详情请参见这篇文章:***.com/questions/4770262/… 使用 this.sonEntities.retainAll(aSet) 而不是 sonEntities.clear() 因为如果 aSet == this.sonEntities(即同一个对象),您将在添加之前清除集合!跨度> 【参考方案19】:

另一个原因可能是使用 lombok。

@Builder - 即使你说.myCollection(new ArrayList());,也会导致保存Collections.emptyList()

@Singular - 忽略类级别默认值并保留字段null,即使类字段声明为myCollection = new ArrayList()

我的 2 美分,只花了 2 个小时,同样的 :)

【讨论】:

【参考方案20】:

而不是分配新的集合

public void setChildren(Set<ChildEntity> children) 
    this.children = children;

替换所有元素
public void setChildren(Set<ChildEntity> children) 
    Collections.replaceAll(this.children,children);

【讨论】:

【参考方案21】:

以下解决方案对我有用

//Parent class
@OneToMany(mappedBy = 'parent', 
           cascade= CascadeType.ALL, orphanRemoval = true)
@OrderBy(value="ordinal ASC")
List<Child> children = new ArrayList<>()

//Updated setter of children 
public void setChildren(List<Children> children) 
    this.children.addAll(children);
    for (Children child: children)
        child.setParent(this);



//Child class
@ManyToOne
@JoinColumn(name="Parent_ID")
private Parent parent;

【讨论】:

【参考方案22】:

添加我的愚蠢答案。我们正在使用 Spring Data Rest。这是我们相当标准的关系。该模式在其他地方使用过。

//Parent class
@OneToMany(mappedBy = 'parent', 
           cascade= CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.FALSE)
List<Child> children = new LinkedList<>()


//Child class
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = 'ParentID', updatable = false)
@JsonBackReference
Parent parent

在我们创建的关系中,总是打算通过他们自己的 repo 添加孩子。我还没有添加回购。我们进行的集成测试通过 REST 调用经历了实体的完整生命周期,因此事务将在请求之间关闭。没有孩子的 repo 意味着 json 将孩子作为主要结构的一部分,而不是在 _embedded 中。对父级的更新会导致问题。

【讨论】:

【参考方案23】:

我得到这个错误的唯一一次是当我尝试将 NULL 传递给集合的 setter 时。为了防止这种情况,我的设置器看起来像这样:

public void setSubmittedForms(Set<SubmittedFormEntity> submittedForms) 
    if(submittedForms == null) 
        this.submittedForms.clear();
    
    else 
        this.submittedForms = submittedForms;
    

【讨论】:

【参考方案24】:

我在尝试使用TreeSet 时遇到了这个问题。我确实用TreeSet 初始化了oneToMany,这很有效

@OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade =  CascadeType.ALL , orphanRemoval=true)
@OrderBy("id")
private Set<WizardAnswer> answers = new TreeSet<WizardAnswer>();

但是,这会带来上面question 中描述的错误。所以似乎hibernate 支持SortedSet 如果只是将上面的行更改为

@OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade =  CascadeType.ALL , orphanRemoval=true)
@OrderBy("id")
private SortedSet<WizardAnswer> answers;

它就像魔术一样工作:) 更多关于hibernate SortedSet的信息可以是here

【讨论】:

或许,用户 Collections.emptySet() 最好使用单例空列表【参考方案25】:

有关系类型:


当集合在hasMany 中声明时,不要尝试实例化集合,只需添加和删除对象。

class Parent 
    static hasMany = [childs:Child]


使用关系类型:


但只有在声明为属性(使用关系)且未在声明中初始化时,集合才能为空。

class Parent 
    List<Child> childs = []

【讨论】:

【参考方案26】:

当我在不同的地方读到 hibernate 不希望你分配给一个集合时,我认为最安全的做法显然是像这样让它最终化:

class User 
  private final Set<Role> roles = new HashSet<>();

public void setRoles(Set<Role> roles) 
  this.roles.retainAll(roles);
  this.roles.addAll(roles);


但是,这不起作用,并且您会收到可怕的“不再引用”错误,在这种情况下,这实际上是非常具有误导性的。

事实证明,hibernate 调用了您的 setRoles 方法,并且它希望在此处安装其特殊的集合类,并且不会接受您的集合类。尽管阅读了有关未在 set 方法中分配给您的集合的所有警告,但这让我很长时间感到困惑。

所以我改成这样:

public class User 
  private Set<Role> roles = null;

  public void setRoles(Set<Role> roles) 
  if (this.roles == null) 
    this.roles = roles;
   else 
    this.roles.retainAll(roles);
   this.roles.addAll(roles);
  


所以在第一次调用时,hibernate 会安装它的特殊类,而在随后的调用中,您可以自己使用该方法而不会破坏一切。如果您想将您的类用作 bean,您可能需要一个有效的 setter,而这至少似乎有效。

【讨论】:

为什么要调用retainAll()?为什么不 clear() 后跟 addAll()? “你为什么要调用retainAll()?为什么不先clear()再调用addAll()?”好问题,我认为我的工作假设是,如果您在再次添加项目之前没有删除这些项目,那么休眠将不太可能将其视为数据库更新。但我怀疑它无论如何都不会那样工作。 您应该使用 retainAll() 而不是 clear() 否则如果您碰巧传入相同的 set 对象,您可能会清除角色。例如:user.setRoles(user.getRoles()) == user.roles.clear() 当您设置 orphanRemoval=true 并创建此集合为空的记录时,您也会收到此错误。所以:a 有 oneToMany b 与 orphanremoval = true。当您创建 A where B = null 时,会触发此问题。您的初始化和最终解决方案似乎是最好的。 谢谢!我将我的列表初始化为List&lt;String&gt; list = new ArrayList&lt;&gt;();。将其更改为 List&lt;String&gt; list = null; 解决了问题:)【参考方案27】:

我有同样的错误。对我来说的问题是,在保存实体后,映射的集合仍然为空,并且在尝试更新实体时抛出了异常。对我有什么帮助:保存实体,然后进行刷新(集合不再为空),然后执行更新。也许使用 new ArrayList() 或其他方法初始化集合也可能会有所帮助。

【讨论】:

发送新的 ArrayList 而不是 null,对我有用。谢谢【参考方案28】:

实际上,我的问题是关于我的实体的 equals 和 hashcode。遗留代码会带来很多问题,永远不要忘记检查它。我所做的只是保持删除孤儿策略和正确的等号和哈希码。

【讨论】:

拜托,一个很好的equals和hashCode方法的例子?因为我有很多问题:或者我无法更新我的设置,或者我收到 *** 错误。我在这里提出了一个问题:***.com/questions/24737145/…。谢谢【参考方案29】:

检查您为sonEntities 分配某些内容的所有位置。您引用的链接清楚地指出了创建一个新的 HashSet 但您在重新分配集合时可能会遇到此错误。例如:

public void setChildren(Set<SonEntity> aSet)

    this.sonEntities = aSet; //This will override the set that Hibernate is tracking.

通常你只想在构造函数中“新建”一次集合。任何时候您想在列表中添加或删除某些内容,您都必须修改列表的内容,而不是分配一个新列表。

添加孩子:

public void addChild(SonEntity aSon)

    this.sonEntities.add(aSon);

要删除孩子:

public void removeChild(SonEntity aSon)

    this.sonEntities.remove(aSon);

【讨论】:

实际上,我的问题是关于我的实体的equals和hashcode。遗留代码会带来很多问题,永远不要忘记检查它。我所做的只是保持删除孤儿策略和正确的等号和哈希码。 很高兴您解决了问题。 equals 和 hashcode 用 Hibernate 咬了我几次。而不是用“[已解决]”更新问题的标题,您应该继续发布您的答案,然后将其标记为已接受的答案。 谢谢,我遇到了类似的事情,结果我的 setter 是空的.. 是的,即使在空列表上也会出现此错误。我没想到会这样,因为没有孤儿(列表为空)。 通常您只想在构造函数中“新建”一次集合。任何时候您想在列表中添加或删除某些内容,您都必须修改列表的内容,而不是分配新列表。 大多数小鬼

以上是关于Hibernate - 拥有的实体实例不再引用具有 cascade=”all-delete-orphan” 的集合的主要内容,如果未能解决你的问题,请参考以下文章

拥有实体实例 - Spring 和 Lombok 不再引用具有 cascade="all-delete-orphan" 的集合

多对多和自我参考表!获取用户拥有的所有角色。实体框架

Hibernate标准对儿童的限制

org.hibernate.AnnotationException:referencedColumnNames 引用未映射到单个属性

如何在 JPA Hibernate 映射中将 GUID(不是 PK)添加到已经具有 PK(整数)的现有实体

您如何发回由另一个实体拥有的 JPA 实体的子集?