Hibernate - cascade= " all-delete-orphan "的集合不再被所属实体实例引用

我有以下问题时,试图更新我的实体:

"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<..只有>,根据这个:如何“可能”解决问题,但它没有工作。

如果你有什么想法,请告诉我。

谢谢!

318179 次浏览

检查所有你给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。遗留代码会带来很多问题,永远不要忘记检查它。我所做的只是保持删除孤立策略和正确的等号和hashcode。

我也犯了同样的错误。我的问题是,保存实体后映射的集合仍然为空,当试图更新实体时抛出异常。对我有帮助的是:保存实体,然后进行刷新(集合不再为空),然后执行更新。也许用new ArrayList()来初始化集合也会有帮助。

方法:

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

当我在很多地方读到hibernate不喜欢你给一个集合赋值时,我认为最安全的做法显然是让它像这样成为final:

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,这至少看起来是可行的。

具有关系类型:


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

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

使用关系类型:


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

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

我在尝试使用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的更多信息可以是在这里

我唯一一次得到这个错误是当我试图将NULL传递到集合的setter时。为了防止这种情况,我的setter是这样的:

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

加上我愚蠢的回答。我们正在使用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。对父进程的更新将导致问题。

下面的解决方案对我很有效

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

而不是分配新的集合

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

将所有元素替换为

public void setChildren(Set<ChildEntity> children) {
Collections.replaceAll(this.children,children);
}

另一个原因可能是使用龙目岛。

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

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

我的2美分,刚刚花了2个小时在同样的东西上:)

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

查看更多详细信息:HibernateException - cascade="all-delete-orphan"的集合不再被所属实体实例引用

小心

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

这种方法太过打破休眠。

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

我正在使用Spring Boot,并且在一个集合中遇到了这个问题,尽管没有直接覆盖它,因为我用自定义序列化器和反序列化器为同一个集合声明了一个额外的字段,以便提供更前端友好的数据表示:

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

似乎即使我没有覆盖集合我自己,反序列化在底层做了这件事,同样触发了这个问题。解决方案是改变与反序列化器相关联的setter,这样它就会清除列表并添加所有内容,而不是覆盖它:

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

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

请注意以下两个hibernate问题:

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

现在孩子复活了其他细节以及它工作得很好。

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

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

经过这么多的尝试,我只是在测试方法上添加了@Transactional,它解决了它。但不要忘记原因。

我在用JSON post请求更新实体时遇到了这个问题。 当我在没有关于子节点数据的情况下更新实体时发生错误,即使没有子节点数据。 添加< / p >
"children": [],

向请求体解决了问题。

我有同样的问题,但它是当集合为空。只有在Set集合中,在List中工作正常。您可以尝试使用hibernate注释@LazyCollection(LazyCollectionOption.FALSE)而不是JPA注释fetch = FetchType.EAGER.

< p > < >强劲我的解决方案: 这是我的配置,工作良好

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

这与之前的答案相反,我有完全相同的错误:“级联=“all-delete-orphan”的集合不再引用....”,当我的setter函数看起来像这样:

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

(hibernate版本-尝试了5.4.10和4.3.11。在回到setter中的简单赋值之前,花了几天时间尝试各种解决方案。现在很困惑为什么会这样。)

在我的例子中,它是从多个线程并发访问一个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可以转义到其他线程:

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

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

所有这些答案都帮不了我,但我找到了另一个解决办法。

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

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

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

我只是删除了orphanRemoval,更新工作。

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

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

有2个可能的解决方案:

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

  • orphanRemoval设置为false

例如- was:

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

变成:

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

从[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

我通过这样做来固定:

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

parent.getChildren().clear();

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

parent.getChildren().addAll(children);

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

当我在我的父实体中使用这些注释时,我面临着类似的问题:

@Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })

错误地,我试图在数据库中保存一个空父对象,并正确设置我的实体对象的值解决了我的错误。所以,做检查,如果你是愚蠢的设置错误的值或试图在数据库中保存一个空对象。

在2021年和Spring Boot 2.5中,它帮助我在声明字段时立即初始化它:

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

当我们将子对象设为最后时,问题就解决了。

我们不应该在构造函数和setter中改变子对象的引用。

@OneToMany(mappedBy = "entity_b", cascade = [CascadeType.ALL] , orphanRemoval = true)

我也遇到了同样的错误。当我删除;&;orphanRemoval = true"部分在上述结构