HHH-9864 - foreign key violation with order_inserts=true and batches with mixed subclass entities

(cherry picked from commit 0c8261b0ae)

Conflicts:
	hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java
This commit is contained in:
Vlad Mihalcea 2016-09-30 15:45:39 +03:00 committed by Gail Badner
parent 113dc5dbcb
commit 9500f92bc6
8 changed files with 1007 additions and 277 deletions

View File

@ -10,12 +10,14 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -44,6 +46,8 @@ import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
/**
@ -1008,12 +1012,56 @@ public class ActionQueue {
*/
public static final InsertActionSorter INSTANCE = new InsertActionSorter();
private static class BatchIdentifier {
private final String entityName;
private Set<String> parentEntityNames = new HashSet<>( );
private Set<String> childEntityNames = new HashSet<>( );
public BatchIdentifier(
String entityName) {
this.entityName = entityName;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( !( o instanceof BatchIdentifier ) ) {
return false;
}
BatchIdentifier that = (BatchIdentifier) o;
return Objects.equals( entityName, that.entityName );
}
@Override
public int hashCode() {
return Objects.hash( entityName );
}
public String getEntityName() {
return entityName;
}
public Set<String> getParentEntityNames() {
return parentEntityNames;
}
public Set<String> getChildEntityNames() {
return childEntityNames;
}
}
// the mapping of entity names to their latest batch numbers.
private Map<String, Integer> latestBatches;
private Map<Object, Integer> entityBatchNumber;
private List<BatchIdentifier> latestBatches;
private Map<Object, BatchIdentifier> entityBatchIdentifier;
// the map of batch numbers to EntityInsertAction lists
private Map<Integer, List<AbstractEntityInsertAction>> actionBatches;
private Map<BatchIdentifier, List<AbstractEntityInsertAction>> actionBatches;
public InsertActionSorter() {
}
@ -1023,64 +1071,68 @@ public class ActionQueue {
*/
public void sort(List<AbstractEntityInsertAction> insertions) {
// optimize the hash size to eliminate a rehash.
this.latestBatches = new HashMap<String, Integer>();
this.entityBatchNumber = new HashMap<Object, Integer>( insertions.size() + 1, 1.0f );
this.actionBatches = new HashMap<Integer, List<AbstractEntityInsertAction>>();
this.latestBatches = new ArrayList<>( );
this.entityBatchIdentifier = new HashMap<>( insertions.size() + 1, 1.0f );
this.actionBatches = new HashMap<>();
// the list of entity names that indicate the batch number
for ( AbstractEntityInsertAction action : insertions ) {
// remove the current element from insertions. It will be added back later.
String entityName = action.getEntityName();
BatchIdentifier batchIdentifier = new BatchIdentifier( action.getEntityName() );
// the entity associated with the current action.
Object currentEntity = action.getInstance();
int index = latestBatches.indexOf( batchIdentifier );
Integer batchNumber;
if ( latestBatches.containsKey( entityName ) ) {
// There is already an existing batch for this type of entity.
// Check to see if the latest batch is acceptable.
batchNumber = findBatchNumber( action, entityName );
if ( index != -1 ) {
batchIdentifier = latestBatches.get( index );
}
else {
// add an entry for this type of entity.
// we can be assured that all referenced entities have already
// been processed,
// so specify that this entity is with the latest batch.
// doing the batch number before adding the name to the list is
// a faster way to get an accurate number.
batchNumber = actionBatches.size();
latestBatches.put( entityName, batchNumber );
latestBatches.add( batchIdentifier );
}
entityBatchNumber.put( currentEntity, batchNumber );
addToBatch( batchNumber, action );
addParentChildEntityNames( action, batchIdentifier );
entityBatchIdentifier.put( currentEntity, batchIdentifier );
addToBatch(batchIdentifier, action);
}
insertions.clear();
for ( int i = 0; i < latestBatches.size(); i++ ) {
BatchIdentifier batchIdentifier = latestBatches.get( i );
String entityName = batchIdentifier.getEntityName();
//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 )) {
latestBatches.remove( i );
latestBatches.add( j, batchIdentifier );
}
}
//Make sure that parent entries are not after children
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() )) {
latestBatches.remove( i );
latestBatches.add( j, batchIdentifier );
}
}
}
// now rebuild the insertions list. There is a batch for each entry in the name list.
for ( int i = 0; i < actionBatches.size(); i++ ) {
List<AbstractEntityInsertAction> batch = actionBatches.get( i );
for ( BatchIdentifier rootIdentifier : latestBatches ) {
List<AbstractEntityInsertAction> batch = actionBatches.get( rootIdentifier );
insertions.addAll( batch );
}
}
/**
* Finds an acceptable batch for this entity to be a member as part of the {@link InsertActionSorter}
* Add parent and child entity names so that we know how to rearrange dependencies
*
* @param action The action being sorted
* @param entityName The name of the entity affected by the action
* @return An appropriate batch number; todo document this process better
* @param batchIdentifier The batch identifier of the entity affected by the action
*/
private Integer findBatchNumber(AbstractEntityInsertAction action, String entityName) {
// loop through all the associated entities and make sure they have been
// processed before the latest
// batch associated with this entity type.
// the current batch number is the latest batch for this entity type.
Integer latestBatchNumberForType = latestBatches.get( entityName );
// loop through all the associations of the current entity and make sure that they are processed
// before the current batch number
private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) {
Object[] propertyValues = action.getState();
Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes();
@ -1088,35 +1140,30 @@ public class ActionQueue {
Object value = propertyValues[i];
Type type = propertyTypes[i];
if ( type.isEntityType() && value != null ) {
// find the batch number associated with the current association, if any.
Integer associationBatchNumber = entityBatchNumber.get( value );
if ( associationBatchNumber != null && associationBatchNumber.compareTo( latestBatchNumberForType ) > 0 ) {
// create a new batch for this type. The batch number is the number of current batches.
latestBatchNumberForType = actionBatches.size();
latestBatches.put( entityName, latestBatchNumberForType );
// since this entity will now be processed in the latest possible batch,
// we can be assured that it will come after all other associations,
// there's not need to continue checking.
break;
EntityType entityType = (EntityType) type;
String entityName = entityType.getName();
batchIdentifier.getParentEntityNames().add( entityName );
}
else if ( type.isCollectionType() && value != null ) {
CollectionType collectionType = (CollectionType) type;
String entityName = collectionType.getAssociatedEntityName( ( action.getSession().getFactory() ) );
batchIdentifier.getChildEntityNames().add( entityName );
}
}
}
return latestBatchNumberForType;
}
private void addToBatch(Integer batchNumber, AbstractEntityInsertAction action) {
List<AbstractEntityInsertAction> actions = actionBatches.get( batchNumber );
private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAction action) {
List<AbstractEntityInsertAction> actions = actionBatches.get( batchIdentifier );
if ( actions == null ) {
actions = new LinkedList<AbstractEntityInsertAction>();
actionBatches.put( batchNumber, actions );
actions = new LinkedList<>();
actionBatches.put( batchIdentifier, actions );
}
actions.add( action );
}
}
private static abstract class ListProvider<T extends Executable & Comparable & Serializable> {
abstract ExecutableList<T> get(ActionQueue instance);
abstract ExecutableList<T> init(ActionQueue instance);

View File

@ -0,0 +1,127 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.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.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-9864")
public class InsertOrderingWithBidirectionalManyToMany
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.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 -> {
Person father = new Person();
Person mother = new Person();
Person son = new Person();
Person daughter = new Person();
Address home = new Address();
Address office = new Address();
home.addPerson( father );
home.addPerson( mother );
home.addPerson( son );
home.addPerson( daughter );
office.addPerson( father );
office.addPerson( mother );
session.persist( home );
session.persist( office );
connectionProvider.clear();
} );
assertEquals( 3, connectionProvider.getPreparedStatements().size() );
PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement(
"insert into Address (ID) values (?)" );
verify( addressPreparedStatement, times( 2 ) ).addBatch();
PreparedStatement personPreparedStatement = connectionProvider.getPreparedStatement(
"insert into Person (ID) values (?)" );
verify( personPreparedStatement, times( 4 ) ).addBatch();
}
@Entity(name = "Address")
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@ManyToMany(mappedBy = "addresses", cascade = CascadeType.PERSIST)
private List<Person> persons = new ArrayList<>();
public void addPerson(Person person) {
persons.add( person );
person.addresses.add( this );
}
}
@Entity(name = "Person")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@ManyToMany
private List<Address> addresses = new ArrayList<>();
}
}

