【问题标题】:JPA OrderColumn null values on merge合并时的 JPA OrderColumn 空值
【发布时间】:2014-02-24 18:52:11
【问题描述】:

我正在使用 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 (?, ?)

这里的问题是第二个插入不包含位置属性,所以它保持为空。我做错了什么?

编辑 我正在使用 Spring 声明式事务管理,并且我所有的合并和持久方法都使用 @Transactional 进行了注释。会不会有问题?

【问题讨论】:

  • 我已经编辑了下面的答案(请参阅编辑),其中包含指向休眠文档的链接,他们建议将索引列映射到 @Column

标签: java hibernate jpa jpa-2.0


【解决方案1】:

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

现在,如果您从休眠的角度来看情况,您会意识到,在持久化一个新的子实体(向MenuItem_MenuItem 表中添加新记录)期间,休眠没有机会获得正确的索引列。实际上,要获得新实体的顺序,您需要查看父实体的映射列表。但这并不总是可能的。考虑以下 sn-p:

MenuItem newItem = new MenuItem();
newItem.setParent(parent);
entityManager.persist(newItem);

这里的position列应该保存什么?

归档目标的正确方法是在MenuItem 上添加position 列,如下所示:

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 hooks 来自动调用这个方法。但这有点棘手。由于您必须在正在创建/更新的孩子的父母上调用此方法。如果您添加/更新多个子项,您最终将多次调用此方法。

【讨论】:

    【解决方案2】:

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

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

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

    后来有人提到,会显示更好的错误消息来解释问题,而不是默默地用 null 填充 position

    还提到,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 ...
    
    }
    

    并在添加子菜单的代码中:

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

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

    编辑:请参阅here Hibernate 文档了解此解决方案

    【讨论】:

    • 我也用 EclipseLink 对其进行了测试,它也没有设置位置字段。我检查了 JPA 规范,The OrderColumn annotation may be specified on a one-to-many or many-to-many relationship or on an element collection. The OrderColumn annotation is specified on the side of the relationship that references the collection that is to be ordered. The order column is not visible as part of the state of the entity or embeddable class.[107] 的表述真的很模糊。所以我不确定是否真的应该支持,但我承认修复它不是一个大问题。
    • 另外,我针对 hibernate-entitymanager-4.3.1.Final 对其进行了测试,但仍未修复...(尽管他们说它已在 4.3.0 BETA 中修复)
    • 我使用了您链接的文档中的解决方案。将@JoinTable 与位置一起使用是一团糟。
    【解决方案3】:

    如果position 包含孩子,而你只保存孩子(它本身不包含孩子),应该如何填充?之后也保存父菜单项!

    【讨论】:

    • 如果我保存父对象,我会得到以下异常:org.springframework.orm.hibernate3.HibernateSystemException null index column for collection: pkg.MenuItem.children
    • 当您将非托管对象设置为引用时,有时会在休眠中发生,因此首先创建并保存子对象,然后将托管(!)子对象设置为父对象的子对象并保存父对象.
    • 我正在这样做。对问题进行了更改,以反映你们迄今为止的建议。
    • 请尝试以下操作:MenuItem newItem = service.persist(..); newItem.setParent(parent); service.merge(newItem); parent.getChildren().add(newItem); service.merge(parent);
    • 是的,只是关系错误地持续存在,没有位置属性。
    【解决方案4】:

    根据 Hibernate 文档,mappedBy 属性在双向​​关联中是允许的,如果 order 列也被映射为 children 端的属性:

    http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection-extratype-indexbidir

    在你的情况下,孩子和父母是一样的,所以我很想知道它会如何表现.. 否则,文档描述了没有mappedBy的第二个选项

    【讨论】:

      猜你喜欢
      • 2019-08-09
      • 2016-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-19
      相关资源
      最近更新 更多