Regression between 7.1.6 and 7.1.9: UnknownParameterException when updating non-key column on entity with @EmbeddedId + @MapsId, when a second entity is mapped to the same table

After updating to hibernate >= 7.1.9, I get the following error:

Unable to locate parameter CHILD.PARENT_ID for RESTRICT - UPDATE : org.hibernate.bugs.ChildEntityA
UnknownParameterException(CHILD.PARENT_ID for RESTRICT - UPDATE : o.h.b.ChildEntityA)
at org.hibernate.engine.jdbc.mutation.internal.JdbcValueBindingsImpl.bindValue(JdbcValueBindingsImpl.java:59)
at org.hibernate.persister.entity.mutation.AbstractMutationCoordinator.lambda$breakDownKeyJdbcValues$0(AbstractMutationCoordinator.java:212)
at org.hibernate.persister.entity.mutation.EntityTableMapping$AbstractKeyMapping.lambda$breakDownKeyJdbcValues$0(EntityTableMapping.java:312)
at org.hibernate.metamodel.mapping.internal.BasicAttributeMapping.forEachDisassembledJdbcValue(BasicAttributeMapping.java:494)
at org.hibernate.metamodel.mapping.Bindable.forEachJdbcValue(Bindable.java:173)
at org.hibernate.metamodel.internal.AbstractCompositeIdentifierMapping.forEachJdbcValue(AbstractCompositeIdentifierMapping.java:210)
at org.hibernate.metamodel.mapping.Bindable.forEachJdbcValue(Bindable.java:157)
at org.hibernate.persister.entity.mutation.EntityTableMapping$AbstractKeyMapping.breakDownKeyJdbcValues(EntityTableMapping.java:308)
at org.hibernate.persister.entity.mutation.AbstractMutationCoordinator.breakDownKeyJdbcValues(AbstractMutationCoordinator.java:209)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.decomposeForUpdate(UpdateCoordinatorStandard.java:855)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.doStaticUpdate(UpdateCoordinatorStandard.java:791)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.performUpdate(UpdateCoordinatorStandard.java:322)
at org.hibernate.persister.entity.mutation.UpdateCoordinatorStandard.update(UpdateCoordinatorStandard.java:240)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:161)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:634)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:505)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:381)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireFlush(SessionImpl.java:1453)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:484)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2080)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2002)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:426)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:166)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commitNoRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:251)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:241)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:89)
at org.hibernate.testing.orm.transaction.TransactionUtil.wrapInTransaction(TransactionUtil.java:77)
at org.hibernate.testing.orm.transaction.TransactionUtil.inTransaction(TransactionUtil.java:41)
at org.hibernate.testing.orm.junit.SessionFactoryExtension$SessionFactoryScopeImpl.inTransaction(SessionFactoryExtension.java:373)
at org.hibernate.testing.orm.junit.SessionFactoryExtension$SessionFactoryScopeImpl.inTransaction(SessionFactoryExtension.java:350)
at org.hibernate.bugs.ORMUnitTestCase.hhh123Test(ORMUnitTestCase.java:56)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)

One weird thing is that in my Quarkus application, the bug happens ~50% of the time: When you start the application, the exception will either always be thrown or never; it’s decided at startup and stays consistent. However, in the minimal reproducible example below, the exception is always thrown.

@DomainModel(
        annotatedClasses = {
                ParentEntityB.class,
                ChildEntityB.class,
                ChildEntityA.class,
                ParentEntityA.class,
        }
)
@ServiceRegistry(
        settings = {
                @Setting(name = AvailableSettings.SHOW_SQL, value = "true"),
                @Setting(name = AvailableSettings.FORMAT_SQL, value = "true"),
        }
)
@SessionFactory
class ORMUnitTestCase {
    @Test
    void multipleEntitiesSameTableCascadeAll(SessionFactoryScope scope) {
        scope.inTransaction(session -> {
            ParentEntityA parentEntityA = new ParentEntityA("id");
            ChildEntityA childEntityA = new ChildEntityA(parentEntityA);
            parentEntityA.addChild(childEntityA);
            session.persist(parentEntityA);
            session.flush();
        });

        scope.inTransaction(session -> {
            ParentEntityA terminal = session.find(ParentEntityA.class, "id");
            terminal.getChildren().forEach(childEntityA -> childEntityA.setFlag(true));
        });
    }
}

@Entity
@Table(name = "PARENT")
class ParentEntityA {

    @Id
    @Column(name = "ID", nullable = false)
    String id;

    @OneToMany(mappedBy = "parent", cascade = ALL)
    private final Set<ChildEntityA> children = new HashSet<>();

    public ParentEntityA() {}

    public ParentEntityA(String id) {
        this.id = id;
    }

    public void addChild(ChildEntityA child) {
        children.add(child);
    }

    public Set<ChildEntityA> getChildren() {
        return children;
    }

    public String getId() {
        return id;
    }
}

@Entity
@Table(name = "CHILD")
class ChildEntityA {

    @EmbeddedId
    private ChildId childId;

    @ManyToOne
    @MapsId("parentId")
    @JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
    private ParentEntityA parent;

    @Column(name = "FLAG", precision = 1)
    private boolean flag;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public ChildEntityA() {}

    public ChildEntityA(ParentEntityA parent) {
        this.parent = parent;
        this.childId = new ChildId(parent.getId());
    }
}

@Embeddable
class ChildId {

    @Column(name = "PARENT_ID", nullable = false)
    private String parentId;

    public ChildId() {}

    public ChildId(String parentId) {
        this.parentId = parentId;
    }
}

// The entities B exist (and are registered in the DomainModel) but are not used
@Entity
@Table(name = "PARENT")
class ParentEntityB {

    @Id
    @Column(name = "ID", nullable = false)
    String id;

    @OneToMany(mappedBy = "parent")
    private final Set<ChildEntityB> children = new HashSet<>();

    public ParentEntityB() {}
}

@Entity
@Table(name = "CHILD")
class ChildEntityB {

    @EmbeddedId
    private ChildId childId;

    @ManyToOne
    @MapsId("parentId")
    @JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
    private ParentEntityB parent;

    public ChildEntityB() {}
}

It seems that it has been introduced by HHH-18860. I get the same error with 7.1.24, 7.2.12 and 7.3.2. Last working version is 7.1.6.

I created an atlassian account for this but I am not been able to create an issue so I write here instead.

Is this expected but just not enforced in the older version or is this a bug ?

This is a bug. Please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

Opened Jira