HHH-15393 - Improve write-paths to use mapping model

This commit is contained in:
Steve Ebersole 2022-11-30 23:50:01 -06:00
parent 26e7393775
commit 631d0bad71
4 changed files with 102 additions and 25 deletions

View File

@ -9,17 +9,20 @@
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;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection; import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.Map; import org.hibernate.mapping.Map;
import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Value; import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityAssociationMapping;
@ -32,6 +35,7 @@
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.VirtualModelPart; import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.persister.collection.BasicCollectionPersister;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.mutation.CollectionMutationTarget; import org.hibernate.persister.collection.mutation.CollectionMutationTarget;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
@ -222,16 +226,17 @@ public TableGroupJoin createTableGroupJoin(
null null
); );
lazyTableGroup.setTableGroupInitializerCallback( lazyTableGroup.setTableGroupInitializerCallback( (partTableGroup) -> {
tableGroup -> join.applyPredicate( // `partTableGroup` is the association table group
foreignKey.generateJoinPredicate( join.applyPredicate(
tableGroup.getPrimaryTableReference(), foreignKey.generateJoinPredicate(
collectionTableGroup.resolveTableReference( foreignKey.getKeyTable() ), partTableGroup.getPrimaryTableReference(),
sqlExpressionResolver, collectionTableGroup.resolveTableReference( foreignKey.getKeyTable() ),
creationContext sqlExpressionResolver,
) creationContext
) )
); );
} );
return join; return join;
} }
@ -341,13 +346,38 @@ else if ( getNature() == Nature.INDEX ) {
} }
else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty() ) ) { else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty() ) ) {
final ModelPart mappedByPart = resolveNamedTargetPart( bootCollectionDescriptor.getMappedByProperty(), getAssociatedEntityMappingType(), collectionDescriptor ); final ModelPart mappedByPart = resolveNamedTargetPart( bootCollectionDescriptor.getMappedByProperty(), getAssociatedEntityMappingType(), collectionDescriptor );
if ( mappedByPart instanceof Association ) { if ( mappedByPart instanceof ToOneAttributeMapping ) {
final Association toOne = (Association) mappedByPart; ////////////////////////////////////////////////
if ( toOne.getForeignKeyDescriptor() == null ) { // E.g.
// key is not yet ready, we need to wait //
return false; // @Entity
} // class Book {
foreignKey = toOne.getForeignKeyDescriptor(); // ...
// @ManyToOne(fetch = FetchType.LAZY)
// @JoinTable(name = "author_book",
// joinColumns = @JoinColumn(name = "book_id"),
// inverseJoinColumns = @JoinColumn(name="author_id",nullable = false))
// private Author author;
// }
//
// @Entity
// class Author {
// ...
// @OneToMany(mappedBy = "author")
// private List<Book> books;
// }
// create the foreign-key from the join-table (author_book) to the part table (Book) :
// `author_book.book_id -> Book.id`
final ManyToOne elementDescriptor = (ManyToOne) bootCollectionDescriptor.getElement();
assert elementDescriptor.isReferenceToPrimaryKey();
final String collectionTableName = ( (BasicCollectionPersister) collectionDescriptor ).getTableName();
foreignKey = createJoinTablePartForeignKey( collectionTableName, elementDescriptor, creationProcess );
creationProcess.registerForeignKey( this, foreignKey );
} }
else { else {
final PluralAttributeMapping manyToManyInverse = (PluralAttributeMapping) mappedByPart; final PluralAttributeMapping manyToManyInverse = (PluralAttributeMapping) mappedByPart;
@ -389,6 +419,58 @@ else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty(
return true; return true;
} }
private ForeignKeyDescriptor createJoinTablePartForeignKey(
String collectionTableName,
ManyToOne elementDescriptor,
MappingModelCreationProcess creationProcess) {
final EntityIdentifierMapping identifierMapping = getAssociatedEntityMappingType().getIdentifierMapping();
if ( identifierMapping.getNature() == EntityIdentifierMapping.Nature.SIMPLE ) {
final BasicEntityIdentifierMapping basicIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
assert elementDescriptor.getColumns().size() == 1;
final Column keyColumn = elementDescriptor.getColumns().get( 0 );
// collectionTableName.keyColumnName -> targetTableName.targetColumnName
final SelectableMapping keySelectableMapping = SelectableMappingImpl.from(
collectionTableName,
keyColumn,
basicIdMapping.getJdbcMapping(),
creationProcess.getCreationContext().getTypeConfiguration(),
true,
false,
creationProcess.getCreationContext().getSessionFactory().getJdbcServices().getDialect(),
creationProcess.getSqmFunctionRegistry()
);
final BasicAttributeMapping keyModelPart = BasicAttributeMapping.withSelectableMapping(
getAssociatedEntityMappingType(),
basicIdMapping,
basicIdMapping.getPropertyAccess(),
NoValueGeneration.INSTANCE,
true,
false,
keySelectableMapping
);
return new SimpleForeignKeyDescriptor(
// the key
keyModelPart,
// the target
basicIdMapping,
// refers to primary key
true,
// has a constraint
true,
// do not swap the sides
false
);
}
else {
throw new NotYetImplementedFor6Exception( getClass() );
}
}
private static ModelPart resolveNamedTargetPart( private static ModelPart resolveNamedTargetPart(
String targetPartName, String targetPartName,
EntityMappingType entityMappingType, EntityMappingType entityMappingType,

View File

@ -46,8 +46,6 @@
* @author Gavin King * @author Gavin King
* @author Steve Ebersole * @author Steve Ebersole
*/ */
// todo (6.0) : ^^ this introduces a problem in code that relies on `instanceof` checks
// against any of these interfaces when the wrapped type does not
public class CustomType<J> public class CustomType<J>
extends AbstractType extends AbstractType
implements ConvertedBasicType<J>, ProcedureParameterNamedBinder<J>, ProcedureParameterExtractionAware<J> { implements ConvertedBasicType<J>, ProcedureParameterNamedBinder<J>, ProcedureParameterExtractionAware<J> {

View File

@ -17,7 +17,6 @@
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority; import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Test; import org.junit.Test;
@ -100,7 +99,6 @@ public void testRevisionCounts() {
} }
@Test @Test
@FailureExpected( jiraKey = "HHH-15393", message = "Work for HHH-15393 (write-paths) causes a failure" )
// tests that Author has 3 books. // tests that Author has 3 books.
public void testAuthorState() { public void testAuthorState() {
EntityManager entityManager = getEntityManager(); EntityManager entityManager = getEntityManager();
@ -258,7 +256,8 @@ public static class Book {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinTable(name = "author_book", @JoinTable(name = "author_book",
joinColumns = @JoinColumn(name = "book_id"), inverseJoinColumns = @JoinColumn(name="author_id",nullable = false)) joinColumns = @JoinColumn(name = "book_id"),
inverseJoinColumns = @JoinColumn(name="author_id",nullable = false))
@NotAudited @NotAudited
private Author author; private Author author;

View File

@ -18,7 +18,6 @@
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase; import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority; import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Test; import org.junit.Test;
@ -101,7 +100,6 @@ public void testRevisionCounts() {
} }
@Test @Test
@FailureExpected( jiraKey = "HHH-15393", message = "Work for HHH-15393 (write-paths) causes a failure" )
// tests that Author has 3 books. // tests that Author has 3 books.
public void testAuthorState() { public void testAuthorState() {
EntityManager entityManager = getEntityManager(); EntityManager entityManager = getEntityManager();