From 4fb35c10eb763659a4a42478b8be758d0c936427 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 19 Apr 2017 17:00:42 +0300 Subject: [PATCH 01/15] HHH-11634 - ActionQueue#InsertActionSorter fails to generate right order Prove that the issue is caused by an improer bidirectional association side synchronization (cherry picked from commit 84d61690c580518918fbeb601acc950e2906b261) --- .../InsertOrderingDuplicateTest.java | 339 ++++++++++++++++++ 1 file changed, 339 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java new file mode 100644 index 000000000000..88569dc548fd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java @@ -0,0 +1,339 @@ +/* + * 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.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.SequenceGenerator; + +import org.hibernate.cfg.Environment; + +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.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-11634") +public class InsertOrderingDuplicateTest + extends BaseNonConfigCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Product.class, + SaleDocument.class, + SaleDocumentItem.class, + SaleDocumentSummary.class + }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + } + + @Test + public void testBatching() throws SQLException { + + doInHibernate( this::sessionFactory, session -> { + SaleDocumentSummary saleDocumentsummary = new SaleDocumentSummary(); + session.persist(saleDocumentsummary); + + SaleDocumentItem saleDocumentItem = new SaleDocumentItem(); + saleDocumentsummary.addItem( saleDocumentItem ); + session.persist( saleDocumentItem ); + + Product product = new Product(); + session.persist(product); + saleDocumentItem.setProduct(product); + + SaleDocument saleDocument = new SaleDocument(); + session.persist(saleDocument); + saleDocument.addItem( saleDocumentItem ); + + SaleDocument correction = new SaleDocument(); + session.persist(correction); + + saleDocument.setCorerctionSubject(correction); + } ); + } + + + @Entity(name = "Product") + public static class Product { + @Id + @GeneratedValue + private Long id; + + @Column(unique = true) + private String name; + + private String description; + + private Integer quantity; + + private BigDecimal price; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + } + + @Entity(name = "SaleDocument") + public static class SaleDocument { + @Id + @GeneratedValue + private Long id; + + private String number; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "saleDocument") + private Set items = new HashSet(); + + @JoinColumn(name = "ID_SALE_DOCUMENT_CORRECTION", nullable = true) + @ManyToOne(fetch = FetchType.LAZY) + private SaleDocument corerctionSubject; + + private BigDecimal totalPrice; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Set getItems() { + return items; + } + + public void setItems(Set items) { + this.items = items; + } + + public BigDecimal getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(BigDecimal totalPrice) { + this.totalPrice = totalPrice; + } + + public void addItem(SaleDocumentItem sdi) { + this.getItems().add(sdi); + sdi.setSaleDocument( this ); + } + + public SaleDocument getCorerctionSubject() { + return corerctionSubject; + } + + public void setCorerctionSubject(SaleDocument corerctionSubject) { + this.corerctionSubject = corerctionSubject; + } + + } + + @Entity(name = "SaleDocumentItem") + public class SaleDocumentItem { + @Id + @GeneratedValue + private Long id; + + private Integer lp; + + @ManyToOne(optional = true) + private Product product; + + @JoinColumn(name = "ID_SALE_DOCUMENT", nullable = true) + @ManyToOne(fetch = FetchType.LAZY) + private SaleDocument saleDocument; + + @JoinColumn(name = "ID_SALE_DOCUMENT_SUMAMRY", nullable = true) + @ManyToOne(fetch = FetchType.LAZY) + private SaleDocumentSummary summary; + + private Integer quantity; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Integer getLp() { + return lp; + } + + public void setLp(Integer lp) { + this.lp = lp; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public SaleDocument getSaleDocument() { + return saleDocument; + } + + public void setSaleDocument(SaleDocument saleDocument) { + this.saleDocument = saleDocument; + } + + public SaleDocumentSummary getSummary() { + return summary; + } + + public void setSummary(SaleDocumentSummary summary) { + this.summary = summary; + } + + } + + @Entity(name = "SaleDocumentSummary") + public class SaleDocumentSummary { + + @Id + @GeneratedValue + private Long id; + private String number; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "summary") + private Set items = new HashSet(); + + private BigDecimal totalPrice; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Set getItems() { + return items; + } + + public void setItems(Set items) { + this.items = items; + } + + public BigDecimal getTotalPrice() { + return totalPrice; + } + + public void setTotalPrice(BigDecimal totalPrice) { + this.totalPrice = totalPrice; + } + + public void addItem(SaleDocumentItem sdi) { + this.getItems().add(sdi); + sdi.setSummary( this ); + } + } +} From 06b105e1002bddf0efd8d66290d6661b1b4a20ba Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 20:52:04 -0800 Subject: [PATCH 02/15] HHH-11634 : Fix test case to work in 5.1 branch --- .../InsertOrderingDuplicateTest.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java index 88569dc548fd..cc37a7f8ff22 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java @@ -7,38 +7,26 @@ package org.hibernate.test.insertordering; import java.math.BigDecimal; -import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; -import javax.persistence.SequenceGenerator; +import org.hibernate.Session; import org.hibernate.cfg.Environment; 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.junit.Assert.assertEquals; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - /** * @author Vlad Mihalcea */ @@ -70,7 +58,9 @@ public void releaseResources() { @Test public void testBatching() throws SQLException { - doInHibernate( this::sessionFactory, session -> { + Session session = openSession(); + session.getTransaction().begin(); + { SaleDocumentSummary saleDocumentsummary = new SaleDocumentSummary(); session.persist(saleDocumentsummary); @@ -90,7 +80,9 @@ public void testBatching() throws SQLException { session.persist(correction); saleDocument.setCorerctionSubject(correction); - } ); + } + session.getTransaction().commit(); + session.close(); } From f1eb8672d7913ca3f1e96cc8985bda4a4466068b Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 20:55:47 -0800 Subject: [PATCH 03/15] HHH-11634 : Change test to reproduce issue --- .../test/insertordering/InsertOrderingDuplicateTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java index cc37a7f8ff22..9f6e8580c943 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingDuplicateTest.java @@ -61,12 +61,13 @@ public void testBatching() throws SQLException { Session session = openSession(); session.getTransaction().begin(); { + SaleDocumentItem saleDocumentItem = new SaleDocumentItem(); + session.persist( saleDocumentItem ); + SaleDocumentSummary saleDocumentsummary = new SaleDocumentSummary(); - session.persist(saleDocumentsummary); + session.persist(saleDocumentsummary ); - SaleDocumentItem saleDocumentItem = new SaleDocumentItem(); saleDocumentsummary.addItem( saleDocumentItem ); - session.persist( saleDocumentItem ); Product product = new Product(); session.persist(product); From 98a98eefe5f6dcfd6b4f43d2afa0e815c6cc55b4 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 19 Apr 2017 21:02:02 +0300 Subject: [PATCH 04/15] HHH-11634 - ActionQueue#InsertActionSorter fails to generate right order Use the same object for both the remove and the add operations (cherry picked from commit 020414e1aa79058c8a3196898a815079c38a8315) --- .../src/main/java/org/hibernate/engine/spi/ActionQueue.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4b98dfce4808..45c97d3d659e 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 @@ -1105,7 +1105,7 @@ public void sort(List insertions) { for ( int j = i - 1; j >= 0; j-- ) { BatchIdentifier prevBatchIdentifier = latestBatches.get( j ); if(prevBatchIdentifier.getParentEntityNames().contains( entityName )) { - latestBatches.remove( i ); + latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } } @@ -1116,7 +1116,7 @@ public void sort(List insertions) { //Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany if(nextBatchIdentifier.getChildEntityNames().contains( entityName ) && !batchIdentifier.getChildEntityNames().contains( nextBatchIdentifier.getEntityName() )) { - latestBatches.remove( i ); + latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } } From 1a63755630952300cc7a0f889774c9cd58e89936 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 9 Jun 2017 10:40:06 -0400 Subject: [PATCH 05/15] HHH-11768 - Test case (cherry picked from commit e7e6cc53ab28f4694f1963f6ae4b2641cb3fe8ec) --- .../InsertOrderingWithCascadeOnPersist.java | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java new file mode 100644 index 000000000000..196964ca7663 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java @@ -0,0 +1,130 @@ +/* + * 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.HashSet; +import java.util.Set; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.TableGenerator; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * @author Chris Cranford + */ +public class InsertOrderingWithCascadeOnPersist extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { MarketBid.class, MarketBidGroup.class, MarketResult.class }; + } + + @Override + protected void configure(Configuration configuration) { + super.configure( configuration ); + configuration.setProperty( AvailableSettings.ORDER_INSERTS, Boolean.TRUE.toString() ); + configuration.setProperty( AvailableSettings.ORDER_UPDATES, Boolean.TRUE.toString() ); + } + + @Test + @TestForIssue(jiraKey = "HHH-11768") + public void testInsertOrderingAvoidingForeignKeyConstraintViolation() { + Long bidId = doInHibernate( this::sessionFactory, session -> { + // create MarketBid and Group + final MarketBidGroup group = new MarketBidGroup(); + final MarketBid bid = new MarketBid(); + bid.setGroup( group ); + session.persist( bid ); + return bid.getId(); + } ); + + // This block resulted in a Foreign Key ConstraintViolation because the inserts were ordered incorrectly. + doInHibernate( this::sessionFactory, session -> { + // Add marketResult to existing Bid + final MarketBid bid = session.load( MarketBid.class, bidId ); + final MarketResult result = new MarketResult(); + result.setMarketBid( bid ); + session.persist( result ); + // create new MarketBid, Group and Result + final MarketBidGroup newGroup = new MarketBidGroup(); + final MarketBid newBid = new MarketBid(); + newBid.setGroup( newGroup ); + final MarketResult newResult = new MarketResult(); + newResult.setMarketBid( newBid ); + session.persist( newBid ); + session.persist( newResult ); + } ); + } + + @Entity(name = "MarketBid") + @Access(AccessType.FIELD) + public static class MarketBid { + @Id + @GeneratedValue(strategy = GenerationType.TABLE, generator = "MarketBid") + @TableGenerator(name = "MarketBid", pkColumnValue = "MarketBid", allocationSize = 10000) + private Long id; + + @ManyToOne(optional = false, cascade = CascadeType.PERSIST) + private MarketBidGroup group; + + public Long getId() { + return id; + } + + public void setGroup(MarketBidGroup group) { + this.group = group; + } + } + + @Entity(name = "MarketBidGroup") + @Access(AccessType.FIELD) + public static class MarketBidGroup { + @Id + @GeneratedValue(strategy = GenerationType.TABLE, generator = "MarketBidGroup") + @TableGenerator(name = "MarketBidGroup", pkColumnValue = "MarketBidGroup", allocationSize = 10000) + private Long id; + + @OneToMany(mappedBy = "group") + private final Set marketBids = new HashSet<>(); + + public void addMarketBid(MarketBid marketBid) { + this.marketBids.add(marketBid); + } + } + + @Entity(name = "MarketResult") + @Access(AccessType.FIELD) + public static class MarketResult { + @Id + @GeneratedValue(strategy = GenerationType.TABLE, generator = "MarketResult") + @TableGenerator(name = "MarketResult", pkColumnValue = "MarketResult", allocationSize = 10000) + private Long id; + + @ManyToOne(optional = false) + private MarketBid marketBid; + + public void setMarketBid(MarketBid marketBid) { + this.marketBid = marketBid; + } + + } +} From 7e4ddca5c61c25a3c38ce9b03e9c66b360176a9f Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 22:57:08 -0800 Subject: [PATCH 06/15] HHH-11768 : Fix test case to work in 5.1 --- .../InsertOrderingWithCascadeOnPersist.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java index 196964ca7663..60deaaefcfb1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithCascadeOnPersist.java @@ -20,6 +20,7 @@ import javax.persistence.OneToMany; import javax.persistence.TableGenerator; +import org.hibernate.Session; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.junit.Test; @@ -27,8 +28,6 @@ import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; - /** * @author Chris Cranford */ @@ -48,17 +47,24 @@ protected void configure(Configuration configuration) { @Test @TestForIssue(jiraKey = "HHH-11768") public void testInsertOrderingAvoidingForeignKeyConstraintViolation() { - Long bidId = doInHibernate( this::sessionFactory, session -> { + Long bidId; + Session session = openSession(); + session.getTransaction().begin(); + { // create MarketBid and Group final MarketBidGroup group = new MarketBidGroup(); final MarketBid bid = new MarketBid(); bid.setGroup( group ); session.persist( bid ); - return bid.getId(); - } ); + bidId = bid.getId(); + } + session.getTransaction().commit(); + session.close(); // This block resulted in a Foreign Key ConstraintViolation because the inserts were ordered incorrectly. - doInHibernate( this::sessionFactory, session -> { + session = openSession(); + session.getTransaction().begin(); + { // Add marketResult to existing Bid final MarketBid bid = session.load( MarketBid.class, bidId ); final MarketResult result = new MarketResult(); @@ -72,7 +78,9 @@ public void testInsertOrderingAvoidingForeignKeyConstraintViolation() { newResult.setMarketBid( newBid ); session.persist( newBid ); session.persist( newResult ); - } ); + } + session.getTransaction().commit(); + session.close(); } @Entity(name = "MarketBid") From 519e76728817ecd80f803055bbb5759525133d88 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 9 Jun 2017 10:40:43 -0400 Subject: [PATCH 07/15] HHH-11768 - Fix FK ConstraintViolationException with ordered inserts enabled and cascade persist. (cherry picked from commit 234849d33bae8fe1588d1c74f4eb76027c169bb2) --- .../java/org/hibernate/engine/spi/ActionQueue.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 45c97d3d659e..8373b3e25524 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 @@ -1104,7 +1104,7 @@ public void sort(List insertions) { //Make sure that child entries are not before parents for ( int j = i - 1; j >= 0; j-- ) { BatchIdentifier prevBatchIdentifier = latestBatches.get( j ); - if(prevBatchIdentifier.getParentEntityNames().contains( entityName )) { + if ( prevBatchIdentifier.getParentEntityNames().contains( entityName ) ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } @@ -1114,8 +1114,12 @@ public void sort(List insertions) { for ( int j = i + 1; j < latestBatches.size(); j++ ) { BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); //Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany - if(nextBatchIdentifier.getChildEntityNames().contains( entityName ) && - !batchIdentifier.getChildEntityNames().contains( nextBatchIdentifier.getEntityName() )) { + if ( nextBatchIdentifier.getChildEntityNames().contains( entityName ) && + !batchIdentifier.getChildEntityNames().contains( nextBatchIdentifier.getEntityName() ) ) { + latestBatches.remove( batchIdentifier ); + latestBatches.add( j, batchIdentifier ); + } + else if ( batchIdentifier.getParentEntityNames().contains( nextBatchIdentifier.getEntityName() ) ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } From 980088e985b0b35518b784d8cee0b2e23a6b7f7c Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 9 Jun 2017 11:41:09 -0400 Subject: [PATCH 08/15] HHH-11714 - Added test case. (cherry picked from commit f4cd82a2652a019c0bf8807c54066b8dda4ee739) --- .../InsertOrderingWithSecondaryTable.java | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java new file mode 100644 index 000000000000..bad6246af71a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java @@ -0,0 +1,199 @@ +/* + * 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.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-11714") +public class InsertOrderingWithSecondaryTable extends BaseEntityManagerFunctionalTestCase { + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put( AvailableSettings.ORDER_INSERTS, Boolean.TRUE ); + options.put( AvailableSettings.ORDER_UPDATES, Boolean.TRUE ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + ShapeEntity.class, + ShapePolygonEntity.class, + ShapeCircleEntity.class, + GeographicArea.class, + TopLevelEntity.class + }; + } + + @Test + public void testInheritanceWithSecondaryTable() { + doInJPA( this::entityManagerFactory, entityManager -> { + final TopLevelEntity top = new TopLevelEntity(); + + final GeographicArea area1 = new GeographicArea(); + area1.setTopLevel( top ); + area1.setShape( new ShapePolygonEntity() ); + top.getGeographicAreas().add( area1 ); + + final ShapeCircleEntity circle = new ShapeCircleEntity(); + circle.setCentre( "CENTRE" ); + + final GeographicArea area2 = new GeographicArea(); + area2.setTopLevel( top ); + area2.setShape( circle ); + top.getGeographicAreas().add( area2 ); + + entityManager.persist( top ); + entityManager.flush(); + } ); + } + + @Entity + @Table(name = "SHAPE") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "SHAPE_TYPE", discriminatorType = DiscriminatorType.STRING) + public static class ShapeEntity { + @Id + @SequenceGenerator(name = "SHAPE_ID_GENERATOR", sequenceName = "SHAPE_SEQ", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SHAPE_ID_GENERATOR") + @Column(name = "SHAPE_ID", insertable = false, updatable = false) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @Entity + @DiscriminatorValue("POLYGON") + @Table(name = "POLYGON") + public static class ShapePolygonEntity extends ShapeEntity { + + } + + @Entity + @DiscriminatorValue("CIRCLE") + @SecondaryTable(name = "SHAPE_CIRCLE", pkJoinColumns = @PrimaryKeyJoinColumn(name = "SHAPE_ID")) + @Table(name = "CIRCLE") + public static class ShapeCircleEntity extends ShapeEntity { + @Column(table = "SHAPE_CIRCLE") + private String centre; + + public String getCentre() { + return centre; + } + + public void setCentre(String centre) { + this.centre = centre; + } + } + + @Entity + @Table(name = "GEOGRAPHIC_AREA") + public static class GeographicArea { + @Id + @GeneratedValue + private Integer id; + + /// The reference to the top level class. + @ManyToOne + @JoinColumn(name = "TOP_LEVEL_ID") + private TopLevelEntity topLevel; + + // The reference to the shape. + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "SHAPE_ID") + private ShapeEntity shape; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public TopLevelEntity getTopLevel() { + return topLevel; + } + + public void setTopLevel(TopLevelEntity topLevel) { + this.topLevel = topLevel; + } + + public ShapeEntity getShape() { + return shape; + } + + public void setShape(ShapeEntity shape) { + this.shape = shape; + } + } + + @Entity + @Table(name = "TOP_LEVEL") + public static class TopLevelEntity { + @Id + @GeneratedValue + private Integer id; + + @OneToMany(mappedBy = "topLevel", cascade = {CascadeType.ALL}, orphanRemoval = true) + private List geographicAreas = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getGeographicAreas() { + return geographicAreas; + } + + public void setGeographicAreas(List geographicAreas) { + this.geographicAreas = geographicAreas; + } + } +} \ No newline at end of file From 0a9aa13b2e7d5f571180b9ad10b24166cbac505c Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 23:06:27 -0800 Subject: [PATCH 09/15] HHH-11714 : Correct test case to work in 5.1 branch --- .../InsertOrderingWithSecondaryTable.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename {hibernate-core/src/test/java/org/hibernate => hibernate-entitymanager/src/test/java/org/hibernate/jpa}/test/insertordering/InsertOrderingWithSecondaryTable.java (94%) diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/insertordering/InsertOrderingWithSecondaryTable.java similarity index 94% rename from hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java rename to hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/insertordering/InsertOrderingWithSecondaryTable.java index bad6246af71a..2f4fdadfc901 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithSecondaryTable.java +++ b/hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/insertordering/InsertOrderingWithSecondaryTable.java @@ -4,7 +4,7 @@ * 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; +package org.hibernate.jpa.test.insertordering; import java.util.ArrayList; import java.util.List; @@ -16,6 +16,7 @@ import javax.persistence.DiscriminatorType; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; +import javax.persistence.EntityManager; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -36,8 +37,6 @@ import org.hibernate.testing.TestForIssue; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; - /** * @author Chris Cranford */ @@ -63,7 +62,9 @@ protected Class[] getAnnotatedClasses() { @Test public void testInheritanceWithSecondaryTable() { - doInJPA( this::entityManagerFactory, entityManager -> { + EntityManager entityManager = getOrCreateEntityManager(); + entityManager.getTransaction().begin(); + { final TopLevelEntity top = new TopLevelEntity(); final GeographicArea area1 = new GeographicArea(); @@ -81,7 +82,9 @@ public void testInheritanceWithSecondaryTable() { entityManager.persist( top ); entityManager.flush(); - } ); + } + entityManager.getTransaction().commit(); + entityManager.close(); } @Entity From 80f1a6e4161696e2e4934a13f16ca653b78c49db Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 9 Jun 2017 11:41:34 -0400 Subject: [PATCH 10/15] HHH-11714 - Fix ordered inserts with secondary tables and inheritance. (cherry picked from commit c6135f2db3acb51dbbda65543647c6a7e8a566f7) --- .../org/hibernate/engine/spi/ActionQueue.java | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) 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 8373b3e25524..c1056597c751 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 @@ -1018,14 +1018,15 @@ private static class InsertActionSorter implements ExecutableList.Sorter parentEntityNames = new HashSet( ); private Set childEntityNames = new HashSet( ); - public BatchIdentifier( - String entityName) { + public BatchIdentifier(String entityName, String rootEntityName) { this.entityName = entityName; + this.rootEntityName = rootEntityName; } @Override @@ -1049,6 +1050,10 @@ public String getEntityName() { return entityName; } + public String getRootEntityName() { + return rootEntityName; + } + public Set getParentEntityNames() { return parentEntityNames; } @@ -1056,6 +1061,24 @@ public Set getParentEntityNames() { public Set getChildEntityNames() { return childEntityNames; } + + public boolean hasAnyParentEntityNames(String... entityNames) { + for ( String entityName : entityNames ) { + if ( parentEntityNames.contains( entityName ) ) { + return true; + } + } + return false; + } + + public boolean hasAnyChildEntityNames(String... entityNames) { + for ( String entityName : entityNames ) { + if ( childEntityNames.contains( entityName ) ) { + return true; + } + } + return false; + } } // the mapping of entity names to their latest batch numbers. @@ -1079,7 +1102,10 @@ public void sort(List insertions) { this.actionBatches = new HashMap>(); for ( AbstractEntityInsertAction action : insertions ) { - BatchIdentifier batchIdentifier = new BatchIdentifier( action.getEntityName() ); + BatchIdentifier batchIdentifier = new BatchIdentifier( + action.getEntityName(), + action.getSession().getFactory().getMetamodel().entityPersister( action.getEntityName() ).getRootEntityName() + ); // the entity associated with the current action. Object currentEntity = action.getInstance(); @@ -1100,11 +1126,12 @@ public void sort(List insertions) { for ( int i = 0; i < latestBatches.size(); i++ ) { BatchIdentifier batchIdentifier = latestBatches.get( i ); String entityName = batchIdentifier.getEntityName(); + String rootEntityName = batchIdentifier.getRootEntityName(); //Make sure that child entries are not before parents for ( int j = i - 1; j >= 0; j-- ) { BatchIdentifier prevBatchIdentifier = latestBatches.get( j ); - if ( prevBatchIdentifier.getParentEntityNames().contains( entityName ) ) { + if ( prevBatchIdentifier.hasAnyParentEntityNames( entityName, rootEntityName ) ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } @@ -1113,13 +1140,17 @@ public void sort(List insertions) { //Make sure that parent entries are not after children for ( int j = i + 1; j < latestBatches.size(); j++ ) { BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); + + final boolean nextBatchHasChild = nextBatchIdentifier.hasAnyChildEntityNames( entityName, rootEntityName ); + + final boolean batchHasChild = batchIdentifier.hasAnyChildEntityNames( + nextBatchIdentifier.getEntityName(), nextBatchIdentifier.getRootEntityName() ); + + final boolean batchHasParent = batchIdentifier.hasAnyParentEntityNames( + nextBatchIdentifier.getEntityName(), nextBatchIdentifier.getRootEntityName() ); + //Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany - if ( nextBatchIdentifier.getChildEntityNames().contains( entityName ) && - !batchIdentifier.getChildEntityNames().contains( nextBatchIdentifier.getEntityName() ) ) { - latestBatches.remove( batchIdentifier ); - latestBatches.add( j, batchIdentifier ); - } - else if ( batchIdentifier.getParentEntityNames().contains( nextBatchIdentifier.getEntityName() ) ) { + if ( ( nextBatchHasChild && !batchHasChild ) || batchHasParent ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } @@ -1152,13 +1183,20 @@ private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchI if ( type.isEntityType() && value != null ) { EntityType entityType = (EntityType) type; String entityName = entityType.getName(); + String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( 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 ); + } } } else if ( type.isCollectionType() && value != null ) { @@ -1166,7 +1204,11 @@ else if ( type.isCollectionType() && value != null ) { final SessionFactoryImplementor sessionFactory = action.getSession().getFactory(); if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { String entityName = collectionType.getAssociatedEntityName( sessionFactory ); + String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); batchIdentifier.getChildEntityNames().add( entityName ); + if ( !rootEntityName.equals( entityName ) ) { + batchIdentifier.getChildEntityNames().add( rootEntityName ); + } } } } From c4fabf0dace98d3c5628663cfbbdea3e8054914e Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 23:18:40 -0800 Subject: [PATCH 11/15] HHH-11714 : Correct code to work in 5.1 branch --- .../src/main/java/org/hibernate/engine/spi/ActionQueue.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 c1056597c751..84f9a1b84bc3 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 @@ -1104,7 +1104,7 @@ public void sort(List insertions) { for ( AbstractEntityInsertAction action : insertions ) { BatchIdentifier batchIdentifier = new BatchIdentifier( action.getEntityName(), - action.getSession().getFactory().getMetamodel().entityPersister( action.getEntityName() ).getRootEntityName() + action.getSession().getFactory().getEntityPersister( action.getEntityName() ).getRootEntityName() ); // the entity associated with the current action. @@ -1183,7 +1183,7 @@ private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchI if ( type.isEntityType() && value != null ) { EntityType entityType = (EntityType) type; String entityName = entityType.getName(); - String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); + String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName(); if ( entityType.isOneToOne() && OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) { @@ -1204,7 +1204,7 @@ else if ( type.isCollectionType() && value != null ) { final SessionFactoryImplementor sessionFactory = action.getSession().getFactory(); if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { String entityName = collectionType.getAssociatedEntityName( sessionFactory ); - String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName(); + String rootEntityName = action.getSession().getFactory().getEntityPersister( entityName ).getRootEntityName(); batchIdentifier.getChildEntityNames().add( entityName ); if ( !rootEntityName.equals( entityName ) ) { batchIdentifier.getChildEntityNames().add( rootEntityName ); From 0ec5c8a47a50463fcb23440454a6552f152dfab7 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 12 Jun 2017 11:37:56 -0400 Subject: [PATCH 12/15] HHH-11768 HHH-11714 Apply code suggestions. (cherry picked from commit 699b50725d1cab1378cc7617bf92920bd7888b5a) --- .../org/hibernate/engine/spi/ActionQueue.java | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) 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 84f9a1b84bc3..4abef37eb911 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 @@ -1024,7 +1024,7 @@ private static class BatchIdentifier { private Set childEntityNames = new HashSet( ); - public BatchIdentifier(String entityName, String rootEntityName) { + BatchIdentifier(String entityName, String rootEntityName) { this.entityName = entityName; this.rootEntityName = rootEntityName; } @@ -1046,38 +1046,30 @@ public int hashCode() { return entityName.hashCode(); } - public String getEntityName() { + String getEntityName() { return entityName; } - public String getRootEntityName() { + String getRootEntityName() { return rootEntityName; } - public Set getParentEntityNames() { + Set getParentEntityNames() { return parentEntityNames; } - public Set getChildEntityNames() { + Set getChildEntityNames() { return childEntityNames; } - public boolean hasAnyParentEntityNames(String... entityNames) { - for ( String entityName : entityNames ) { - if ( parentEntityNames.contains( entityName ) ) { - return true; - } - } - return false; + boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) { + return parentEntityNames.contains( batchIdentifier.getEntityName() ) || + parentEntityNames.contains( batchIdentifier.getRootEntityName() ); } - public boolean hasAnyChildEntityNames(String... entityNames) { - for ( String entityName : entityNames ) { - if ( childEntityNames.contains( entityName ) ) { - return true; - } - } - return false; + boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) { + return childEntityNames.contains( batchIdentifier.getEntityName() ) || + parentEntityNames.contains( batchIdentifier.getRootEntityName() ); } } @@ -1123,33 +1115,34 @@ public void sort(List insertions) { } insertions.clear(); + // Examine each entry in the batch list, sorting them based on parent/child associations. for ( int i = 0; i < latestBatches.size(); i++ ) { BatchIdentifier batchIdentifier = latestBatches.get( i ); - String entityName = batchIdentifier.getEntityName(); - String rootEntityName = batchIdentifier.getRootEntityName(); - //Make sure that child entries are not before parents + // 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( entityName, rootEntityName ) ) { + if ( prevBatchIdentifier.hasAnyParentEntityNames( batchIdentifier ) ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); } } - //Make sure that parent entries are not after children + // 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( entityName, rootEntityName ); - - final boolean batchHasChild = batchIdentifier.hasAnyChildEntityNames( - nextBatchIdentifier.getEntityName(), nextBatchIdentifier.getRootEntityName() ); - - final boolean batchHasParent = batchIdentifier.hasAnyParentEntityNames( - nextBatchIdentifier.getEntityName(), nextBatchIdentifier.getRootEntityName() ); + final boolean nextBatchHasChild = nextBatchIdentifier.hasAnyChildEntityNames( batchIdentifier ); + final boolean batchHasChild = batchIdentifier.hasAnyChildEntityNames( nextBatchIdentifier ); + final boolean batchHasParent = batchIdentifier.hasAnyParentEntityNames( nextBatchIdentifier ); - //Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany + // Take care of unidirectional @OneToOne associations but exclude bidirectional @ManyToMany if ( ( nextBatchHasChild && !batchHasChild ) || batchHasParent ) { latestBatches.remove( batchIdentifier ); latestBatches.add( j, batchIdentifier ); From 76aa1fbe39949736c10bff4f1e261225f1fad820 Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Mon, 12 Jun 2017 11:40:38 -0400 Subject: [PATCH 13/15] HHH-11768 HHH-11714 : Checkstyle fixups. (cherry picked from commit 03053502e1a3829d0f989922c1960b3628b05723) --- .../org/hibernate/engine/spi/ActionQueue.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) 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 4abef37eb911..b77a6ea6b43e 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 @@ -285,8 +285,8 @@ private void addResolvedEntityInsertAction(AbstractEntityInsertAction insert) { } insert.makeEntityManaged(); if( unresolvedInsertions != null ) { - for (AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions(insert.getInstance(), session)) { - addResolvedEntityInsertAction(resolvedAction); + for ( AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions( insert.getInstance(), session ) ) { + addResolvedEntityInsertAction( resolvedAction ); } } } @@ -383,7 +383,7 @@ private void registerCleanupActions(Executable executable) { if( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } - beforeTransactionProcesses.register(executable.getBeforeTransactionCompletionProcess()); + beforeTransactionProcesses.register( executable.getBeforeTransactionCompletionProcess() ); } if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { invalidateSpaces( executable.getPropertySpaces() ); @@ -392,7 +392,7 @@ private void registerCleanupActions(Executable executable) { if( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } - afterTransactionProcesses.register(executable.getAfterTransactionCompletionProcess()); + afterTransactionProcesses.register( executable.getAfterTransactionCompletionProcess() ); } } @@ -495,7 +495,7 @@ public void afterTransactionCompletion(boolean success) { if ( !isTransactionCoordinatorShared ) { // Execute completion actions only in transaction owner (aka parent session). if( afterTransactionProcesses != null ) { - afterTransactionProcesses.afterTransactionCompletion(success); + afterTransactionProcesses.afterTransactionCompletion( success ); } } } @@ -593,13 +593,13 @@ private & Serializable> void executeAction if( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } - beforeTransactionProcesses.register(e.getBeforeTransactionCompletionProcess()); + beforeTransactionProcesses.register( e.getBeforeTransactionCompletionProcess() ); } if( e.getAfterTransactionCompletionProcess() != null ) { if( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } - afterTransactionProcesses.register(e.getAfterTransactionCompletionProcess()); + afterTransactionProcesses.register( e.getAfterTransactionCompletionProcess() ); } } } @@ -655,14 +655,14 @@ private void invalidateSpaces(Serializable... spaces) { */ @Override public String toString() { - return "ActionQueue[insertions=" + toString(insertions) - + " updates=" + toString(updates) - + " deletions=" + toString(deletions) - + " orphanRemovals=" + toString(orphanRemovals) - + " collectionCreations=" + toString(collectionCreations) - + " collectionRemovals=" + toString(collectionRemovals) - + " collectionUpdates=" + toString(collectionUpdates) - + " collectionQueuedOps=" + toString(collectionQueuedOps) + return "ActionQueue[insertions=" + toString( insertions ) + + " updates=" + toString( updates ) + + " deletions=" + toString( deletions ) + + " orphanRemovals=" + toString( orphanRemovals ) + + " collectionCreations=" + toString( collectionCreations ) + + " collectionRemovals=" + toString( collectionRemovals ) + + " collectionUpdates=" + toString( collectionUpdates ) + + " collectionQueuedOps=" + toString( collectionQueuedOps ) + " unresolvedInsertDependencies=" + unresolvedInsertions + "]"; } @@ -811,25 +811,25 @@ public boolean hasAnyQueuedActions() { public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { if ( rescuedEntity instanceof HibernateProxy ) { - LazyInitializer initializer = ( ( HibernateProxy ) rescuedEntity ).getHibernateLazyInitializer(); + LazyInitializer initializer = ( (HibernateProxy) rescuedEntity ).getHibernateLazyInitializer(); if ( !initializer.isUninitialized() ) { rescuedEntity = initializer.getImplementation( session ); } } if( deletions != null ) { for ( int i = 0; i < deletions.size(); i++ ) { - EntityDeleteAction action = deletions.get(i); + EntityDeleteAction action = deletions.get( i ); if (action.getInstance() == rescuedEntity) { - deletions.remove(i); + deletions.remove( i ); return; } } } if( orphanRemovals != null ) { for ( int i = 0; i < orphanRemovals.size(); i++ ) { - EntityDeleteAction action = orphanRemovals.get(i); + EntityDeleteAction action = orphanRemovals.get( i ); if (action.getInstance() == rescuedEntity) { - orphanRemovals.remove(i); + orphanRemovals.remove( i ); return; } } @@ -851,9 +851,9 @@ public void serialize(ObjectOutputStream oos) throws IOException { unresolvedInsertions.serialize( oos ); for ( ListProvider p : EXECUTABLE_LISTS_MAP.values() ) { - ExecutableList l = p.get(this); + ExecutableList l = p.get( this ); if( l == null ) { - oos.writeBoolean(false); + oos.writeBoolean( false ); } else { oos.writeBoolean( true ); @@ -874,18 +874,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { public static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor session) throws IOException, ClassNotFoundException { final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { - LOG.trace("Deserializing action-queue"); + LOG.trace( "Deserializing action-queue" ); } ActionQueue rtn = new ActionQueue( session ); rtn.unresolvedInsertions = UnresolvedEntityInsertActions.deserialize( ois, session ); for ( ListProvider provider : EXECUTABLE_LISTS_MAP.values() ) { - ExecutableList l = provider.get(rtn); + ExecutableList l = provider.get( rtn ); boolean notNull = ois.readBoolean(); if( notNull ) { if(l == null) { - l = provider.init(rtn); + l = provider.init( rtn ); } l.readExternal( ois ); @@ -899,7 +899,7 @@ public static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor return rtn; } - private static abstract class AbstractTransactionCompletionProcessQueue { + private abstract static class AbstractTransactionCompletionProcessQueue { protected SessionImplementor session; // Concurrency handling required when transaction completion process is dynamically registered // inside event listener (HHH-7478). @@ -1111,7 +1111,7 @@ public void sort(List insertions) { } addParentChildEntityNames( action, batchIdentifier ); entityBatchIdentifier.put( currentEntity, batchIdentifier ); - addToBatch(batchIdentifier, action); + addToBatch( batchIdentifier, action ); } insertions.clear(); @@ -1220,7 +1220,7 @@ private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAct } - private static abstract class ListProvider { + private abstract static class ListProvider { abstract ExecutableList get(ActionQueue instance); abstract ExecutableList init(ActionQueue instance); ExecutableList getOrInit( ActionQueue instance ) { From c9541834d522e5ce55a716e2361706ba2d7b6db9 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 20 Sep 2017 10:42:39 +0300 Subject: [PATCH 14/15] HHH-11996 - order_inserts causing constraint violation (cherry picked from commit 287221e26e1ce7bfcf537b4f0c6d9476f0d5b1ca) --- .../org/hibernate/engine/spi/ActionQueue.java | 3 +- .../InsertOrderingWithMultipleManyToOne.java | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java 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 b77a6ea6b43e..ce9f2b9c8af8 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 @@ -1068,8 +1068,7 @@ boolean hasAnyParentEntityNames(BatchIdentifier batchIdentifier) { } boolean hasAnyChildEntityNames(BatchIdentifier batchIdentifier) { - return childEntityNames.contains( batchIdentifier.getEntityName() ) || - parentEntityNames.contains( batchIdentifier.getRootEntityName() ); + return childEntityNames.contains( batchIdentifier.getEntityName() ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java new file mode 100644 index 000000000000..e980394cf723 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java @@ -0,0 +1,164 @@ +/* + * 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.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.SequenceGenerator; + +import org.hibernate.cfg.Environment; + +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.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-11996") +public class InsertOrderingWithMultipleManyToOne + extends BaseNonConfigCoreFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Parent.class, + ChildA.class, + ChildB.class, + }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + settings.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + connectionProvider + ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + connectionProvider.stop(); + } + + @Test + public void testBatching() throws SQLException { + doInHibernate( this::sessionFactory, session -> { + Parent parent = new Parent(); + session.persist(parent); + + ChildA childA = new ChildA(); + childA.setParent(parent); + session.persist(childA); + + ChildB childB = new ChildB(); + childB.setParent(parent); + session.persist(childB); + + connectionProvider.clear(); + } ); + + assertEquals( 3, connectionProvider.getPreparedStatements().size() ); + /*PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement( + "insert into Address (ID) values (?)" ); + verify( addressPreparedStatement, times( 2 ) ).addBatch(); + verify( addressPreparedStatement, times( 1 ) ).executeBatch(); + PreparedStatement personPreparedStatement = connectionProvider.getPreparedStatement( + "insert into Person (ID) values (?)" ); + verify( personPreparedStatement, times( 4 ) ).addBatch(); + verify( personPreparedStatement, times( 1 ) ).executeBatch();*/ + } + + @Entity(name = "Parent") + public static class Parent { + @Id + @GeneratedValue + private Integer id; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + } + + @Entity(name = "ChildA") + public static class ChildA { + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + + @Entity(name = "ChildB") + public static class ChildB { + @Id + @GeneratedValue + private Integer id; + + @ManyToOne + private Parent parent; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } +} From 21253df74d3c7d457795f51408811515ba573320 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 25 Jan 2018 23:40:59 -0800 Subject: [PATCH 15/15] HHH-11996 : Fix test to work with 5.1 branch --- .../InsertOrderingWithMultipleManyToOne.java | 25 +++++++++++++------ ...paredStatementProxyConnectionProvider.java | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java index e980394cf723..0ede6345102a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithMultipleManyToOne.java @@ -21,14 +21,17 @@ import javax.persistence.ManyToOne; import javax.persistence.SequenceGenerator; +import org.hibernate.Session; import org.hibernate.cfg.Environment; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; -import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.test.util.jdbc.BasicPreparedStatementObserver; +import org.hibernate.test.util.jdbc.PreparedStatementObserver; +import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider; +import org.junit.Before; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -40,7 +43,11 @@ public class InsertOrderingWithMultipleManyToOne extends BaseNonConfigCoreFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); + private static final PreparedStatementObserver preparedStatementObserver = new BasicPreparedStatementObserver(); + private static final PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider( + preparedStatementObserver + ); + @Override protected Class[] getAnnotatedClasses() { @@ -69,7 +76,9 @@ public void releaseResources() { @Test public void testBatching() throws SQLException { - doInHibernate( this::sessionFactory, session -> { + Session session = openSession(); + session.getTransaction().begin(); + { Parent parent = new Parent(); session.persist(parent); @@ -81,10 +90,12 @@ public void testBatching() throws SQLException { childB.setParent(parent); session.persist(childB); - connectionProvider.clear(); - } ); + preparedStatementObserver.clear(); + } + session.getTransaction().commit(); + session.close(); - assertEquals( 3, connectionProvider.getPreparedStatements().size() ); + assertEquals( 3, preparedStatementObserver.getPreparedStatements().size() ); /*PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement( "insert into Address (ID) values (?)" ); verify( addressPreparedStatement, times( 2 ) ).addBatch(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java index 48dfc62114d9..f472e50aafb5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java @@ -31,7 +31,7 @@ public class PreparedStatementProxyConnectionProvider extends ConnectionProvider private final Map acquiredConnectionProxyByConnection = new LinkedHashMap(); private final PreparedStatementObserver preparedStatementObserver; - public PreparedStatementProxyConnectionProvider(BasicPreparedStatementObserver preparedStatementObserver) { + public PreparedStatementProxyConnectionProvider(PreparedStatementObserver preparedStatementObserver) { this.preparedStatementObserver = preparedStatementObserver; }