Null index column in bidirectionnal and hierarchical relationship

I have en entity with a bidirectional hierarchical relationship :

public class Organization {

  @ManyToOne(fetch = FetchType.LAZY)
  public Organization getParent() {
    return this.parent;
  }

  public void setParent(final Organization parent) {
    if (parent == null || !parent.equals(this.getParent())) {
      removeFromParent();
    }
    this.parent = parent;
  }

  @OneToMany(fetch = FetchType.LAZY, orphanRemoval = false)
  @JoinColumn(name = "parent_id")
  @OrderColumn(name = "parent_order")
  public List<Organization> getOrganizations() {
    return this.organizations;
  }

  public void setOrganizations(final List<Organization> organizations) {
    organizations.stream().filter(org -> !this.equals(org.getParent())).forEach(org -> org.setParent(this));
    this.organizations = organizations;
  }

  @PrePersist
  void prePersist() {
    addToParent();
  }

  @PreUpdate
  void preUpdate() {
    addToParent();
  }

  private void addToParent() {
    if (parent != null && !parent.getOrganizations().contains(this)) {
      parent.getOrganizations().add(this);
    }
  }
  @PreRemove
  void removeFromParent() {
    if (parent != null) {
      parent.getOrganizations().remove(this);
    }
  }
}

I need to be able to :

  • set the parent from the child (which will be added at the end of the parent’s children list)
  • add a child from the parent which will be inserted at any index of the children’s list
  • move a child from a parent to another (or just removing the parent) without having a null element in my old parent’s children list afterward

But when creating an Organization and setting an existing Organization as its parent, the next call which loads the parent (read, update, delete) :

Organization otherParent = manager.read(otherParentId);

Organization someChild = newOrg("Some Child");
someChild.setParent(otherParent);
someChild = manager.create(someChild);

assertThat(otherParent.getOrganizations()).containsExactly(someChild);

manager.read(otherParentId); // throws HibernateException: null index column for collection: Organization.organizations

I’m probably doing something wrong but I don’t see what.

I have one hard contraint is that everything must be compliant with the java bean format.
I can have custom code inside the class, in the getters/setters and outside (annotations, …) but i can’t add methods like void addChild(Organization child) because these methods will not be used by the controllers which are generated code and common to many entities.

I’ve also tried to manually manage the index with an @OrderBy and an Integer field to no avail.

I have pushed a full reproducer test case here (the test failing is creatingOtherChildrenWithParentOrg) :

I think that this kind of mapping can only work if you use cascading and always persist/merge the parent organization and make the index column nullable in the table. The reason for that is, Hibernate splits management of the entity table and the “collection table” into 2 parts i.e. it first inserts/updates the columns of the entity table and only then updates the index column. The “collection table” management is triggered through the owner of the collection i.e. the parent.

I would discourage such a mapping. Either you use a proper @JoinTable where the index is managed or you switch to something like this:

public class Organization implements Comparable<Organization> {
    @Column(name = "parent_order")
    int order;

    @OneToMany(mappedBy = "parent")
    SortedSet<Organization> organizations;

    @Override
    public int compareTo(Organization other) {
        return Integer.compare(order, other.order);
    }