合并时JPA OrderColumn为空值

acs*_*404 3 java hibernate jpa jpa-2.0

我正在使用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;
Run Code Online (Sandbox Code Playgroud)

此映射使用parent_id,child_id和position列创建连接表.当我想将一个孩子添加到父母时,我会执行以下操作:

MenuItem newItem = service.persist(..); 
parent.getChildren().add(newItem);
newItem.setParent(parent); 
service.merge(newItem);
service.merge(parent);
Run Code Online (Sandbox Code Playgroud)

生成以下内容:

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 (?, ?)
Run Code Online (Sandbox Code Playgroud)

这里的问题是第二个插入不包含position属性,因此它保持为null.我究竟做错了什么?

编辑 我正在使用spring声明式事务管理,我所有的merge和persist方法都使用@Transactional注释.这可能是个问题吗?

Bor*_*ski 5

您的问题是,您将@OrderColumn注释放在关联的映射旁边.JPA和hibernate都不支持此功能.Hibernate children在实体加载时填充列表,但没有监视列表中是否有可能的更改.将新子项添加到此类列表不会触发休眠方面的任何操作.您必须从其他(非mappedBy)方面管理此类关联:在您的情况下,您必须使用setParent()方法.

现在,如果从hibernate的角度来看情况,你会发现,在持久化一个新的Child实体(向MenuItem_MenuItem表中添加新记录)期间,hibernate没有机会让索引列正确.实际上,要获得新实体的顺序,您需要查看父项的映射列表.但这并不总是可行的.请考虑以下代码段:

MenuItem newItem = new MenuItem();
newItem.setParent(parent);
entityManager.persist(newItem);
Run Code Online (Sandbox Code Playgroud)

position这里的栏目应该保存什么?

存档目标的正确方法是在此处添加position以下列MenuItem:

private Integer position;
Run Code Online (Sandbox Code Playgroud)

然后用以下内容替换@OrderColumn注释@OrderBy("position"):

@OneToMany(mappedBy = "parent", ...)
...
@OrderBy("position")
private List<MenuItem> children = new ArrayList<MenuItem>();
Run Code Online (Sandbox Code Playgroud)

然后添加一个位置重新排序方法,如下所示:

public void reorder() {
    for (int i = 0; i < getChildren().size(); i++) {
        MenuItem menuItem = getChildren().get(i);
        menuItem.setMyOrder(i);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您可以尝试使用@PrePersist@PreUpdate挂钩自动调用此方法.但这有点棘手.因为您必须在正在创建/更新的子级的父级上调用此方法.如果添加/更新多个子项,则最终会多次调用此方法.


Ang*_*ity 5

这是一个已知错误HHH-5732,将在 4.3 中修复。一般来说,此功能存在一些问题,请查看这些最近的错误报告:

  • HHH-8580 - 从集合中删除项目时发生 NPE
  • HHH-5378 - 插入时 @OrderColumn 未更新
  • HHH-5390 - 反向列表上的索引列应引发警告

最初,讨论说从概念的角度来看,您提到的使用场景被认为是无效的,因为这意味着有关 MenuItem 的信息position仅在父项上可用,而在子项上不可用。

后来提到会显示更好的错误消息来解释问题,而不是默默地填充positionnull。

还提到 Hibernate 文档本身提供了与您提供的示例类似的示例,并且 JPA 没有提到任何反对它的内容,因此它应该是一个有效的用例。

HHH-5732 上的错误修复似乎解决了这个问题。但是,如果您想在当前版本上应用修复程序,有一种方法可行。

上周我在使用 Hibernate 4.1 时遇到了同样的问题并且无法升级,所以我应用了以下解决方案:

添加position为 MenuItem 中的私有属性字段,而不必为其提供 getter 和 setter。自己填写这个属性并让 Hibernate 持久化它:

public class MenuItem {

     public MenuItem(... other parameters ..., int position) {

     }

     @Column("position")
     private int position;

     .... no getters or setters ...

}
Run Code Online (Sandbox Code Playgroud)

在添加子菜单的代码中:

int counter = 0; // some counter keeping the current position being added
MenuItem newItem = service.persist(.., counter); 
parent.getChildren().add(newItem);
Run Code Online (Sandbox Code Playgroud)

这也使得有关元素位置的信息在关系的两侧都可用。

编辑:请参阅此处此解决方案的 Hibernate 文档