我正在使用JPA 2.0和Hibernate 4.2.5。 我有一个双向映射到相同的实体类型。
@OneToMany(mappedBy = "parent", cascade = { CascadeType.PERSIST, CascadeType.DETACH }, orphanRemoval = true, fetch = FetchType.EAGER)
@Fetch(FetchMode.JOIN)
@OrderColumn(name = "position", nullable=false)
private List<MenuItem> children = new ArrayList<MenuItem>();
@ManyToOne(optional = false, fetch = FetchType.EAGER)
@JoinTable(name = "MenuItem_MenuItem", joinColumns = { @JoinColumn(name = "child_id") }, inverseJoinColumns = { @JoinColumn(name = "parent_id") })
private MenuItem parent;
此映射使用parent_id,child_id和position列创建连接表。当我想将一个孩子添加到父母时,我会执行以下操作:
MenuItem newItem = service.persist(..);
parent.getChildren().add(newItem);
newItem.setParent(parent);
service.merge(newItem);
service.merge(parent);
生成以下内容:
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into MenuItem (menu_id, message, messageId, params, id) values (?, ?, ?, ?, ?)
Hibernate: insert into MenuItem_MenuItem (parent_id, child_id) values (?, ?)
这里的问题是第二个插入不包含position属性,因此它保持为null。我做错了什么?
修改 我正在使用spring声明式事务管理,我的所有merge和persist方法都使用@Transactional注释。这可能是个问题吗?
答案 0 :(得分:5)
您的问题是,您将@OrderColumn
注释放在关联的映射旁边。 JPA和hibernate都不支持此功能。 Hibernate在实体加载上填充children
列表,但没有监视列表中是否有可能的更改。将新子项添加到此类列表不会触发休眠方面的任何操作。您必须从其他(非mappedBy)方面管理此类关联:在您的情况下,您必须使用setParent()
方法。
现在,如果从hibernate的角度来看情况,你会发现,在持久化一个新的Child实体(向MenuItem_MenuItem
表添加新记录)期间,hibernate没有机会让索引列正确。实际上,要获得新实体的顺序,您需要查看父项的映射列表。但这并不总是可行的。请考虑以下代码段:
MenuItem newItem = new MenuItem();
newItem.setParent(parent);
entityManager.persist(newItem);
这里的position
栏应该保存哪些内容?
归档目标的正确方法是在position
上添加MenuItem
列,如下所示:
private Integer position;
然后将@OrderColumn
注释替换为@OrderBy("position")
:
@OneToMany(mappedBy = "parent", ...)
...
@OrderBy("position")
private List<MenuItem> children = new ArrayList<MenuItem>();
然后添加位置重新排序方法,如下所示:
public void reorder() {
for (int i = 0; i < getChildren().size(); i++) {
MenuItem menuItem = getChildren().get(i);
menuItem.setMyOrder(i);
}
}
然后,您可以尝试使用@PrePersist
或@PreUpdate
挂钩自动调用此方法。但这有点棘手。因为您必须在正在创建/更新的子级的父级上调用此方法。如果添加/更新多个孩子,最终会多次调用此方法。
答案 1 :(得分:4)
这是一个已知的错误HHH-5732,将在4.3中修复。一般来说,此功能存在一些问题,请查看最近的错误报告:
最初在那里讨论说你提到的使用场景从概念的角度来看被认为是无效的,因为它意味着关于MenuItem position
的信息仅在父级上可用但不在孩子。
后来有人提到会显示一条更好的错误消息来解释这个问题,而不是默默地将position
填充为null。
还有人提到Hibernate文档本身提供的示例与您提供的示例类似,JPA没有提及任何内容,因此它应该是一个有效的用例。
HHH-5732上的错误修复似乎解决了这个问题。但是,如果要在当前版本上应用此修复程序,则有一种方法可行。
我上周使用Hibernate 4.1遇到了同样的问题但无法升级,所以我应用了以下解决方案:
在MenuItem中添加position
作为私有属性字段,而不必为其提供getter和setter。自己填写这个属性,让Hibernate坚持下去:
public class MenuItem {
public MenuItem(... other parameters ..., int position) {
}
@Column("position")
private int position;
.... no getters or setters ...
}
在添加子菜单的代码中:
int counter = 0; // some counter keeping the current position being added
MenuItem newItem = service.persist(.., counter);
parent.getChildren().add(newItem);
这也会产生关于关系两侧可用元素位置的信息。
编辑:请参阅here此解决方案的Hibernate文档
答案 2 :(得分:0)
如果position
包含孩子并且您只保存孩子(其本身不包含孩子),应该如何填充?之后也保存父菜单项!
答案 3 :(得分:0)
根据Hibernate文档,如果订单列也被映射为子节点上的属性,则双向关联中允许mappedBy
属性:
在你的情况下,孩子和父母是一样的,所以我很想知道它会如何表现......
否则,文档描述了没有mappedBy