Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 118 additions & 49 deletions hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
Expand Down Expand Up @@ -48,6 +47,7 @@
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.CollectionType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.OneToOneType;
Expand Down Expand Up @@ -1024,11 +1024,21 @@ private static class BatchIdentifier {

private Set<String> childEntityNames = new HashSet<String>( );

private BatchIdentifier parent;

BatchIdentifier(String entityName, String rootEntityName) {
this.entityName = entityName;
this.rootEntityName = rootEntityName;
}

public BatchIdentifier getParent() {
return parent;
}

public void setParent(BatchIdentifier parent) {
this.parent = parent;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
Expand Down Expand Up @@ -1070,6 +1080,22 @@ boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) {
boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) {
return childEntityNames.contains( batchIdentifier.getEntityName() );
}

/**
* Check if the this {@link BatchIdentifier} has a parent or grand parent
* matching the given {@link BatchIdentifier reference.
*
* @param batchIdentifier {@link BatchIdentifier} reference
*
* @return This {@link BatchIdentifier} has a parent matching the given {@link BatchIdentifier reference
*/
boolean hasParent(BatchIdentifier batchIdentifier) {
return (
parent == batchIdentifier ||
( parent != null && parent.hasParent( batchIdentifier ) ) ||
( parentEntityNames.contains( batchIdentifier.getEntityName() ) )
);
}
}

// the mapping of entity names to their latest batch numbers.
Expand Down Expand Up @@ -1114,42 +1140,71 @@ public void sort(List<AbstractEntityInsertAction> insertions) {
}
insertions.clear();

// Examine each entry in the batch list, sorting them based on parent/child associations.
// Examine each entry in the batch list, and build the dependency graph.
for ( int i = 0; i < latestBatches.size(); i++ ) {
BatchIdentifier batchIdentifier = latestBatches.get( i );

// Iterate previous batches and make sure that parent types are before children
// Since the outer loop looks at each batch entry individually, we need to verify that any
// prior batches in the list are not considered children (or have a parent) of the current
// batch. If so, we reordered them.
for ( int j = i - 1; j >= 0; j-- ) {
BatchIdentifier prevBatchIdentifier = latestBatches.get( j );
if ( prevBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) {
latestBatches.remove( batchIdentifier );
latestBatches.add( j, batchIdentifier );
prevBatchIdentifier.parent = batchIdentifier;
}
if ( batchIdentifier.hasAnyChildEntityNames( prevBatchIdentifier ) ) {
prevBatchIdentifier.parent = batchIdentifier;
}
}

// Iterate next batches and make sure that children types are after parents.
// Since the outer loop looks at each batch entry individually and the prior loop will reorder
// entries as well, we need to look and verify if the current batch is a child of the next
// batch or if the current batch is seen as a parent or child of the next batch.
for ( int j = i + 1; j < latestBatches.size(); j++ ) {
BatchIdentifier nextBatchIdentifier = latestBatches.get( j );

final boolean nextBatchHasChild = nextBatchIdentifier.hasAnyChildEntityNames( batchIdentifier );
final boolean batchHasChild = batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier );
final boolean batchHasParent = batchIdentifier.hasAnyParentEntityNames( nextBatchIdentifier );
if ( nextBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) {
nextBatchIdentifier.parent = batchIdentifier;
}
if ( batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier ) ) {
nextBatchIdentifier.parent = batchIdentifier;
}
}
}

boolean sorted = false;

long maxIterations = latestBatches.size() * 2;
long iterations = 0;

sort:
do {
// Examine each entry in the batch list, sorting them based on parent/child association
// as depicted by the dependency graph.
iterations++;

for ( int i = 0; i < latestBatches.size(); i++ ) {
BatchIdentifier batchIdentifier = latestBatches.get( i );

// Iterate next batches and make sure that children types are after parents.
// Since the outer loop looks at each batch entry individually and the prior loop will reorder
// entries as well, we need to look and verify if the current batch is a child of the next
// batch or if the current batch is seen as a parent or child of the next batch.
for ( int j = i + 1; j < latestBatches.size(); j++ ) {
BatchIdentifier nextBatchIdentifier = latestBatches.get( j );

if ( batchIdentifier.hasParent( nextBatchIdentifier ) && !nextBatchIdentifier.hasParent( batchIdentifier ) ) {
latestBatches.remove( batchIdentifier );
latestBatches.add( j, batchIdentifier );

// Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany
if ( ( nextBatchHasChild && !batchHasChild ) || batchHasParent ) {
latestBatches.remove( batchIdentifier );
latestBatches.add( j, batchIdentifier );
continue sort;
}
}
}
sorted = true;
}
while ( !sorted && iterations <= maxIterations);

if ( iterations > maxIterations ) {
LOG.warn( "The batch containing " + latestBatches.size() + " statements could not be sorted after " + maxIterations + " iterations. " +
"This might indicate a circular entity relationship." );
}

// now rebuild the insertions list. There is a batch for each entry in the name list.
// Now, rebuild the insertions list. There is a batch for each entry in the name list.
for ( BatchIdentifier rootIdentifier : latestBatches ) {
List<AbstractEntityInsertAction> batch = actionBatches.get( rootIdentifier );
insertions.addAll( batch );
Expand All @@ -1172,39 +1227,53 @@ private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchI
for ( int i = 0; i < propertyValues.length; i++ ) {
Object value = propertyValues[i];
Type type = propertyTypes[i];
if ( type.isEntityType() && value != null ) {
EntityType entityType = (EntityType) type;
String entityName = entityType.getName();
String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName();

if ( entityType.isOneToOne() &&
OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
batchIdentifier.getChildEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getChildEntityNames().add( rootEntityName );
}
}
else {
batchIdentifier.getParentEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getParentEntityNames().add( rootEntityName );
}
}
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value );
}
}
}

private void addParentChildEntityNameByPropertyAndValue(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier, Type type, Object value) {
if ( type.isEntityType() && value != null ) {
final EntityType entityType = (EntityType) type;
final String entityName = entityType.getName();
final String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName();

if ( entityType.isOneToOne() && OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
batchIdentifier.getChildEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getChildEntityNames().add( rootEntityName );
}
else if ( type.isCollectionType() && value != null ) {
CollectionType collectionType = (CollectionType) type;
final SessionFactoryImplementor sessionFactory = action.getSession().getFactory();
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName();
batchIdentifier.getChildEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getChildEntityNames().add( rootEntityName );
}
}
}
else {
batchIdentifier.getParentEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getParentEntityNames().add( rootEntityName );
}
}
}
else if ( type.isCollectionType() && value != null ) {
CollectionType collectionType = (CollectionType) type;
final SessionFactoryImplementor sessionFactory = action.getSession().getFactory();
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName();
batchIdentifier.getChildEntityNames().add( entityName );
if ( !rootEntityName.equals( entityName ) ) {
batchIdentifier.getChildEntityNames().add( rootEntityName );
}
}
}
else if ( type.isComponentType() && value != null ) {
// Support recursive checks of composite type properties for associations and collections.
CompositeType compositeType = (CompositeType) type;
final SessionImplementor session = action.getSession();
Object[] componentValues = compositeType.getPropertyValues( value, session );
for ( int j = 0; j < componentValues.length; ++j ) {
Type componentValueType = compositeType.getSubtypes()[j];
Object componentValue = componentValues[j];
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, componentValueType, componentValue );
}
}
}

private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAction action) {
Expand Down
Loading