From 97a99c31f0fec26569a3f5e420a0a9d120caf17c Mon Sep 17 00:00:00 2001 From: Javad Alimohammadi Date: Sun, 23 Sep 2018 18:44:04 +0330 Subject: [PATCH 1/2] HHH-12968 - Flush is not flushing inserts for inherited tables before a select within a transaction (cherry picked from commit 9dfdb2b471ce6c1d26c4a5cbb15586d961dcd627) --- .../entity/AbstractEntityPersister.java | 10 +- .../IdentityJoinedSubclassBatchingTest.java | 80 +++- .../SequenceJoinedSubclassBatchingTest.java | 357 ++++++++++++++++++ 3 files changed, 437 insertions(+), 10 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/SequenceJoinedSubclassBatchingTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 9e715e7a4273..cc530e88e16e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -83,6 +83,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.IdentityGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.Binder; @@ -144,8 +145,6 @@ import org.hibernate.type.TypeHelper; import org.hibernate.type.VersionType; -import static org.hibernate.internal.util.StringHelper.safeInterning; - /** * Basic functionality for persisting an entity via JDBC * through either generated or custom SQL @@ -154,7 +153,7 @@ */ public abstract class AbstractEntityPersister implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable, - SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { + SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class ); @@ -3161,8 +3160,9 @@ protected void insert( // TODO : shouldn't inserts be Expectations.NONE? final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] ); final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); - final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1; - if ( useBatch && inserBatchKey == null) { + final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1 && !( getIdentifierGenerator() instanceof IdentityGenerator ); + + if ( useBatch && inserBatchKey == null ) { inserBatchKey = new BasicBatchKey( getEntityName() + "#INSERT", expectation diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/IdentityJoinedSubclassBatchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/IdentityJoinedSubclassBatchingTest.java index 20eae4fdcacb..b9eacccf38fb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/IdentityJoinedSubclassBatchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/IdentityJoinedSubclassBatchingTest.java @@ -22,7 +22,6 @@ import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; -import org.hibernate.annotations.GenericGenerator; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; @@ -30,6 +29,7 @@ import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; @@ -113,8 +113,8 @@ public void doBatchInsertUpdateJoined(int nEntities, int nBeforeFlush) { doInHibernate( this::sessionFactory, s -> { int i = 0; ScrollableResults sr = s.createQuery( - "select e from Employee e" ) - .scroll( ScrollMode.FORWARD_ONLY ); + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); while ( sr.next() ) { Employee e = (Employee) sr.get( 0 ); @@ -125,8 +125,8 @@ public void doBatchInsertUpdateJoined(int nEntities, int nBeforeFlush) { doInHibernate( this::sessionFactory, s -> { int i = 0; ScrollableResults sr = s.createQuery( - "select e from Employee e" ) - .scroll( ScrollMode.FORWARD_ONLY ); + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); while ( sr.next() ) { Employee e = (Employee) sr.get( 0 ); @@ -135,6 +135,76 @@ public void doBatchInsertUpdateJoined(int nEntities, int nBeforeFlush) { } ); } + @Test + public void testAssertSubclassInsertedSuccessfullyAfterCommit() { + final int nEntities = 10; + + doInHibernate( this::sessionFactory, s -> { + for ( int i = 0; i < nEntities; i++ ) { + Employee e = new Employee(); + e.setName( "Mark" ); + e.setTitle( "internal sales" ); + e.setSex( 'M' ); + e.setAddress( "buckhead" ); + e.setZip( "30305" ); + e.setCountry( "USA" ); + s.save( e ); + } + } ); + + doInHibernate( this::sessionFactory, s -> { + long numberOfInsertedEmployee = (long) s.createQuery( "select count(e) from Employee e" ).uniqueResult(); + Assert.assertEquals( nEntities, numberOfInsertedEmployee ); + } ); + + doInHibernate( this::sessionFactory, s -> { + int i = 0; + ScrollableResults sr = s.createQuery( + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); + + while ( sr.next() ) { + Employee e = (Employee) sr.get( 0 ); + s.delete( e ); + } + } ); + + } + + @Test + public void testAssertSubclassInsertedSuccessfullyAfterFlush() { + + doInHibernate( this::sessionFactory, s -> { + Employee e = new Employee(); + e.setName( "Mark" ); + e.setTitle( "internal sales" ); + e.setSex( 'M' ); + e.setAddress( "buckhead" ); + e.setZip( "30305" ); + e.setCountry( "USA" ); + s.save( e ); + s.flush(); + + long numberOfInsertedEmployee = (long) s.createQuery( "select count(e) from Employee e" ).uniqueResult(); + Assert.assertEquals( 1L, numberOfInsertedEmployee ); + } ); + + + doInHibernate( this::sessionFactory, s -> { + int i = 0; + ScrollableResults sr = s.createQuery( + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); + + while ( sr.next() ) { + Employee e = (Employee) sr.get( 0 ); + s.delete( e ); + } + } ); + + } + + @Embeddable public static class Address implements Serializable { diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/SequenceJoinedSubclassBatchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/SequenceJoinedSubclassBatchingTest.java new file mode 100644 index 000000000000..06d0ff0a7dd3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinedsubclassbatch/SequenceJoinedSubclassBatchingTest.java @@ -0,0 +1,357 @@ +/* + * 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.joinedsubclassbatch; + +import java.io.Serializable; +import java.math.BigDecimal; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.Embedded; +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.ManyToOne; +import javax.persistence.SequenceGenerator; + +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +/** + * Test batching of insert,update,delete on joined subclasses using SEQUENCE + * + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-12968\n") +@RequiresDialectFeature(DialectChecks.SupportsSequences.class) +public class SequenceJoinedSubclassBatchingTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Employee.class, + Customer.class + }; + } + + @Override + public void configure(Configuration cfg) { + cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "20" ); + } + + @Test + public void doBatchInsertUpdateJoinedSubclassNrEqualWithBatch() { + doBatchInsertUpdateJoined( 20, 20 ); + } + + @Test + public void doBatchInsertUpdateJoinedSubclassNrLessThenBatch() { + doBatchInsertUpdateJoined( 19, 20 ); + } + + @Test + public void doBatchInsertUpdateJoinedSubclassNrBiggerThenBatch() { + doBatchInsertUpdateJoined( 21, 20 ); + } + + @Test + public void testBatchInsertUpdateSizeEqJdbcBatchSize() { + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); + doBatchInsertUpdateJoined( 50, batchSize ); + } + + @Test + public void testBatchInsertUpdateSizeLtJdbcBatchSize() { + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); + doBatchInsertUpdateJoined( 50, batchSize - 1 ); + } + + @Test + public void testBatchInsertUpdateSizeGtJdbcBatchSize() { + int batchSize = sessionFactory().getSettings().getJdbcBatchSize(); + doBatchInsertUpdateJoined( 50, batchSize + 1 ); + } + + public void doBatchInsertUpdateJoined(int nEntities, int nBeforeFlush) { + + doInHibernate( this::sessionFactory, s -> { + for ( int i = 0; i < nEntities; i++ ) { + Employee e = new Employee(); + e.getId(); + e.setName( "Mark" ); + e.setTitle( "internal sales" ); + e.setSex( 'M' ); + e.setAddress( "buckhead" ); + e.setZip( "30305" ); + e.setCountry( "USA" ); + s.save( e ); + if ( i % nBeforeFlush == 0 && i > 0 ) { + s.flush(); + s.clear(); + } + } + } ); + + doInHibernate( this::sessionFactory, s -> { + int i = 0; + ScrollableResults sr = s.createQuery( + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); + + while ( sr.next() ) { + Employee e = (Employee) sr.get( 0 ); + e.setTitle( "Unknown" ); + } + } ); + + doInHibernate( this::sessionFactory, s -> { + int i = 0; + ScrollableResults sr = s.createQuery( + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); + + while ( sr.next() ) { + Employee e = (Employee) sr.get( 0 ); + s.delete( e ); + } + } ); + } + + @Test + public void testAssertSubclassInsertedSuccessfullyAfterFlush() { + + doInHibernate( this::sessionFactory, s -> { + Employee e = new Employee(); + e.setName( "Mark" ); + e.setTitle( "internal sales" ); + e.setSex( 'M' ); + e.setAddress( "buckhead" ); + e.setZip( "30305" ); + e.setCountry( "USA" ); + s.save( e ); + s.flush(); + + long numberOfInsertedEmployee = (long) s.createQuery( "select count(e) from Employee e" ).uniqueResult(); + Assert.assertEquals( 1L, numberOfInsertedEmployee ); + } ); + + + doInHibernate( this::sessionFactory, s -> { + int i = 0; + ScrollableResults sr = s.createQuery( + "select e from Employee e" ) + .scroll( ScrollMode.FORWARD_ONLY ); + + while ( sr.next() ) { + Employee e = (Employee) sr.get( 0 ); + s.delete( e ); + } + } ); + + } + + @Embeddable + public static class Address implements Serializable { + + public String address; + + public String zip; + + public String country; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getZip() { + return zip; + } + + public void setZip(String zip) { + this.zip = zip; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + } + + @Entity(name = "Customer") + public static class Customer extends Person { + + @ManyToOne(fetch = FetchType.LAZY) + private Employee salesperson; + + private String comments; + + public Employee getSalesperson() { + return salesperson; + } + + public void setSalesperson(Employee salesperson) { + this.salesperson = salesperson; + } + + public String getComments() { + return comments; + } + + public void setComments(String comments) { + this.comments = comments; + } + } + + @Entity(name = "Employee") + public static class Employee extends Person { + + @Column(nullable = false, length = 20) + private String title; + + private BigDecimal salary; + + private double passwordExpiryDays; + + @ManyToOne(fetch = FetchType.LAZY) + private Employee manager; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Employee getManager() { + return manager; + } + + public void setManager(Employee manager) { + this.manager = manager; + } + + public BigDecimal getSalary() { + return salary; + } + + public void setSalary(BigDecimal salary) { + this.salary = salary; + } + + public double getPasswordExpiryDays() { + return passwordExpiryDays; + } + + public void setPasswordExpiryDays(double passwordExpiryDays) { + this.passwordExpiryDays = passwordExpiryDays; + } + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @Column(nullable = false, length = 80) + private String name; + + @Column(nullable = false, updatable = false) + private char sex; + + @javax.persistence.Version + private int version; + + private double heightInches; + + @Embedded + private Address address = new Address(); + + public Address getAddress() { + return address; + } + + public void setAddress(String string) { + this.address.address = string; + } + + public void setZip(String string) { + this.address.zip = string; + } + + public void setCountry(String string) { + this.address.country = string; + } + + public char getSex() { + return sex; + } + + public void setSex(char sex) { + this.sex = sex; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String identity) { + this.name = identity; + } + + public double getHeightInches() { + return heightInches; + } + + public void setHeightInches(double heightInches) { + this.heightInches = heightInches; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + } +} + From 3b0c912f8da922a00ab974078ac011553b5eb921 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 24 Sep 2018 20:40:37 +0300 Subject: [PATCH 2/2] HHH-12968 - Flush is not flushing inserts for inherited tables before a select within a transaction Extract IdentityGenerator batch support validation logic (cherry picked from commit f21c8c292759466151a5adf1d11a445f2d5c4ba8) --- .../org/hibernate/id/AbstractPostInsertGenerator.java | 5 +++++ .../java/org/hibernate/id/IdentifierGenerator.java | 10 +++++++++- .../persister/entity/AbstractEntityPersister.java | 5 +++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java index ca57bc0dd3c9..3dc3649dc376 100755 --- a/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/AbstractPostInsertGenerator.java @@ -32,4 +32,9 @@ public boolean supportsBulkInsertionIdentifierGeneration() { public String determineBulkInsertionIdentifierGenerationSelectFragment(Dialect dialect) { return null; } + + @Override + public boolean supportsJdbcBatchInserts() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java index aa157fe052df..51bc8a355e5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGenerator.java @@ -7,7 +7,6 @@ package org.hibernate.id; import java.io.Serializable; - import javax.persistence.GeneratedValue; import org.hibernate.HibernateException; @@ -60,4 +59,13 @@ public interface IdentifierGenerator { * @throws HibernateException Indicates trouble generating the identifier */ Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException; + + /** + * Check if JDBC batch inserts are supported. + * + * @return JDBC batch inserts are supported. + */ + default boolean supportsJdbcBatchInserts() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index cc530e88e16e..abdb534c89c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -83,7 +83,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.ValueInclusion; import org.hibernate.id.IdentifierGenerator; -import org.hibernate.id.IdentityGenerator; import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.Binder; @@ -3160,7 +3159,9 @@ protected void insert( // TODO : shouldn't inserts be Expectations.NONE? final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] ); final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); - final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1 && !( getIdentifierGenerator() instanceof IdentityGenerator ); + final boolean useBatch = expectation.canBeBatched() && + jdbcBatchSizeToUse > 1 && + getIdentifierGenerator().supportsJdbcBatchInserts(); if ( useBatch && inserBatchKey == null ) { inserBatchKey = new BasicBatchKey(