Hibernate @OneToMany 与 mappedBy(父子)关系和缓存问题
Posted
技术标签:
【中文标题】Hibernate @OneToMany 与 mappedBy(父子)关系和缓存问题【英文标题】:Hibernate @OneToMany with mappedBy (parent-child) relationship and cache problem 【发布时间】:2010-11-15 10:35:13 【问题描述】:这个问题我已经很久了,我已经在网上搜索过,所以进进出出,还没有找到解决办法。我希望你能帮助我。
我在两个实体之间存在父子关系,如下所示:
@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()
始终返回最新的孩子列表?
【问题讨论】:
【参考方案1】:大多数 ORM 都会以这种方式运行。
缓存中的对象未从数据库更新(不必要的额外读取)。还可以将对象模型和持久性视为分开的。即保持您的对象模型与自身一致,不要依赖持久性机制为您执行此操作。
因此,如果您希望将对象添加到集合中,请在“setParent”代码中执行此操作。
在这种情况下,最佳实践实际上是让关系的一方完成所有工作,让另一方推迟处理。另外我建议使用字段访问而不是方法访问,这样您可以更灵活地自定义方法。
向父级添加一个名为 addChild 的方法
public void addChild(Child child)
child.setParent0(this);
getChildren().add(individualNeed);
然后在 Child 中设置 setParent:
public void setParent(Parent parent)
parent.addChild(child);
setParent0 in Child 是 child 上 parent 的属性 stter。
public void setParent0(Parent parent)
this.parent = parent;
我还建议“getChildren”方法返回一个不可变集合,这样开发人员就不会无意中不使用该方法(我在所有这些中都学到了艰难的方法)。
还有一件事,你应该在上面的代码中包含空检查代码和其他防御性部分,为了清楚起见,我把它省略了。
【讨论】:
感谢您的广泛回答,迈克尔。我在里面找到了一些很好的信息。但是,恐怕它并不能解决我遇到的问题,因为两个不同的 EntityManager 实例拥有两个不同的缓存,当其中一个更新实体实例时,另一个看不到更新并且它的缓存实体变得过时 听起来您需要查看更新触发器,然后该触发器将获取该对象并更新其他缓存。或者,如果您的缓存解决方案支持集群,您可以将这两个缓存成员设为同一个集群。 不幸的是,我无法控制 Hibernate 的会话缓存。还是我?【参考方案2】:很确定您的问题是您的 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
【讨论】:
【参考方案3】:关于缓存问题,当您有多个虚拟机针对具有单独缓存的同一数据库运行时,这是一个非常常见的问题。它被称为“缓存漂移”。
大多数对休眠友好的缓存实现(ehcache、OSCache 和 SwarmCache)都具有内置的分布式缓存,可用于同步缓存。分布式缓存通常发送更新缓存状态的多播消息。例如,通过 SessionFactory.evict(Class,id) 执行二级缓存驱逐,将导致向集群中的其他缓存发送无效消息,这将使该对象在其他缓存中的任何其他副本无效。
根据您的部署,您可能接受也可能不接受多播。如果不是,您可能需要使用像 memcached 这样的单缓存解决方案。
我个人觉得eh cache的分布式缓存的配置很简单。
EH 缓存在这里更详细地讨论了这个问题:http://ehcache.org/documentation/distributed_caching.html
【讨论】:
以上是关于Hibernate @OneToMany 与 mappedBy(父子)关系和缓存问题的主要内容,如果未能解决你的问题,请参考以下文章
Hibernate @OneToMany 与 mappedBy(父子)关系和缓存问题
使用 SpringBoot 和 Hibernate 与复合 pk 的双向 @OneToMany 关系
如何使用 Spring Data / Hibernate 级联保持 @OneToMany 与 @EmbeddedId 的关系