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

时间:2011-04-07 20:50:15

标签: java hibernate hibernate-mapping

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

"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&lt; ..&gt;只有,根据这个:How to "possible" solve the problem但它不起作用。

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

谢谢!

27 个答案:

答案 0 :(得分:179)

检查您为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);
}

答案 1 :(得分:87)

方法:

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);
    }
}

答案 2 :(得分:22)

当我在各个地方读到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方法并且它希望在这里安装它的特殊集合类,并且不接受你的集合类。尽管已经阅读了所有关于不按照你的方法分配给你的收藏集的警告,但这让我难以忍受了很久。

所以我改为:

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,这至少看起来有效。

答案 3 :(得分:14)

实际上,我的问题是关于我的实体的equals和hashcode。遗留代码可能带来很多问题,永远不要忘记检查它。我所做的只是保持delete-orphan策略并纠正equals和hashcode。

答案 4 :(得分:9)

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

答案 5 :(得分:5)

我通过这样做修复了:

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

parent.getChildren().clear();

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

parent.getChildren().addAll(children);

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

答案 6 :(得分:4)

有关系类型:


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

class Parent {
    static hasMany = [childs:Child]
}

使用关系类型:


但是只有当声明为属性(使用关系)并且未在声明中初始化时,该集合才可以为null。

class Parent {
    List<Child> childs = []
}

答案 7 :(得分:3)

尝试使用TreeSet时出现此问题。我使用oneToMany初始化了TreeSet

@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

答案 8 :(得分:2)

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

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

答案 9 :(得分:2)

我使用@ 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);
            }
        }
    }
}

答案 10 :(得分:1)

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

我的实体A包含实体B的列表。实体B包含实体C的列表。

我正在尝试更新实体A和B。它起作用了。但是在更新实体C时,出现了上述错误。在实体B中,我有这样的注释:

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

我只是删除了orphanRemoval,更新就起作用了。

答案 11 :(得分:1)

@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);

现在,孩子也可以在其他细节上恢复活力,并且一切正常。

答案 12 :(得分:0)

我的与Spring Boot完全不同! 对我来说,这不是由于设置了收集属性。

在测试中,我试图创建一个实体,并为另一个未使用的集合获取此错误!

经过这么多尝试,我只是在测试方法上添加了@Transactional并解决了它。不过不要无故。

答案 13 :(得分:0)

添加我愚蠢的答案。我们正在使用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

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

答案 14 :(得分:0)

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

也许是Intellij缓存问题?

答案 15 :(得分:0)

有一个看起来可疑的错误:https://hibernate.atlassian.net/browse/HHH-9940

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

对此有2种可能的解决方法:

  • 使用空集合(而不是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;

答案 16 :(得分:0)

在我的情况下,这是从多个线程并发访问一个Hibernate会话。 我有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 -> processor -> writer一笔交易。

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

进入读取部分的第一个线程获取大小为10(RepositoryItemReader#doRead)的JobEntity页面,该项目包含一个共享的GroupEntity对象(因为它们都指向相同的组ID)。然后,它采用第一个实体。接下来要阅读的线程部分将逐页从此页面获取JobEntity,直到此页面耗尽为止。

因此,现在线程可以访问与JobEntity实例相同的GroupEntity实例,即对一个Hibernate会话不安全的多线程访问

答案 17 :(得分:0)

这与先前的答案相反,我遇到的错误完全相同:“当我的setter函数看起来像这样时,“不再引用带有层叠=” 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中的简单分配。现在就为什么会这样感到困惑。)

答案 18 :(得分:0)

我遇到了同样的问题,但是那是当集合为null时。仅在“列表”中的“集合”工作中找到。您可以尝试在JPA注释fetch = FetchType.EAGER中插入休眠注释@LazyCollection(LazyCollectionOption.FALSE)。

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

@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;

答案 19 :(得分:0)

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

"children": [],

解决了请求正文。

答案 20 :(得分:0)

而不是分配新的集合

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

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

答案 21 :(得分:0)

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

请注意这两个休眠问题:

答案 22 :(得分:0)

尽管我没有直接覆盖它,但我正在使用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);
  }

答案 23 :(得分:0)

以下解决方案为我工作

//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;

答案 24 :(得分:0)

注意

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

此方法也会使休眠模式中断。

答案 25 :(得分:0)

设置A collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance时得到parent.setChildren(new ArrayList<>())。当我更改为parent.getChildren().clear()时,它解决了问题。

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

答案 26 :(得分:0)

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

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

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

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