HHH-15393 - Improve write-paths to use mapping model
This commit is contained in:
parent
33ce6a3d79
commit
ee1788c3c3
|
@ -14,7 +14,7 @@ import org.hibernate.engine.spi.IdentifierValue;
|
||||||
*
|
*
|
||||||
* @author Andrea Boriero
|
* @author Andrea Boriero
|
||||||
*/
|
*/
|
||||||
public interface CompositeIdentifierMapping extends EntityIdentifierMapping {
|
public interface CompositeIdentifierMapping extends EntityIdentifierMapping, EmbeddableValuedModelPart {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
default IdentifierValue getUnsavedStrategy() {
|
default IdentifierValue getUnsavedStrategy() {
|
||||||
|
|
|
@ -63,6 +63,27 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
|
||||||
private final AssociationKey associationKey;
|
private final AssociationKey associationKey;
|
||||||
private final boolean hasConstraint;
|
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(
|
public EmbeddedForeignKeyDescriptor(
|
||||||
EmbeddableValuedModelPart keyMappingType,
|
EmbeddableValuedModelPart keyMappingType,
|
||||||
EmbeddableValuedModelPart targetMappingType,
|
EmbeddableValuedModelPart targetMappingType,
|
||||||
|
|
|
@ -9,7 +9,6 @@ package org.hibernate.metamodel.mapping.internal;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.hibernate.NotYetImplementedFor6Exception;
|
|
||||||
import org.hibernate.annotations.NotFoundAction;
|
import org.hibernate.annotations.NotFoundAction;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
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.AssociationKey;
|
||||||
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
|
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
|
||||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||||
|
import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
|
||||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
||||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
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.PluralAttributeMapping;
|
||||||
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
import org.hibernate.metamodel.mapping.SelectableConsumer;
|
||||||
import org.hibernate.metamodel.mapping.SelectableMapping;
|
import org.hibernate.metamodel.mapping.SelectableMapping;
|
||||||
|
import org.hibernate.metamodel.mapping.SelectableMappings;
|
||||||
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
import org.hibernate.metamodel.mapping.VirtualModelPart;
|
||||||
import org.hibernate.persister.collection.BasicCollectionPersister;
|
import org.hibernate.persister.collection.BasicCollectionPersister;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
|
@ -53,6 +54,8 @@ import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNullElse;
|
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 <ul>
|
* Entity-valued collection-part mapped through a join table. Models both <ul>
|
||||||
|
@ -375,8 +378,12 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
||||||
|
|
||||||
final String collectionTableName = ( (BasicCollectionPersister) collectionDescriptor ).getTableName();
|
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 );
|
creationProcess.registerForeignKey( this, foreignKey );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -421,21 +428,28 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
||||||
|
|
||||||
private ForeignKeyDescriptor createJoinTablePartForeignKey(
|
private ForeignKeyDescriptor createJoinTablePartForeignKey(
|
||||||
String collectionTableName,
|
String collectionTableName,
|
||||||
ManyToOne elementDescriptor,
|
ManyToOne elementBootDescriptor,
|
||||||
MappingModelCreationProcess creationProcess) {
|
MappingModelCreationProcess creationProcess) {
|
||||||
final EntityIdentifierMapping identifierMapping = getAssociatedEntityMappingType().getIdentifierMapping();
|
final EntityMappingType associatedEntityMapping = getAssociatedEntityMappingType();
|
||||||
if ( identifierMapping.getNature() == EntityIdentifierMapping.Nature.SIMPLE ) {
|
final EntityIdentifierMapping associatedIdMapping = associatedEntityMapping.getIdentifierMapping();
|
||||||
final BasicEntityIdentifierMapping basicIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
|
assert associatedIdMapping != null;
|
||||||
|
|
||||||
assert elementDescriptor.getColumns().size() == 1;
|
// NOTE : `elementBootDescriptor` describes the key side of the fk
|
||||||
final Column keyColumn = elementDescriptor.getColumns().get( 0 );
|
// 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(
|
final SelectableMapping keySelectableMapping = SelectableMappingImpl.from(
|
||||||
collectionTableName,
|
collectionTableName,
|
||||||
keyColumn,
|
keyColumn,
|
||||||
basicIdMapping.getJdbcMapping(),
|
targetModelPart.getJdbcMapping(),
|
||||||
creationProcess.getCreationContext().getTypeConfiguration(),
|
creationProcess.getCreationContext().getTypeConfiguration(),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
@ -444,9 +458,9 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
||||||
);
|
);
|
||||||
|
|
||||||
final BasicAttributeMapping keyModelPart = BasicAttributeMapping.withSelectableMapping(
|
final BasicAttributeMapping keyModelPart = BasicAttributeMapping.withSelectableMapping(
|
||||||
getAssociatedEntityMappingType(),
|
associatedEntityMapping,
|
||||||
basicIdMapping,
|
targetModelPart,
|
||||||
basicIdMapping.getPropertyAccess(),
|
targetModelPart.getPropertyAccess(),
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
keySelectableMapping
|
keySelectableMapping
|
||||||
|
@ -456,17 +470,45 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
||||||
// the key
|
// the key
|
||||||
keyModelPart,
|
keyModelPart,
|
||||||
// the target
|
// the target
|
||||||
basicIdMapping,
|
targetModelPart,
|
||||||
// refers to primary key
|
// refers to primary key
|
||||||
true,
|
true,
|
||||||
// has a constraint
|
!elementBootDescriptor.isNullable(),
|
||||||
true,
|
|
||||||
// do not swap the sides
|
// do not swap the sides
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 ComponentType componentType;
|
||||||
final boolean sorted;
|
final boolean sorted;
|
||||||
if ( bootValueMapping instanceof Collection ) {
|
if ( bootValueMapping instanceof Collection ) {
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p/>
|
||||||
|
* 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<String> 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<Book> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
* <p/>
|
||||||
|
* 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<String> 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<Book> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* Hibernate, Relational Persistence for Idiomatic Java
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
*
|
*
|
||||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
* 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
|
* 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;
|
package org.hibernate.orm.test.mapping.manytoone.jointable;
|
||||||
|
|
||||||
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 org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||||
import org.hibernate.metamodel.mapping.ModelPart;
|
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.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
import org.junit.jupiter.api.Test;
|
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.instanceOf;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
Loading…
Reference in New Issue