Hibernate @OneToMany 与mappedBy(父子)关系和缓存问题
-
05-07-2019 - |
题
这个问题困扰我很久了,在网上查了很多资料,也没有找到解决办法。我希望你能在这方面帮助我。
我在两个实体之间有如下所示的父子关系:
@Entity
public class Parent {
// ...
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Set<Child> children = new HashSet<Child>();
// ...
}
@Entity
public class Child {
// ...
@ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
// ...
}
问题是,当我创建一个新的子级并将其分配给父级时,当父级已经在缓存中时,它不会被更新。
Parent parent = new Parent();
em.persist(parent);
// ...
Child child = new Child();
child.setParent(parent);
em.persist(child);
parent.getChildren().size(); // returns 0
我尝试使用 @PreUpdate 在子项持久化时自动将子项添加到父项,但是当我们在 2 个不同的线程中有 2 个实体管理器时(例如在 JBoss 中),问题仍然存在,直到我们调用 em.refresh(parent)
那么问题来了——有没有办法可以顺利的消除这个问题并保证 parent.getChildren()
总是返回最新的孩子列表?
解决方案
大多数 ORM 都会这样做。
缓存中的对象不会从数据库更新(不必要的额外读取)。还要将对象模型和持久性视为分开的。IE。保持对象模型与其自身一致,并且不要依赖持久性机制来为您执行此操作。
因此,如果您希望将对象添加到集合中,请在“setParent”代码中执行此操作。
在这种情况下,最佳做法实际上是让关系中的一方完成所有工作,并让另一方推迟完成。另外,我建议使用字段访问而不是方法访问,这样您就可以更灵活地自定义方法。
向父级添加一个名为 addChild 的方法
public void addChild(Child child) {
child.setParent0(this);
getChildren().add(individualNeed);
}
然后在Child中设置Parent:
public void setParent(Parent parent) {
parent.addChild(child);
}
Child 中的 setParent0 是父级对子级的属性。
public void setParent0(Parent parent) {
this.parent = parent;
}
我还建议“getChildren”方法返回一个不可变的集合,以便开发人员不会无意中不使用此方法(我在这一切中学到了惨痛的教训)。
另一件事,上面的代码中应该有空检查代码和其他防御部分,为了清楚起见,我将其省略。
其他提示
非常确定您的问题是您的Cascade设置。
@Entity
public class Parent {
// ...
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY,
cascade = {CascadeType.REMOVE, CascadeType.PERSIST})
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private Set<Child> children = new HashSet<Child>();
// ...
}
@Entity
public class Child {
// ...
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
private Parent parent;
// ...
}
使用这些级联设置将级联持久化并更新到子对象。
例如。
Parent parent = new Parent();
em.persist(parent);
// ...
Child child = new Child();
child.setParent(parent);
em.persist(child); //will cascade update to parent
parent.getChildren().size(); // returns 1
或
Parent parent = new Parent();
Child child = new Child();
parent.setChild(parent);
em.persist(parent); //will cascade update to child
child.getParent(); // returns the parent
有关这方面的更多信息,请参阅 Hibernate Annotations
关于缓存问题,当您有多个VM针对具有单独缓存的同一数据库运行时,这是一个非常常见的问题。它被称为“缓存漂移”。
大多数适用于hibernate的缓存实现(ehcache,OSCache和SwarmCache)都内置了分布式缓存,可用于同步缓存。通常,分布式缓存发送更新缓存状态的多播消息。例如,通过SessionFactory.evict(Class,id)执行二级缓存逐出将导致将失效消息发送到集群中的其他缓存,这将使其他缓存中该对象的任何其他副本无效。
根据您的部署,您可能会接受或不接受多播。如果不是,您可能需要使用像memcached这样的单缓存解决方案。
我个人发现eh cache的分布式缓存配置非常简单。
EH缓存在这里更详细地讨论了这个问题: http://ehcache.org/documentation/ distributed_caching.html