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