HHH-13068 - "order_inserts = true" causes FK Violation when inserting Self Referential Entity with Single_Table Inherited Entities
This commit is contained in:
parent
5e30e509b3
commit
e8b88f5350
|
@ -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<AbstractEntityInsertAction> batch = actionBatches.get( rootIdentifier );
|
||||
insertions.addAll( batch );
|
||||
if ( sorted ) {
|
||||
insertions.clear();
|
||||
|
||||
for ( BatchIdentifier rootIdentifier : latestBatches ) {
|
||||
List<AbstractEntityInsertAction> 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 );
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue