diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CompositeIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CompositeIdentifierMapping.java
index 91d1c4bd62..32a67ecb78 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CompositeIdentifierMapping.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CompositeIdentifierMapping.java
@@ -14,7 +14,7 @@ import org.hibernate.engine.spi.IdentifierValue;
*
* @author Andrea Boriero
*/
-public interface CompositeIdentifierMapping extends EntityIdentifierMapping {
+public interface CompositeIdentifierMapping extends EntityIdentifierMapping, EmbeddableValuedModelPart {
@Override
default IdentifierValue getUnsavedStrategy() {
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java
index e74c877a5e..74f31946e2 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java
@@ -63,6 +63,27 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
private final AssociationKey associationKey;
private final boolean hasConstraint;
+ public EmbeddedForeignKeyDescriptor(
+ String keyTable,
+ SelectableMappings keySelectableMappings,
+ EmbeddableValuedModelPart keyMappingType,
+ String targetTable,
+ SelectableMappings targetSelectableMappings,
+ EmbeddableValuedModelPart targetMappingType,
+ boolean hasConstraint,
+ MappingModelCreationProcess creationProcess) {
+ this(
+ keyMappingType,
+ targetMappingType,
+ keyTable,
+ keySelectableMappings,
+ targetTable,
+ targetSelectableMappings,
+ hasConstraint,
+ creationProcess
+ );
+ }
+
public EmbeddedForeignKeyDescriptor(
EmbeddableValuedModelPart keyMappingType,
EmbeddableValuedModelPart targetMappingType,
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
index 37a4f67e9f..1815c99cac 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java
@@ -9,7 +9,6 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.Locale;
import java.util.function.Consumer;
-import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@@ -24,6 +23,7 @@ import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
+import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
@@ -34,6 +34,7 @@ import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
+import org.hibernate.metamodel.mapping.SelectableMappings;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.persister.collection.BasicCollectionPersister;
import org.hibernate.persister.collection.CollectionPersister;
@@ -53,6 +54,8 @@ import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.type.EntityType;
import static java.util.Objects.requireNonNullElse;
+import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.createInverseModelPart;
+import static org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper.getPropertyOrder;
/**
* Entity-valued collection-part mapped through a join table. Models both
@@ -375,8 +378,12 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
final String collectionTableName = ( (BasicCollectionPersister) collectionDescriptor ).getTableName();
- foreignKey = createJoinTablePartForeignKey( collectionTableName, elementDescriptor, creationProcess );
+ // this fk will refer to the associated entity's id. if that id is not ready yet, delay this creation
+ if ( getAssociatedEntityMappingType().getIdentifierMapping() == null ) {
+ return false;
+ }
+ foreignKey = createJoinTablePartForeignKey( collectionTableName, elementDescriptor, creationProcess );
creationProcess.registerForeignKey( this, foreignKey );
}
else {
@@ -421,21 +428,28 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
private ForeignKeyDescriptor createJoinTablePartForeignKey(
String collectionTableName,
- ManyToOne elementDescriptor,
+ ManyToOne elementBootDescriptor,
MappingModelCreationProcess creationProcess) {
- final EntityIdentifierMapping identifierMapping = getAssociatedEntityMappingType().getIdentifierMapping();
- if ( identifierMapping.getNature() == EntityIdentifierMapping.Nature.SIMPLE ) {
- final BasicEntityIdentifierMapping basicIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
+ final EntityMappingType associatedEntityMapping = getAssociatedEntityMappingType();
+ final EntityIdentifierMapping associatedIdMapping = associatedEntityMapping.getIdentifierMapping();
+ assert associatedIdMapping != null;
- assert elementDescriptor.getColumns().size() == 1;
- final Column keyColumn = elementDescriptor.getColumns().get( 0 );
+ // NOTE : `elementBootDescriptor` describes the key side of the fk
+ // NOTE : `associatedIdMapping` is the target side model-part
- // collectionTableName.keyColumnName -> targetTableName.targetColumnName
+ // we have the fk target model-part and selectables via the associated entity's id mapping
+ // and need to create the inverse (key) selectable-mappings and composite model-part
+
+ if ( associatedIdMapping.getNature() == EntityIdentifierMapping.Nature.SIMPLE ) {
+ final BasicEntityIdentifierMapping targetModelPart = (BasicEntityIdentifierMapping) associatedIdMapping;
+
+ assert elementBootDescriptor.getColumns().size() == 1;
+ final Column keyColumn = elementBootDescriptor.getColumns().get( 0 );
final SelectableMapping keySelectableMapping = SelectableMappingImpl.from(
collectionTableName,
keyColumn,
- basicIdMapping.getJdbcMapping(),
+ targetModelPart.getJdbcMapping(),
creationProcess.getCreationContext().getTypeConfiguration(),
true,
false,
@@ -444,9 +458,9 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
);
final BasicAttributeMapping keyModelPart = BasicAttributeMapping.withSelectableMapping(
- getAssociatedEntityMappingType(),
- basicIdMapping,
- basicIdMapping.getPropertyAccess(),
+ associatedEntityMapping,
+ targetModelPart,
+ targetModelPart.getPropertyAccess(),
true,
false,
keySelectableMapping
@@ -456,17 +470,45 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
// the key
keyModelPart,
// the target
- basicIdMapping,
+ targetModelPart,
// refers to primary key
true,
- // has a constraint
- true,
+ !elementBootDescriptor.isNullable(),
// do not swap the sides
false
);
}
else {
- throw new NotYetImplementedFor6Exception( getClass() );
+ final CompositeIdentifierMapping targetModelPart = (CompositeIdentifierMapping) associatedIdMapping;
+
+ final SelectableMappings keySelectableMappings = SelectableMappingsImpl.from(
+ collectionTableName,
+ elementBootDescriptor,
+ getPropertyOrder( elementBootDescriptor, creationProcess ),
+ creationProcess.getCreationContext().getSessionFactory(),
+ creationProcess.getCreationContext().getTypeConfiguration(),
+ elementBootDescriptor.getColumnInsertability(),
+ elementBootDescriptor.getColumnUpdateability(),
+ creationProcess.getCreationContext().getSessionFactory().getJdbcServices().getDialect(),
+ creationProcess.getSqmFunctionRegistry()
+ );
+
+ return new EmbeddedForeignKeyDescriptor(
+ collectionTableName,
+ keySelectableMappings,
+ createInverseModelPart(
+ targetModelPart,
+ associatedEntityMapping,
+ this,
+ keySelectableMappings,
+ creationProcess
+ ),
+ targetModelPart.getContainingTableExpression(),
+ targetModelPart.getPartMappingType(),
+ targetModelPart,
+ !elementBootDescriptor.isNullable(),
+ creationProcess
+ );
}
}
diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java
index ff67841e42..56a8a5f583 100644
--- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java
+++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java
@@ -1225,7 +1225,7 @@ public class MappingModelCreationHelper {
}
}
- private static int[] getPropertyOrder(Value bootValueMapping, MappingModelCreationProcess creationProcess) {
+ public static int[] getPropertyOrder(Value bootValueMapping, MappingModelCreationProcess creationProcess) {
final ComponentType componentType;
final boolean sorted;
if ( bootValueMapping instanceof Collection ) {
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableCompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableCompositeIdTest.java
new file mode 100644
index 0000000000..dbc43056be
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableCompositeIdTest.java
@@ -0,0 +1,260 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
+ */
+package org.hibernate.orm.test.mapping.manytoone.jointable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.hibernate.metamodel.mapping.PluralAttributeMapping;
+import org.hibernate.metamodel.mapping.internal.EmbeddedForeignKeyDescriptor;
+import org.hibernate.metamodel.mapping.internal.ManyToManyCollectionPart;
+import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
+import org.hibernate.persister.entity.EntityPersister;
+
+import org.hibernate.testing.orm.junit.DomainModel;
+import org.hibernate.testing.orm.junit.SessionFactory;
+import org.hibernate.testing.orm.junit.SessionFactoryScope;
+import org.junit.jupiter.api.Test;
+
+import jakarta.persistence.Basic;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinTable;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Asserts the runtime model descriptors related to the inverse side
+ * of a many-to-one with join-table.
+ *
+ * This tests simple keys. See {@link InverseManyToOneJoinTableSimpleIdTest} for
+ * simple id testing
+ *
+ * @author Steve Ebersole
+ */
+@DomainModel( annotatedClasses = {
+ InverseManyToOneJoinTableCompositeIdTest.Book.class,
+ InverseManyToOneJoinTableCompositeIdTest.Author.class
+} )
+@SessionFactory
+public class InverseManyToOneJoinTableCompositeIdTest {
+ @Test
+ public void assertModel(SessionFactoryScope scope) {
+ final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
+ .getRuntimeMetamodels()
+ .getMappingMetamodel();
+
+ final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( Author.class );
+ final PluralAttributeMapping books = (PluralAttributeMapping) entityDescriptor.findAttributeMapping( "books" );
+ final ManyToManyCollectionPart booksElementDescriptor = (ManyToManyCollectionPart) books.getElementDescriptor();
+ final EmbeddedForeignKeyDescriptor booksFk = (EmbeddedForeignKeyDescriptor) booksElementDescriptor.getForeignKeyDescriptor();
+
+ assertThat( booksFk.getKeyTable() ).isEqualTo( "book_authors" );
+ booksFk.getKeyPart().forEachSelectable( (selectionIndex, selectableMapping) -> {
+ final String expectedColumnName;
+ if ( selectionIndex == 0 ) {
+ expectedColumnName = "book_int_key";
+ }
+ else {
+ assert selectionIndex == 1;
+ expectedColumnName = "book_char_key";
+ }
+ assertThat( selectableMapping.getSelectionExpression() ).isEqualTo( expectedColumnName );
+ } );
+
+ assertThat( booksFk.getTargetTable() ).isEqualTo( "books" );
+ booksFk.getTargetPart().forEachSelectable( (selectionIndex, selectableMapping) -> {
+ final String expectedColumnName;
+ if ( selectionIndex == 0 ) {
+ expectedColumnName = "int_key";
+ }
+ else {
+ assert selectionIndex == 1;
+ expectedColumnName = "char_key";
+ }
+ assertThat( selectableMapping.getSelectionExpression() ).isEqualTo( expectedColumnName );
+ } );
+ }
+
+ @Test
+ public void usageSmokeTest(SessionFactoryScope scope) {
+ createTestData( scope );
+
+ try {
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = session.get( Author.class, 1 );
+ verifyStephenKingBooks( stephenKing );
+ } );
+
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = session
+ .createSelectionQuery( "from Author a join fetch a.books where a.id = 1", Author.class )
+ .getSingleResult();
+ verifyStephenKingBooks( stephenKing );
+ } );
+ }
+ finally {
+ dropTestData( scope );
+ }
+ }
+
+ private void verifyStephenKingBooks(Author author) {
+ final List bookNames = author.books.stream().map( Book::getName ).collect( Collectors.toList() );
+ assertThat( bookNames ).contains( "It", "The Shining" );
+ }
+
+ private void createTestData(SessionFactoryScope scope) {
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = new Author( 1, "Stephen King" );
+ final Author johnMilton = new Author( 2, "John Milton" );
+ session.persist( stephenKing );
+ session.persist( johnMilton );
+
+ session.persist( new Book( new BookPk( 1, "king" ), "It", stephenKing ) );
+ session.persist( new Book( new BookPk( 2, "king" ), "The Shining", stephenKing ) );
+
+ session.persist( new Book( new BookPk( 1, "milton" ), "Paradise Lost", johnMilton ) );
+ } );
+ }
+
+ private void dropTestData(SessionFactoryScope scope) {
+ scope.inTransaction( (session) -> {
+ session.createQuery( "from Author", Author.class ).list().forEach( session::remove );
+ } );
+ scope.inTransaction( (session) -> {
+ final Long bookCount = session.createSelectionQuery( "select count(1) from Book", Long.class ).uniqueResult();
+ assertThat( bookCount ).isEqualTo( 0L );
+ } );
+ }
+
+ @Entity( name = "Book" )
+ @Table( name = "books" )
+ public static class Book {
+ @EmbeddedId
+ private BookPk id;
+
+ @Basic
+ private String name;
+
+ @ManyToOne
+ @JoinTable(
+ name = "book_authors",
+ joinColumns = {
+ @JoinColumn(name = "book_int_key", referencedColumnName = "int_key"),
+ @JoinColumn(name = "book_char_key", referencedColumnName = "char_key")
+ },
+ inverseJoinColumns = @JoinColumn(name="author_id",nullable = false)
+ )
+ private Author author;
+
+ private Book() {
+ // for use by Hibernate
+ }
+
+ public Book(BookPk id, String name, Author author) {
+ this.id = id;
+ this.name = name;
+ this.author = author;
+ }
+
+ public BookPk getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Author getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(Author author) {
+ this.author = author;
+ }
+ }
+
+ @Embeddable
+ public static class BookPk {
+ @Column( name="int_key" )
+ private Integer key1;
+ @Column( name="char_key" )
+ private String key2;
+
+ private BookPk() {
+ // for Hibernate
+ }
+
+ public BookPk(Integer key1, String key2) {
+ this.key1 = key1;
+ this.key2 = key2;
+ }
+
+ public Integer getKey1() {
+ return key1;
+ }
+
+ public String getKey2() {
+ return key2;
+ }
+ }
+
+ @Entity( name = "Author" )
+ @Table( name = "authors" )
+ public static class Author {
+
+ @Id
+ private Integer id;
+
+ @Basic
+ private String name;
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "author")
+ private List books;
+
+ private Author() {
+ // for use by Hibernate
+ }
+
+ public Author(Integer id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ @Embeddable
+ public static class AuthorPk {
+ @Column(name = "first_name")
+ private String firstName;
+ @Column(name = "last_name")
+ private String lastName;
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableSimpleIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableSimpleIdTest.java
new file mode 100644
index 0000000000..d89c2b6cb7
--- /dev/null
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/InverseManyToOneJoinTableSimpleIdTest.java
@@ -0,0 +1,184 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
+ */
+package org.hibernate.orm.test.mapping.manytoone.jointable;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.hibernate.metamodel.mapping.PluralAttributeMapping;
+import org.hibernate.metamodel.mapping.internal.ManyToManyCollectionPart;
+import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
+import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
+import org.hibernate.persister.entity.EntityPersister;
+
+import org.hibernate.testing.orm.junit.DomainModel;
+import org.hibernate.testing.orm.junit.SessionFactory;
+import org.hibernate.testing.orm.junit.SessionFactoryScope;
+import org.junit.jupiter.api.Test;
+
+import jakarta.persistence.Basic;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinTable;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Asserts the runtime model descriptors related to the inverse side
+ * of a many-to-one with join-table.
+ *
+ * This tests simple keys. See {@link InverseManyToOneJoinTableCompositeIdTest} for
+ * composite id testing
+ *
+ * @author Steve Ebersole
+ */
+@DomainModel( annotatedClasses = { InverseManyToOneJoinTableSimpleIdTest.Book.class, InverseManyToOneJoinTableSimpleIdTest.Author.class } )
+@SessionFactory
+public class InverseManyToOneJoinTableSimpleIdTest {
+ @Test
+ public void assertModel(SessionFactoryScope scope) {
+ final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory()
+ .getRuntimeMetamodels()
+ .getMappingMetamodel();
+
+ final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( Author.class );
+ final PluralAttributeMapping books = (PluralAttributeMapping) entityDescriptor.findAttributeMapping( "books" );
+ final ManyToManyCollectionPart booksElementDescriptor = (ManyToManyCollectionPart) books.getElementDescriptor();
+ final SimpleForeignKeyDescriptor booksFk = (SimpleForeignKeyDescriptor) booksElementDescriptor.getForeignKeyDescriptor();
+
+ assertThat( booksFk.getKeyTable() ).isEqualTo( "book_authors" );
+ assertThat( booksFk.getKeyPart().getSelectionExpression() ).isEqualTo( "book_id" );
+
+ assertThat( booksFk.getTargetTable() ).isEqualTo( "books" );
+ assertThat( booksFk.getTargetPart().getSelectionExpression() ).isEqualTo( "id" );
+ }
+
+ @Test
+ public void usageSmokeTest(SessionFactoryScope scope) {
+ createTestData( scope );
+
+ try {
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = session.get( Author.class, 1 );
+ verifyStephenKingBooks( stephenKing );
+ } );
+
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = session
+ .createSelectionQuery( "from Author a join fetch a.books where a.id = 1", Author.class )
+ .getSingleResult();
+ verifyStephenKingBooks( stephenKing );
+ } );
+ }
+ finally {
+ dropTestData( scope );
+ }
+ }
+
+ private void verifyStephenKingBooks(Author author) {
+ final List bookNames = author.books.stream().map( Book::getName ).collect( Collectors.toList() );
+ assertThat( bookNames ).contains( "It", "The Shining" );
+ }
+
+ private void createTestData(SessionFactoryScope scope) {
+ scope.inTransaction( (session) -> {
+ final Author stephenKing = new Author( 1, "Stephen King" );
+ final Author johnMilton = new Author( 2, "John Milton" );
+ session.persist( stephenKing );
+ session.persist( johnMilton );
+
+ session.persist( new Book( 1, "It", stephenKing ) );
+ session.persist( new Book( 2, "The Shining", stephenKing ) );
+
+ session.persist( new Book( 3, "Paradise Lost", johnMilton ) );
+ } );
+ }
+
+ private void dropTestData(SessionFactoryScope scope) {
+ scope.inTransaction( (session) -> {
+ session.createQuery( "from Author", Author.class ).list().forEach( session::remove );
+ } );
+ scope.inTransaction( (session) -> {
+ final Long bookCount = session.createSelectionQuery( "select count(1) from Book", Long.class ).uniqueResult();
+ assertThat( bookCount ).isEqualTo( 0L );
+ } );
+ }
+
+ @Entity( name = "Book" )
+ @Table( name = "books" )
+ public static class Book {
+ @Id
+ private Integer id;
+ @Basic
+ private String name;
+ @ManyToOne
+ @JoinTable(name = "book_authors",
+ joinColumns = @JoinColumn(name = "book_id"),
+ inverseJoinColumns = @JoinColumn(name="author_id",nullable = false))
+ private Author author;
+
+ private Book() {
+ // for use by Hibernate
+ }
+
+ public Book(Integer id, String name, Author author) {
+ this.id = id;
+ this.name = name;
+ this.author = author;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ @Entity( name = "Author" )
+ @Table( name = "authors" )
+ public static class Author {
+ @Id
+ private Integer id;
+ @Basic
+ private String name;
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "author")
+ private List books;
+
+ private Author() {
+ // for use by Hibernate
+ }
+
+ public Author(Integer id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneJoinTableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneJoinTableTest.java
similarity index 98%
rename from hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneJoinTableTest.java
rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneJoinTableTest.java
index b7a2313d3f..245276dbe0 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/ManyToOneJoinTableTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/manytoone/jointable/ManyToOneJoinTableTest.java
@@ -1,18 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
- * License: GNU Lesser General Public License (LGPL), version 2.1 or later
- * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html.
*/
-package org.hibernate.orm.test.mapping.manytoone;
-
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import jakarta.persistence.JoinColumn;
-import jakarta.persistence.JoinTable;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.OneToOne;
-import jakarta.persistence.Table;
+package org.hibernate.orm.test.mapping.manytoone.jointable;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
@@ -25,6 +17,14 @@ import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.JoinTable;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.OneToOne;
+import jakarta.persistence.Table;
+
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;