Questions about @OrderColumn in Hibernate 7

I have some question about the changed behaviour of the OrderColumn annotation.

First of all, how is this supposed to work in Hibernate 7:

@OneToMany(mappedBy = "foo", cascade = CascadeType.ALL)
@OrderColumn(name = "pos", nullable = false)
private List<Bar> barList;

If pos is not a mapped field in Bar, then “the persistence provider is responsible for maintaining the order upon retrieval and in the database”.

Upon persisting the entity that contains the collection, Hibernate will execute several insert statements but will not include the pos value. If I set nullable = true we see that Hibernate will only update the pos value after the entity has been inserted.

So how is it even possible to use @OrderColumn with a not nullable column?

Second question:

I was told the following is supposedly not a bug. I’d appreciate if someone could help me understand it.

@Entity
public class Foo {

	@Id
	@GeneratedValue
	private Long id;

	@OneToMany(mappedBy = "foo", cascade = CascadeType.ALL)
	@OrderColumn(name = "pos")
	private List<Bar> barList;

	public void setBarList(List<Bar> barList) {
		barList.forEach(bar -> bar.setFoo(this));
		this.barList = barList;
	}
}

@Entity
public class Bar {

	@Id
	@GeneratedValue
	private Long id;

	@ManyToOne
	@JoinColumn(name = "fk_foo")
	private Foo foo;

	@Column(insertable = false)
	private Integer pos;
}

I set insertable = false so that Hibernate 7 will still update the pos column. This works. However, Hibernate will only update the database, but not the entity. Have a look at this test:

Foo foo = new Foo();
foo.setBarList(List.of(new Bar(), new Bar()));
entityManager.persist(foo);

Foo result = entityManager.createQuery("FROM Foo f, IN(f.barList) bar WHERE f.id = 1 AND  bar.pos = 0", Foo.class).getSingleResult();
assertNotNull(result.getBarList().getFirst().getId());
// Although something was found, which means 'pos' is not null, the assertion fails.
assertNotNull(result.getBarList().getFirst().getPos());

This demonstrates that Hibernate does indeed update the order column and correctly persists the Bar entities. When loading the entity, the id is set but the order column is null.

It’s not possible with @OneToMany(mappedBy = "..."), because Hibernate ORM splits the table state changes per entity and collection. The collection element is being managed through an entity persister, but then the order column would be managed by a collection persister, which is a separate step. That’s why you see an insert with a null value for pos first (entity persister) and only then you see an update happening that sets the pos (collection persister).

This design simply requires that the pos column is nullable. Moving the update of the pos column into the insert requires changes that we are thinking about, but are currently not possible.

This demonstrates that Hibernate does indeed update the order column and correctly persists the Bar entities. When loading the entity, the id is set but the order column is null.

Like I described before, the order column is updated through the collection persister, but that will only update the table state, not the entity itself.
If you want the value in the entity to correspond to the list position, you will have to update the pos field, just like you update the foo field.

1 Like

I understand, but that would be unnecessary, since the purpose of OrderColumn is precisely to prevent the user from having to implement it themselves.

I like the idea of letting the persistence provider maintain the ordering but on the other hand it feels really weird that one entity defines columns of another entity. Before Hibernate 7 one could just map the field for documentation purposes but this no longer works. Except when making it not updateable or insertable. But even this is against the spec if I understood it correctly.

T3rm1 you have made multiple incorrect assertions here.

First of all, with the following mapping there is no change to behavior in Hibernate 7!

Entity
class X {
    @GeneratedValue @Id long id;
    @OneToMany(mappedBy = "x", cascade = CascadeType.ALL)
    @OrderColumn(name = "pos")
    List<Y> ys = new ArrayList<>();
}

@Entity
class Y {
    @GeneratedValue @Id long id;
    @ManyToOne X x;
}

Now, by rights, this mapping is “wrong”, because X.ys is a “partially owned collection”, something that doesn’t exist in JPA, but for backward compatibility, I kept it working.

This is accurately described in the migration guide, I quote:

In Hibernate 7, these SQL UPDATE statements only occur if the @OrderColumn is not also mapped by a field of the entity.

Secondly, the following mapping never worked if there was no field mapping pos in Bar.

@OneToMany(mappedBy = "foo", cascade = CascadeType.ALL)
@OrderColumn(name = "pos", nullable = false)
private List<Bar> barList;

Nothing has changed here. It didn’t work before, and it still doesn’t work now.

Hibernate will only update the database, but not the entity

That is correct and normal and is how Hibernate and JPA have always worked. No expectation exists that Hibernate will magically modify your entities behind your back. The state of your loaded entities is always under your control.

You are saying “accurately described”. But if pos IS mapped AND either not insertable OR not updatable, then Hibernate also issues update statements. So I’d argue that the migration guide does not explain the behaviour accurately.

Now, by rights, this mapping is “wrong”, because X.ys is a “partially owned collection”, something that doesn’t exist in JPA

That’s something I didn’t understand. What is a partially owned collection? Is anything with@OneToMany(mappedBy = "parent") considered as such? What’s the best practice with @OrderColumnand what is “right”?

No expectation exists that Hibernate will magically modify your entities behind your back.

Hibernate updates the @Id @GeneratedValue field when I call em.persist(y).Is that not considered an exception?

@T3rm1 I’m not going to respond any further.