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 @@ 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;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.IndexedCollection;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.Map;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
@ -32,6 +35,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.persister.collection.BasicCollectionPersister;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.mutation.CollectionMutationTarget;
import org.hibernate.spi.NavigablePath;
@ -222,16 +226,17 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
null
);
lazyTableGroup.setTableGroupInitializerCallback(
tableGroup -> join.applyPredicate(
lazyTableGroup.setTableGroupInitializerCallback( (partTableGroup) -> {
// `partTableGroup` is the association table group
join.applyPredicate(
foreignKey.generateJoinPredicate(
tableGroup.getPrimaryTableReference(),
partTableGroup.getPrimaryTableReference(),
collectionTableGroup.resolveTableReference( foreignKey.getKeyTable() ),
sqlExpressionResolver,
creationContext
)
)
);
} );
return join;
}
@ -341,13 +346,38 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
}
else if ( StringHelper.isNotEmpty( bootCollectionDescriptor.getMappedByProperty() ) ) {
final ModelPart mappedByPart = resolveNamedTargetPart( bootCollectionDescriptor.getMappedByProperty(), getAssociatedEntityMappingType(), collectionDescriptor );
if ( mappedByPart instanceof Association ) {
final Association toOne = (Association) mappedByPart;
if ( toOne.getForeignKeyDescriptor() == null ) {
// key is not yet ready, we need to wait
return false;
}
foreignKey = toOne.getForeignKeyDescriptor();
if ( mappedByPart instanceof ToOneAttributeMapping ) {
////////////////////////////////////////////////
// E.g.
//
// @Entity
// class Book {
// ...
// @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 {
final PluralAttributeMapping manyToManyInverse = (PluralAttributeMapping) mappedByPart;
@ -389,6 +419,58 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
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(
String targetPartName,
EntityMappingType entityMappingType,

View File

@ -46,8 +46,6 @@ import org.hibernate.usertype.UserVersionType;
* @author Gavin King
* @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>
extends AbstractType
implements ConvertedBasicType<J>, ProcedureParameterNamedBinder<J>, ProcedureParameterExtractionAware<J> {

View File

@ -17,7 +17,6 @@ import org.hibernate.envers.NotAudited;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@ -100,7 +99,6 @@ public class ListHashcodeChangeTest extends BaseEnversJPAFunctionalTestCase {
}
@Test
@FailureExpected( jiraKey = "HHH-15393", message = "Work for HHH-15393 (write-paths) causes a failure" )
// tests that Author has 3 books.
public void testAuthorState() {
EntityManager entityManager = getEntityManager();
@ -258,7 +256,8 @@ public class ListHashcodeChangeTest extends BaseEnversJPAFunctionalTestCase {
@ManyToOne(fetch = FetchType.LAZY)
@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
private Author author;

View File

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