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:
parent
113dc5dbcb
commit
9500f92bc6
|
@ -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);
|
||||
|
|
|
@ -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