View File

@ -0,0 +1,126 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.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.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.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithBidirectionalOneToMany
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.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 -> {
Person father = new Person();
Person mother = new Person();
Person son = new Person();
Person daughter = new Person();
Address home = new Address();
Address office = new Address();
home.addPerson( father );
home.addPerson( mother );
home.addPerson( son );
home.addPerson( daughter );
office.addPerson( father );
office.addPerson( mother );
session.persist( home );
session.persist( office );
connectionProvider.clear();
} );
PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement(
"insert into Address (ID) values (?)" );
verify( addressPreparedStatement, times( 2 ) ).addBatch();
PreparedStatement personPreparedStatement = connectionProvider.getPreparedStatement(
"insert into Person (address_ID, ID) values (?, ?)" );
verify( personPreparedStatement, times( 4 ) ).addBatch();
}
@Entity(name = "Address")
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@OneToMany(mappedBy = "address", cascade = CascadeType.PERSIST)
private List<Person> persons = new ArrayList<>();
public void addPerson(Person person) {
persons.add( person );
person.address = this;
}
}
@Entity(name = "Person")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@ManyToOne
private Address address;
}
}

View File

@ -1,220 +0,0 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
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.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.BatchSize;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl;
import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator;
import org.hibernate.engine.jdbc.batch.internal.BatchingBatch;
import org.hibernate.engine.jdbc.batch.spi.Batch;
import org.hibernate.engine.jdbc.batch.spi.BatchKey;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* @author Steve Ebersole
*/
@TestForIssue( jiraKey = "HHH-9864" )
public class InsertOrderingWithInheritance extends BaseNonConfigCoreFunctionalTestCase {
@Test
@FailureExpected( jiraKey = "HHH-9864" )
public void testBatchOrdering() {
Session session = openSession();
session.getTransaction().begin();
// First object with dependent object (address)
final Person person = new Person();
person.addAddress(new Address());
session.persist(person);
// Derived Object with dependent object (address)
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress(new Address());
session.persist( specialPerson );
session.getTransaction().commit();
session.close();
cleanupData();
}
@Test
@FailureExpected( jiraKey = "HHH-9864" )
public void testBatchingAmongstSubClasses() {
StatsBatch.reset();
Session session = openSession();
session.getTransaction().begin();
int iterations = 12;
for ( int i = 0; i < iterations; i++ ) {
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress(new Address());
session.persist( specialPerson );
}
StatsBatch.reset();
session.getTransaction().commit();
session.close();
// 2 batches of Person
// 2 batches of SpecialPerson
// 2 batches of Address
assertEquals( 6, StatsBatch.batchSizes.size() );
cleanupData();
}
private void cleanupData() {
Session session = openSession();
session.getTransaction().begin();
session.createQuery( "delete Address" ).executeUpdate();
session.createQuery( "delete Person" ).executeUpdate();
session.getTransaction().commit();
session.close();
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.class, SpecialPerson.class };
}
@Override
protected void addSettings(Map settings) {
super.addSettings( settings );
settings.put( Environment.ORDER_INSERTS, "true" );
settings.put( Environment.STATEMENT_BATCH_SIZE, "10" );
settings.put( BatchBuilderInitiator.BUILDER, StatsBatchBuilder.class.getName() );
}
@Entity(name = "Address")
@Table(name = "ADDRESS")
@Access(AccessType.FIELD)
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity( name = "Person" )
@Access(AccessType.FIELD)
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "CLASSINDICATOR", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@OneToMany(orphanRemoval = true, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "PERSONID", referencedColumnName = "ID", nullable = false, updatable = false)
@BatchSize(size = 100)
private Set<Address> addresses = new HashSet<Address>();
public void addAddress(Address address) {
this.addresses.add(address);
}
}
@Entity
@Access(AccessType.FIELD)
@DiscriminatorValue("2")
public static class SpecialPerson extends Person {
@Column(name = "special")
private String special;
}
public static class Counter {
public int count = 0;
}
public static class StatsBatch extends BatchingBatch {
private static String batchSQL;
private static List batchSizes = new ArrayList();
private static int currentBatch = -1;
public StatsBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) {
super( key, jdbcCoordinator, jdbcBatchSize );
}
static void reset() {
batchSizes = new ArrayList();
currentBatch = -1;
batchSQL = null;
}
@Override
public PreparedStatement getBatchStatement(String sql, boolean callable) {
if ( batchSQL == null || ! batchSQL.equals( sql ) ) {
currentBatch++;
batchSQL = sql;
batchSizes.add( currentBatch, new Counter() );
}
return super.getBatchStatement( sql, callable );
}
@Override
public void addToBatch() {
Counter counter = ( Counter ) batchSizes.get( currentBatch );
counter.count++;
super.addToBatch();
}
}
public static class StatsBatchBuilder extends BatchBuilderImpl {
private int jdbcBatchSize;
@Override
public void setJdbcBatchSize(int jdbcBatchSize) {
this.jdbcBatchSize = jdbcBatchSize;
}
@Override
public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) {
return new StatsBatch( key, jdbcCoordinator, jdbcBatchSize );
}
}
}

