HHH-12968 - Flush is not flushing inserts for inherited tables before a select within a transaction

This commit is contained in:
Javad Alimohammadi 2018-09-23 18:44:04 +03:30 committed by Vlad Mihalcea
parent 519905b99b
commit 9dfdb2b471
3 changed files with 437 additions and 10 deletions

View File

@ -77,6 +77,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.ValueInclusion; import org.hibernate.engine.spi.ValueInclusion;
import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.id.PostInsertIdentifierGenerator; import org.hibernate.id.PostInsertIdentifierGenerator;
import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.insert.Binder; import org.hibernate.id.insert.Binder;
@ -138,8 +139,6 @@ import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper; import org.hibernate.type.TypeHelper;
import org.hibernate.type.VersionType; import org.hibernate.type.VersionType;
import static org.hibernate.internal.util.StringHelper.safeInterning;
/** /**
* Basic functionality for persisting an entity via JDBC * Basic functionality for persisting an entity via JDBC
* through either generated or custom SQL * through either generated or custom SQL
@ -148,7 +147,7 @@ import static org.hibernate.internal.util.StringHelper.safeInterning;
*/ */
public abstract class AbstractEntityPersister public abstract class AbstractEntityPersister
implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable, implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable,
SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable { SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class ); private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class );
@ -3129,8 +3128,9 @@ public abstract class AbstractEntityPersister
// TODO : shouldn't inserts be Expectations.NONE? // TODO : shouldn't inserts be Expectations.NONE?
final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] ); final Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] );
final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize(); final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize();
final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1; final boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1 && !( getIdentifierGenerator() instanceof IdentityGenerator );
if ( useBatch && inserBatchKey == null) {
if ( useBatch && inserBatchKey == null ) {
inserBatchKey = new BasicBatchKey( inserBatchKey = new BasicBatchKey(
getEntityName() + "#INSERT", getEntityName() + "#INSERT",
expectation expectation

View File

@ -22,7 +22,6 @@ import javax.persistence.ManyToOne;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
@ -30,6 +29,7 @@ import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
@ -113,8 +113,8 @@ public class IdentityJoinedSubclassBatchingTest extends BaseCoreFunctionalTestCa
doInHibernate( this::sessionFactory, s -> { doInHibernate( this::sessionFactory, s -> {
int i = 0; int i = 0;
ScrollableResults sr = s.createQuery( ScrollableResults sr = s.createQuery(
"select e from Employee e" ) "select e from Employee e" )
.scroll( ScrollMode.FORWARD_ONLY ); .scroll( ScrollMode.FORWARD_ONLY );
while ( sr.next() ) { while ( sr.next() ) {
Employee e = (Employee) sr.get( 0 ); Employee e = (Employee) sr.get( 0 );
@ -125,8 +125,8 @@ public class IdentityJoinedSubclassBatchingTest extends BaseCoreFunctionalTestCa
doInHibernate( this::sessionFactory, s -> { doInHibernate( this::sessionFactory, s -> {
int i = 0; int i = 0;
ScrollableResults sr = s.createQuery( ScrollableResults sr = s.createQuery(
"select e from Employee e" ) "select e from Employee e" )
.scroll( ScrollMode.FORWARD_ONLY ); .scroll( ScrollMode.FORWARD_ONLY );
while ( sr.next() ) { while ( sr.next() ) {
Employee e = (Employee) sr.get( 0 ); Employee e = (Employee) sr.get( 0 );
@ -135,6 +135,76 @@ public class IdentityJoinedSubclassBatchingTest extends BaseCoreFunctionalTestCa
} ); } );
} }
@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 @Embeddable
public static class Address implements Serializable { public static class Address implements Serializable {

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}
}