Hibernate 6 PrimaryKeyJoinColumn causes implicit version update to fail

Hello - I’m working on updating a large application from Hibernate 5.x to Hibernate 6 (as part of a Spring Boot update to Spring Boot 3.x). Most of the upgrade has been quite smooth but I am running into a few of our tests failing. I have isolated the issue and can reproduce in the latest Hibernate source by adding a new test case. Any help in suggesting a fix or workaround would be appreciated!

When I trace through the Hibernate code in the debugger, it looks like the @PrimaryKeyJoinColumn annotation is causing a mix-up when trying to generate the update statement for the version column. If I make the @PrimaryKeyJoinColumn column name match the parent class column name, then Hibernate works as expected and updates the version field.

The exception I am seeing looks similar to the following:

Unable to locate parameter `"PrimaryKeyJoinColumnTest$VersionedFruit".id` for RESTRICT - UPDATE : org.hibernate.orm.test.jpa.inheritance.PrimaryKeyJoinColumnTest$Raspberry
UnknownParameterException(`"PrimaryKeyJoinColumnTest$VersionedFruit".id` for RESTRICT - UPDATE : o.h.o.t.j.i.PrimaryKeyJoinColumnTest$Raspberry)
	at app//org.hibernate.engine.jdbc.mutation.internal.JdbcValueBindingsImpl.bindValue(JdbcValueBindingsImpl.java:60)
	at app//org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.lambda$doVersionUpdate$3(UpdateCoordinatorStandard.java:474)
	at app//org.hibernate.persister.entity.mutation.EntityTableMapping$KeyMapping.lambda$breakDownKeyJdbcValues$0(EntityTableMapping.java:234)
	at app//org.hibernate.type.BasicType.forEachDisassembledJdbcValue(BasicType.java:134)
	at app//org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl.forEachDisassembledJdbcValue(BasicEntityIdentifierMappingImpl.java:398)
	at app//org.hibernate.metamodel.mapping.Bindable.forEachJdbcValue(Bindable.java:194)
	at app//org.hibernate.metamodel.mapping.Bindable.forEachJdbcValue(Bindable.java:178)
	at app//org.hibernate.persister.entity.mutation.EntityTableMapping$KeyMapping.breakDownKeyJdbcValues(EntityTableMapping.java:230)
	at app//org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.doVersionUpdate(UpdateCoordinatorStandard.java:471)
	at app//org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.handlePotentialImplicitForcedVersionIncrement(UpdateCoordinatorStandard.java:415)
	at app//org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.coordinateUpdate(UpdateCoordinatorStandard.java:154)
	at app//org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2673)

Here is a test case to reproduce:

package org.hibernate.orm.test.jpa.inheritance;

import java.util.HashSet;
import java.util.Set;

import jakarta.persistence.*;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.junit.jupiter.api.Test;

@Jpa(annotatedClasses = {
		PrimaryKeyJoinColumnTest.VersionedFruit.class,
		PrimaryKeyJoinColumnTest.Raspberry.class,
		PrimaryKeyJoinColumnTest.Drupelet.class
})
public class PrimaryKeyJoinColumnTest {

	@Test
	//@FailureExpected
	public void testImplicitForcedVersionIncrementWithPrimaryKeyJoinColumn(EntityManagerFactoryScope scope) {
		scope.inEntityManager(
				entityManager -> {
					Raspberry raspberry = new Raspberry();
					Drupelet drupelet = new Drupelet();
					try {
						entityManager.getTransaction().begin();
						entityManager.persist(drupelet);
						raspberry.setDrupelets(new HashSet<>());
						raspberry.getDrupelets().add(drupelet);
						entityManager.persist(raspberry);
						entityManager.flush();

						// Force an implicit update of the raspberry object so the version will increment on next flush
						raspberry.getDrupelets().clear();
						entityManager.flush();

						entityManager.getTransaction().commit();
					}
					catch (Exception e) {
						if ( entityManager.getTransaction().isActive() ) {
							entityManager.getTransaction().rollback();
						}
						throw e;
					}
				}
		);
	}

	@Entity
	@Inheritance(strategy = InheritanceType.JOINED)
	public abstract class VersionedFruit {
		Long id;
		Long version = 0L;

		@Id
		@GeneratedValue
		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		@Version
		public Long getVersion() {
			return version;
		}

		public void setVersion(Long version) {
			this.version = version;
		}
	}

	@Entity
	@PrimaryKeyJoinColumn(name = "RASPBERRY_ID")
	public class Raspberry extends VersionedFruit {
		private Set<Drupelet> drupelets;

		@OneToMany(fetch = FetchType.LAZY)
		@JoinTable(
				name = "DRUPELETS",
				joinColumns = {@JoinColumn(name = "RASPBERRY_ID")},
				inverseJoinColumns = {@JoinColumn(name = "DRUPELET_ID")}
		)
		public Set<Drupelet> getDrupelets() {
			return drupelets;
		}

		public void setDrupelets(Set<Drupelet> drupelets) {
			this.drupelets = drupelets;
		}
	}

	@Entity
	public class Drupelet {
		Long id;

		@Id
		@GeneratedValue
		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		};
	}
}

Thanks a lot for your effort. Please create an issue in the issue tracker(https://hibernate.atlassian.net) and attach your testcase to it.

Thanks. I created [HHH-16667] - Hibernate JIRA