質問

I'm using JPA 2.0 with Hibernate 4.2.5. I have a bidirectional mapping to the same entity type.

@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;

This mapping creates a jointable with parent_id, child_id and position columns. When I want to add a child to the parent I do the following:

MenuItem newItem = service.persist(..); 
parent.getChildren().add(newItem);
newItem.setParent(parent); 
service.merge(newItem);
service.merge(parent);

which generates the following:

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 (?, ?)

The problem here is that the second insert doesn't include the position property so it remains null. What am I doing wrong?

EDIT I'm using spring declarative transaction management, and all my merge and persist methods are annotated with @Transactional. Could it be a problem?

役に立ちましたか?

解決 2

This is a known bug HHH-5732 that will be fixed in 4.3. In general there have been a few problems with this functionality, have a look at these recent bug reports:

  • HHH-8580 - NPE while deleting items from collection
  • HHH-5378 - @OrderColumn not updated when inserting
  • HHH-5390 - Index column on inverse List should throw a warning

Initially there where discussions saying that the scenario of usage that you mention is considered not valid from a conceptual point of view, as it meant that the information about the MenuItem position is available only on the parent but not on the child.

Later it was mentioned that a better error message would be shown explaining the problem, instead of silently filling position with null.

It was also mentioned that the Hibernate documentation itself gave examples similar to the one you presented, and JPA mentioned nothing against it so it should be a valid use case.

The bug fix on HHH-5732 seems to address this. However if you want to apply the fix on your current version, there is one way that will work.

I had the same problem last week with Hibernate 4.1 and could not upgrade, so I applied the following solution:

Add position as a private property field in MenuItem, without necessarily giving it getters and setters. Fill in this property yourself and let Hibernate persist it:

public class MenuItem {

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

     }

     @Column("position")
     private int position;

     .... no getters or setters ...

}

And in the code that adds submenus:

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

This also makes the information about what is the position of the element available at both sides of the relation.

EDIT: see here the Hibernate documentation for this solution

他のヒント

Your problem is, that you put the @OrderColumn annotation on the mapped-by side of the association. This is not supported by JPA nor hibernate. Hibernate fill the children list on entity load but didn't monitor the list for possible changes. Adding a new child to such list doesn't trigger any actions on hibernate side. You have to manage such associations from the other (non-mappedBy) side: in your case you have to use setParent() method.

Now if you look at the situation from the hibernate point of view you will realize, that during persisting a new Child entity (adding new record to the MenuItem_MenuItem table) hibernate has no chance to get the index column right. Indeed, to get the order of the new entity you have look into the mapped-by list of the parent. But this is not always possible. Consider following snippet:

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

What should be saved in the position column here?

The right way to archive your goals would be adding position column on your MenuItem like this:

private Integer position;

Then replace @OrderColumn annotation with @OrderBy("position"):

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

And then add a position reorder method, like this:

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

Then you can try to use @PrePersist or @PreUpdate hooks to call this method automatically. But this is a bit tricky. Since you have to call this method on parent of the child being created/updated. You will end up calling this method multiple times, if you add/update multiple children.

If position contains the children and you save only the child (which contains no children by itself), how should it be filled? Save the parent menu item afterwards too!

According to Hibernate documentation, the mappedBy attribute is permitted in a bidirectional association, if the order column is also mapped as a property on the children side:

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

In your case, children and parent are the same, so I am curious to know how it's going to behave.. Otherwise, the documentation describes a second option without mappedBy

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top