diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ImplicitToOneJoinTableSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ImplicitToOneJoinTableSecondPass.java index bd28f1f8c2b0..d95cd78c9897 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ImplicitToOneJoinTableSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ImplicitToOneJoinTableSecondPass.java @@ -14,6 +14,7 @@ import org.hibernate.mapping.Join; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; import org.hibernate.mapping.Table; import jakarta.persistence.JoinTable; @@ -58,9 +59,9 @@ public ImplicitToOneJoinTableSecondPass( } // Note: Instead of deferring creation of the whole Table object, perhaps - // we could create it in the first pass, and reset its name here in - // the second pass. The problem is that there is some quite involved - // logic in TableBinder that isn't set up for that. + // we could create it in the first pass and reset its name here in + // the second pass. The problem is that there is some quite involved + // logic in TableBinder that isn't set up for that. private void inferJoinTableName(TableBinder tableBinder, Map persistentClasses) { if ( isEmpty( tableBinder.getName() ) ) { @@ -116,6 +117,12 @@ public void doSecondPass(Map persistentClasses) { final Table table = tableBinder.bind(); value.setTable( table ); final Join join = propertyHolder.addJoin( joinTable, table, true ); + final PersistentClass owner = propertyHolder.getPersistentClass(); + final Property property = owner.getProperty( inferredData.getPropertyName() ); + assert property != null; + // move the property from the main table to the new join table + owner.removeProperty( property ); + join.addProperty( property ); if ( notFoundAction != null ) { join.disableForeignKeyCreation(); } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 12c0d9e4f65a..0df36e9a3e8c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -1211,4 +1211,11 @@ public Supplier getDeleteExpectation() { public void setDeleteExpectation(Supplier deleteExpectation) { this.deleteExpectation = deleteExpectation; } + + public void removeProperty(Property property) { + if ( !declaredProperties.remove( property ) ) { + throw new IllegalArgumentException( "Property not among declared properties: " + property.getName() ); + } + properties.remove( property ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java new file mode 100644 index 000000000000..a3b486f76d17 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneImplicitJoinTableTest.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.manytoone.jointable; + +import jakarta.persistence.*; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.AssertionsKt.assertNotNull; + +@Jpa(annotatedClasses = + {ManyToOneImplicitJoinTableTest.X.class, + ManyToOneImplicitJoinTableTest.Y.class}) +class ManyToOneImplicitJoinTableTest { + @JiraKey("HHH-19564") @Test + void test(EntityManagerFactoryScope scope) { + scope.inTransaction( s -> { + X x = new X(); + Y y = new Y(); + y.x = x; + s.persist( x ); + s.persist( y ); + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + y.name = "Gavin"; + } ); + scope.inTransaction( s -> { + Y y = s.find( Y.class, 0L ); + assertEquals("Gavin", y.name); + assertNotNull(y.x); + } ); + } + @Entity(name="Y") + static class Y { + @Id + long id; + String name; + @JoinTable + @ManyToOne X x; + } + @Entity(name="X") + static class X { + @Id + long id; + } +}