我有以下问题时,试图更新我的实体:
"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<..只有>,根据这个:如何“可能”解决问题,但它没有工作。
如果你有什么想法,请告诉我。
谢谢!
检查所有你给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);
}
在我的例子中,它是从几个线程并发访问一个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会话。
加上我愚蠢的回答。我们正在使用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中。对父进程的更新将导致问题。
当我在很多地方读到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,这至少看起来是可行的。