View File

@ -0,0 +1,154 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
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.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.BatchSize;
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;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithJoinedTableInheritance
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.class, SpecialPerson.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 testBatchOrdering() {
doInHibernate( this::sessionFactory, session -> {
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
// Derived Object with dependent object (address)
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
} );
}
@Test
public void testBatchingAmongstSubClasses() {
doInHibernate( this::sessionFactory, session -> {
int iterations = 12;
for ( int i = 0; i < iterations; i++ ) {
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
}
connectionProvider.clear();
} );
assertEquals( 26, connectionProvider.getPreparedStatements().size() );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Entity(name = "Address")
@Table(name = "ADDRESS")
@Access(AccessType.FIELD)
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "Person")
@Access(AccessType.FIELD)
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "CLASSINDICATOR", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@OneToMany(orphanRemoval = true, cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
})
@JoinColumn(name = "PERSONID", referencedColumnName = "ID", nullable = false, updatable = false)
@BatchSize(size = 100)
private Set<Address> addresses = new HashSet<Address>();
public void addAddress(Address address) {
this.addresses.add( address );
}
}
@Entity(name = "SpecialPerson")
@Access(AccessType.FIELD)
@DiscriminatorValue("2")
public static class SpecialPerson extends Person {
@Column(name = "special")
private String special;
}
}

