首页 文章

合并时JPA OrderColumn为空值

提问于
浏览
3

我正在使用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 . 我究竟做错了什么?

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

4 回答

  • 0

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

    • HHH-8580 - 从集合中删除项目时的NPE

    • HHH-5378 - 插入时未更新@OrderColumn

    • HHH-5390 - 反向列表上的索引列应该发出警告

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

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

    还有人提到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);
    

    这也使得关于关系两侧可用元素位置的信息 .

    EDIT: 请参阅here此解决方案的Hibernate文档

  • 5

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

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

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

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

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

    private Integer position;
    

    然后用 @OrderBy("position") 替换 @OrderColumn 注释:

    @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 钩子自动调用这个方法 . 但这有点棘手 . 因为您必须在正在创建/更新的子级的父级上调用此方法 . 如果添加/更新多个子项,则最终会多次调用此方法 .

  • 4

    如果 position 包含子项并且您只保存子项(它本身不包含子项),那么它应该如何填充?之后也保存父菜单项!

  • 0

    根据Hibernate文档,如果order列也被映射为子端的属性,则双向关联中允许使用 mappedBy 属性:

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

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

相关问题