diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java index 253cbdce79..909a7efc8e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java @@ -1111,7 +1111,8 @@ public class ActionQueue { boolean hasParent(BatchIdentifier batchIdentifier) { return ( parent == batchIdentifier - || ( parentEntityNames.contains( batchIdentifier.getEntityName() ) ) + || parentEntityNames.contains( batchIdentifier.getEntityName() ) + || ( parentEntityNames.contains( batchIdentifier.getRootEntityName() ) && !this.getEntityName().equals( batchIdentifier.getRootEntityName() ) ) || parent != null && parent.hasParent( batchIdentifier, new ArrayList<>() ) ); } @@ -1168,7 +1169,6 @@ public class ActionQueue { addParentChildEntityNames( action, batchIdentifier ); addToBatch( batchIdentifier, action ); } - insertions.clear(); // Examine each entry in the batch list, and build the dependency graph. for ( int i = 0; i < latestBatches.size(); i++ ) { @@ -1217,7 +1217,12 @@ public class ActionQueue { for ( int j = i + 1; j < latestBatches.size(); j++ ) { BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); - if ( batchIdentifier.hasParent( nextBatchIdentifier ) && !nextBatchIdentifier.hasParent( batchIdentifier ) ) { + if ( batchIdentifier.hasParent( nextBatchIdentifier ) ) { + if( nextBatchIdentifier.hasParent( batchIdentifier ) ) { + //cycle detected, no need to continue + break sort; + } + latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); @@ -1235,9 +1240,13 @@ public class ActionQueue { } // Now, rebuild the insertions list. There is a batch for each entry in the name list. - for ( BatchIdentifier rootIdentifier : latestBatches ) { - List batch = actionBatches.get( rootIdentifier ); - insertions.addAll( batch ); + if ( sorted ) { + insertions.clear(); + + for ( BatchIdentifier rootIdentifier : latestBatches ) { + List batch = actionBatches.get( rootIdentifier ); + insertions.addAll( batch ); + } } } @@ -1252,17 +1261,27 @@ public class ActionQueue { ClassMetadata classMetadata = action.getPersister().getClassMetadata(); if ( classMetadata != null ) { Type[] propertyTypes = classMetadata.getPropertyTypes(); + Type identifierType = classMetadata.getIdentifierType(); for ( int i = 0; i < propertyValues.length; i++ ) { Object value = propertyValues[i]; Type type = propertyTypes[i]; addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value ); } + + if ( identifierType.isComponentType() ) { + CompositeType compositeType = (CompositeType) identifierType; + Type[] compositeIdentifierTypes = compositeType.getSubtypes(); + + for ( Type type : compositeIdentifierTypes ) { + addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, null ); + } + } } } private void addParentChildEntityNameByPropertyAndValue(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier, Type type, Object value) { - if ( type.isEntityType() && value != null ) { + if ( type.isEntityType() ) { final EntityType entityType = (EntityType) type; final String entityName = entityType.getName(); final String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); @@ -1276,17 +1295,26 @@ public class ActionQueue { } } else { - batchIdentifier.getParentEntityNames().add( entityName ); + if ( !batchIdentifier.getEntityName().equals( entityName ) ) { + batchIdentifier.getParentEntityNames().add( entityName ); + } + if ( value != null ) { + String valueClass = value.getClass().getName(); + if ( !valueClass.equals( entityName ) ) { + batchIdentifier.getParentEntityNames().add( valueClass ); + } + } if ( !rootEntityName.equals( entityName ) ) { batchIdentifier.getParentEntityNames().add( rootEntityName ); } } } - else if ( type.isCollectionType() && value != null ) { + else if ( type.isCollectionType() ) { CollectionType collectionType = (CollectionType) type; final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() ) .getSessionFactory(); - if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { + if ( collectionType.getElementType( sessionFactory ).isEntityType() && + !sessionFactory.getMetamodel().collectionPersister( collectionType.getRole() ).isManyToMany() ) { String entityName = collectionType.getAssociatedEntityName( sessionFactory ); String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); batchIdentifier.getChildEntityNames().add( entityName ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingSelfReferenceSingleTableInheritance.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingSelfReferenceSingleTableInheritance.java new file mode 100644 index 0000000000..474a098447 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingSelfReferenceSingleTableInheritance.java @@ -0,0 +1,452 @@ +/* + * 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 . + */ +package org.hibernate.test.insertordering; + +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.OneToOne; + +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.Environment; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Harikant Verma + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-13068") +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class InsertOrderingSelfReferenceSingleTableInheritance + extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + ContentNode.class, + EntityTreeNode.class, + NodeLink.class, + NodeLongValue.class, + NodeStringValue.class, + ReferNode.class, + TreeNodeValue.class, + }; + } + + @Override + protected void addMappings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + } + + @Test + public void test1() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue longVal = new NodeLongValue(); + longVal.setLongValue( 123L ); + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( stringVal, null, null ); + ContentNode cn2 = new ContentNode( longVal, cn1, null ); + + NodeLink nl = new NodeLink( cn2 ); + + ReferNode rn1 = new ReferNode( etn, null, nl ); + + entityManager.persist( rn1 ); + } ); + } + + @Test + public void test2() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue aparam = new NodeLongValue(); + aparam.setLongValue( 123L ); + + ContentNode xa = new ContentNode( aparam, null, null ); + ContentNode xb = new ContentNode( aparam, null, null ); + ContentNode xc = new ContentNode( aparam, xb, null ); + + NodeLink nl = new NodeLink( xc ); + + ReferNode ya = new ReferNode( xa, null, nl ); + + entityManager.persist( ya ); + } ); + } + + @Test + public void test3() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue longVal = new NodeLongValue(); + longVal.setLongValue( 123L ); + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( stringVal, null, null ); + ContentNode cn2 = new ContentNode( longVal, cn1, null ); + + ReferNode rn1 = new ReferNode( etn, cn2, null ); + + entityManager.persist( rn1 ); + } ); + } + + @Test + public void test4() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue longVal = new NodeLongValue(); + longVal.setLongValue( 123L ); + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + ContentNode cn1 = new ContentNode( stringVal, null, null ); + ContentNode cn2 = new ContentNode( longVal, cn1, null ); + + ContentNode cn3 = new ContentNode( null, cn1, cn2 ); + + entityManager.persist( cn3 ); + } ); + } + + @Test + public void test5() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue longVal = new NodeLongValue(); + longVal.setLongValue( 123L ); + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( stringVal, null, null ); + ContentNode cn2 = new ContentNode( longVal, cn1, null ); + + ContentNode cn3 = new ContentNode( null, etn, cn2 ); + + entityManager.persist( cn3 ); + } ); + } + + @Test + public void test6() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeLongValue longVal = new NodeLongValue(); + longVal.setLongValue( 123L ); + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( stringVal, null, null ); + ReferNode rn1 = new ReferNode( null, cn1, null ); + + ContentNode cn3 = new ContentNode( longVal, etn, rn1 ); + + entityManager.persist( cn3 ); + } ); + } + + @Test + public void test7() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( null, etn, null ); + ReferNode rn1 = new ReferNode( null, cn1, null ); + + entityManager.persist( rn1 ); + } ); + } + + @Test + public void test8() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ReferNode rn1 = new ReferNode( null, etn, null ); + ContentNode cn3 = new ContentNode( null, rn1, null ); + + entityManager.persist( cn3 ); + } ); + } + + @Test + public void test9() { + doInJPA( this::entityManagerFactory, entityManager -> { + NodeStringValue stringVal = new NodeStringValue(); + stringVal.setStringValue( "Node 123" ); + + EntityTreeNode etn = new EntityTreeNode( null, null ); + ContentNode cn1 = new ContentNode( null, etn, null ); + ReferNode rn1 = new ReferNode( null, cn1, null ); + + ContentNode cn3 = new ContentNode( null, rn1, null ); + + entityManager.persist( cn3 ); + } ); + } + + @Entity(name = "EntityTreeNode") + @DynamicUpdate + @DynamicInsert + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class EntityTreeNode { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private EntityTreeNode leftNode; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + private EntityTreeNode rightNode; + + public EntityTreeNode(EntityTreeNode leftNode, EntityTreeNode rightNode) { + super(); + this.leftNode = leftNode; + this.rightNode = rightNode; + } + + public EntityTreeNode() { + super(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public EntityTreeNode getLeftNode() { + return leftNode; + } + + public void setLeftNode(EntityTreeNode leftNode) { + this.leftNode = leftNode; + } + + public EntityTreeNode getRightNode() { + return rightNode; + } + + public void setRightNode(EntityTreeNode rightNode) { + this.rightNode = rightNode; + } + } + + @Entity(name = "ContentNode") + @DynamicUpdate + @DynamicInsert + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class ContentNode extends EntityTreeNode { + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private TreeNodeValue nodeValue; + + public TreeNodeValue getNodeValue() { + return nodeValue; + } + + public void setNodeValue(TreeNodeValue nodeValue) { + this.nodeValue = nodeValue; + } + + public ContentNode(TreeNodeValue nodeValue, EntityTreeNode leftNode, EntityTreeNode rightNode) { + super( leftNode, rightNode ); + this.nodeValue = nodeValue; + } + + public ContentNode() { + super(); + } + + } + + @Entity(name = "NodeLink") + @DynamicUpdate + @DynamicInsert + @Inheritance(strategy = InheritanceType.JOINED) + public static class NodeLink { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private ContentNode toNode; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ContentNode getToNode() { + return this.toNode; + } + + public void setToNode(ContentNode toNode) { + this.toNode = toNode; + } + + public NodeLink(ContentNode toNode) { + super(); + setToNode( toNode ); + } + + public NodeLink() { + super(); + } + } + + @Entity(name = "NodeLongValue") + @DynamicUpdate + @DynamicInsert + public static class NodeLongValue extends TreeNodeValue { + + Long longValue; + + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + + public NodeLongValue(String dataType, Long longValue) { + super( dataType ); + this.longValue = longValue; + } + + public NodeLongValue() { + super(); + } + } + + @Entity(name = "NodeStringValue") + @DynamicUpdate + @DynamicInsert + public static class NodeStringValue extends TreeNodeValue { + + String stringValue; + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + public NodeStringValue(String dataType, String stringValue) { + super( dataType ); + this.stringValue = stringValue; + } + + public NodeStringValue() { + super(); + } + } + + @Entity(name = "ReferNode") + @DynamicUpdate + @DynamicInsert + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class ReferNode extends EntityTreeNode { + + @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) + private NodeLink nodeLink; + + public NodeLink getNodeLink() { + return nodeLink; + } + + public void setNodeLink(NodeLink nodeLink) { + this.nodeLink = nodeLink; + } + + public ReferNode(EntityTreeNode leftNode, EntityTreeNode rightNode, NodeLink nodeLink) { + super( leftNode, rightNode ); + this.nodeLink = nodeLink; + } + + public ReferNode() { + super(); + } + } + + @Entity(name = "TreeNodeValue") + @DynamicUpdate + @DynamicInsert + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + public static class TreeNodeValue { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + private String dataType; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDataType() { + return dataType; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public TreeNodeValue(String dataType) { + super(); + this.dataType = dataType; + } + + public TreeNodeValue() { + super(); + } + } + +}