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