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

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

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

谢谢!


当前回答

具有关系类型:


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

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

使用关系类型:


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

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

其他回答

我通过这样做来固定:

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

parent.getChildren().clear();

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

parent.getChildren().addAll(children);

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

在我的例子中,它是从几个线程并发访问一个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; 
}

批处理:在一个事务中读取器->处理器->写入器。

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

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

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

小心

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

这种方法太过打破休眠。

当我在很多地方读到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,这至少看起来是可行的。

我最近在重构一些代码时遇到了这个问题。原始的(正确的)代码是这样的:

public virtual IEnumerable<SubscriptionPurchase> Purchases => _purchases;
public virtual SubscriptionPurchase MostRecentPurchase => _purchases.OrderByDescending(p => p.PurchaseDateUtc).FirstOrDefault();

我尝试通过将. orderbydescent移动到. purchasing属性来更改代码。这个更改破坏了代码,并导致OP中的引用错误:

public virtual IEnumerable<SubscriptionPurchase> Purchases => _purchases.OrderByDescending(p => p.PurchaseDateUtc);
public virtual SubscriptionPurchase MostRecentPurchase => Purchases.FirstOrDefault();

直到试图保存包含. purchasing属性的父对象时,错误才显示出来。

正如对这个问题的其他回答所指出的那样,出现这个问题是因为集合正在重置(在我的例子中是在对. purchasing的每个访问上)。