View File

@ -0,0 +1,186 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
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.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.BatchSize;
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;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithJoinedTableMultiLevelInheritance
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
Address.class,
Person.class,
SpecialPerson.class,
AnotherPerson.class,
President.class,
Office.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 testBatchingAmongstSubClasses() {
doInHibernate( this::sessionFactory, session -> {
int iterations = 2;
for ( int i = 0; i < iterations; i++ ) {
final President president = new President();
president.addAddress( new Address() );
session.persist( president );
final AnotherPerson anotherPerson = new AnotherPerson();
Office office = new Office();
session.persist( office );
anotherPerson.office = office;
session.persist( anotherPerson );
final Person person = new Person();
session.persist( person );
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
}
connectionProvider.clear();
} );
assertEquals( 17, connectionProvider.getPreparedStatements().size() );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Override
protected void cleanupTestData() throws Exception {
doInHibernate( this::sessionFactory, session -> {
session.createQuery( "delete Address" ).executeUpdate();
session.createQuery( "delete Person" ).executeUpdate();
session.createQuery( "delete SpecialPerson" ).executeUpdate();
session.createQuery( "delete AnotherPerson" ).executeUpdate();
session.createQuery( "delete Office" ).executeUpdate();
session.createQuery( "delete President" ).executeUpdate();
} );
}
@Entity(name = "Address")
@Table(name = "ADDRESS")
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "Office")
public static class Office {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "Person")
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "CLASSINDICATOR", discriminatorType = DiscriminatorType.INTEGER)
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "SpecialPerson")
public static class SpecialPerson extends Person {
@Column(name = "special")
private String special;
@OneToMany(orphanRemoval = true, cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
})
@JoinColumn(name = "PERSONID", referencedColumnName = "ID", nullable = false, updatable = false)
@BatchSize(size = 100)
private Set<Address> addresses = new HashSet<Address>();
public void addAddress(Address address) {
this.addresses.add( address );
}
}
@Entity(name = "AnotherPerson")
public static class AnotherPerson extends Person {
private boolean working;
@ManyToOne
private Office office;
}
@Entity(name = "President")
public static class President extends SpecialPerson {
@Column(name = "salary")
private BigDecimal salary;
}
}

View File

@ -0,0 +1,155 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
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.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.BatchSize;
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;
/**
* @author Steve Ebersole
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithSingleTableInheritance
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.class, SpecialPerson.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 testBatchOrdering() {
doInHibernate( this::sessionFactory, session -> {
// First object with dependent object (address)
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
// Derived Object with dependent object (address)
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
} );
}
@Test
public void testBatchingAmongstSubClasses() {
doInHibernate( this::sessionFactory, session -> {
int iterations = 12;
for ( int i = 0; i < iterations; i++ ) {
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
}
connectionProvider.clear();
} );
assertEquals( 3, connectionProvider.getPreparedStatements().size() );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Entity(name = "Address")
@Table(name = "ADDRESS")
@Access(AccessType.FIELD)
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "Person")
@Access(AccessType.FIELD)
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "CLASSINDICATOR", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@OneToMany(orphanRemoval = true, cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
})
@JoinColumn(name = "PERSONID", referencedColumnName = "ID", nullable = false, updatable = false)
@BatchSize(size = 100)
private Set<Address> addresses = new HashSet<Address>();
public void addAddress(Address address) {
this.addresses.add( address );
}
}
@Entity
@Access(AccessType.FIELD)
@DiscriminatorValue("2")
public static class SpecialPerson extends Person {
@Column(name = "special")
private String special;
}
}

View File

@ -0,0 +1,155 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.insertordering;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
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.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.hibernate.annotations.BatchSize;
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;
/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithTablePerClassInheritance
extends BaseNonConfigCoreFunctionalTestCase {
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Address.class, Person.class, SpecialPerson.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 testBatchOrdering() {
doInHibernate( this::sessionFactory, session -> {
// First object with dependent object (address)
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
// Derived Object with dependent object (address)
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
} );
}
@Test
public void testBatchingAmongstSubClasses() {
doInHibernate( this::sessionFactory, session -> {
int iterations = 12;
for ( int i = 0; i < iterations; i++ ) {
final Person person = new Person();
person.addAddress( new Address() );
session.persist( person );
final SpecialPerson specialPerson = new SpecialPerson();
specialPerson.addAddress( new Address() );
session.persist( specialPerson );
}
connectionProvider.clear();
} );
assertEquals( 3, connectionProvider.getPreparedStatements().size() );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Entity(name = "Address")
@Table(name = "ADDRESS")
@Access(AccessType.FIELD)
public static class Address {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
}
@Entity(name = "Person")
@Access(AccessType.FIELD)
@Table(name = "PERSON")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(name = "CLASSINDICATOR", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
public static class Person {
@Id
@Column(name = "ID", nullable = false)
@SequenceGenerator(name = "ID", sequenceName = "PERSON_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
private Long id;
@OneToMany(orphanRemoval = true, cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
})
@JoinColumn(name = "PERSONID", referencedColumnName = "ID", nullable = false, updatable = false)
@BatchSize(size = 100)
private Set<Address> addresses = new HashSet<Address>();
public void addAddress(Address address) {
this.addresses.add( address );
}
}
@Entity(name = "SpecialPerson")
@Access(AccessType.FIELD)
@DiscriminatorValue("2")
public static class SpecialPerson extends Person {
@Column(name = "special")
private String special;
}
}