Skip to content

Commit

Permalink
HHH-11585 - Batch ordering fails for bidirectional one-to-one associa…
Browse files Browse the repository at this point in the history
…tions

(cherry picked from commit f90845c)

HHH-11585 : Batch ordering fails for bidirectional one-to-one associations

HHH-11585 - Batch ordering fails for bidirectional one-to-one associations

- take into consideration legacy one-to-one mappings with composite ids as well

(cherry picked from commit acae69f)

HHH-11585 : Fix test case to work on pre-5.2 branches

HHH-11585 - Batch ordering fails for bidirectional one-to-one associations
  • Loading branch information
Vlad Mihalcea authored and gbadner committed Jun 16, 2017
1 parent 820caed commit 16a0f01
Show file tree
Hide file tree
Showing 11 changed files with 832 additions and 21 deletions.
Expand Up @@ -44,10 +44,13 @@
import org.hibernate.engine.internal.NonNullableTransientDependencies;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.OneToOneType;
import org.hibernate.type.Type;

/**
Expand Down Expand Up @@ -1134,22 +1137,33 @@ public void sort(List<AbstractEntityInsertAction> insertions) {
*/
private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) {
Object[] propertyValues = action.getState();
Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes();

for ( int i = 0; i < propertyValues.length; i++ ) {
Object value = propertyValues[i];
Type type = propertyTypes[i];
if ( type.isEntityType() && value != null ) {
EntityType entityType = (EntityType) type;
String entityName = entityType.getName();
batchIdentifier.getParentEntityNames().add( entityName );
}
else if ( type.isCollectionType() && value != null ) {
CollectionType collectionType = (CollectionType) type;
final SessionFactoryImplementor sessionFactory = action.getSession().getFactory();
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
batchIdentifier.getChildEntityNames().add( entityName );

ClassMetadata classMetadata = action.getPersister().getClassMetadata();
if ( classMetadata != null ) {
Type[] propertyTypes = classMetadata.getPropertyTypes();

for ( int i = 0; i < propertyValues.length; i++ ) {
Object value = propertyValues[i];
Type type = propertyTypes[i];
if ( type.isEntityType() && value != null ) {
EntityType entityType = (EntityType) type;
String entityName = entityType.getName();

if ( entityType.isOneToOne() &&
OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
batchIdentifier.getChildEntityNames().add( entityName );
}
else {
batchIdentifier.getParentEntityNames().add( entityName );
}
}
else if ( type.isCollectionType() && value != null ) {
CollectionType collectionType = (CollectionType) type;
final SessionFactoryImplementor sessionFactory = action.getSession().getFactory();
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
batchIdentifier.getChildEntityNames().add( entityName );
}
}
}
}
Expand Down
@@ -0,0 +1,51 @@
/*
* 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.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.util.LinkedHashMap;
import java.util.Map;

import org.hibernate.test.util.jdbc.BasicPreparedStatementObserver;

/**
* @author Gail Badner
*/
class BatchCountingPreparedStatementObserver extends BasicPreparedStatementObserver {
private final Map<PreparedStatement, Integer> batchesAddedByPreparedStatement = new LinkedHashMap<PreparedStatement, Integer>();

@Override
public void preparedStatementCreated(PreparedStatement preparedStatement, String sql) {
super.preparedStatementCreated( preparedStatement, sql );
batchesAddedByPreparedStatement.put( preparedStatement, 0 );
}

@Override
public void preparedStatementMethodInvoked(
PreparedStatement preparedStatement,
Method method,
Object[] args,
Object invocationReturnValue) {
super.preparedStatementMethodInvoked( preparedStatement, method, args, invocationReturnValue );
if ( "addBatch".equals( method.getName() ) ) {
batchesAddedByPreparedStatement.put(
preparedStatement,
batchesAddedByPreparedStatement.get( preparedStatement ) + 1
);
}
}

public int getNumberOfBatchesAdded(PreparedStatement preparedStatement) {
return batchesAddedByPreparedStatement.get( preparedStatement );
}

public void connectionProviderStopped() {
super.connectionProviderStopped();
batchesAddedByPreparedStatement.clear();
}
}
@@ -0,0 +1,123 @@
/*
* 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.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.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;

import org.hibernate.Session;
import org.hibernate.cfg.Environment;

import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithBidirectionalMapsIdOneToOne
extends BaseNonConfigCoreFunctionalTestCase {

private BatchCountingPreparedStatementObserver preparedStatementObserver = new BatchCountingPreparedStatementObserver();
private PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider(
preparedStatementObserver
);

@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 {
Session session = openSession();
session.getTransaction().begin();
{
Person worker = new Person();
Person homestay = new Person();

Address home = new Address();
Address office = new Address();

home.addPerson( homestay );

office.addPerson( worker );

session.persist( home );
session.persist( office );

connectionProvider.clear();
}
session.getTransaction().commit();
session.close();

PreparedStatement addressPreparedStatement = preparedStatementObserver.getPreparedStatement(
"insert into Address (ID) values (?)" );
assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( addressPreparedStatement ) );

PreparedStatement personPreparedStatement = preparedStatementObserver.getPreparedStatement(
"insert into Person (address_ID) values (?)" );
assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( personPreparedStatement ) );
}

@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;

@OneToOne(mappedBy = "address", cascade = CascadeType.PERSIST)
private Person person;

public void addPerson(Person person) {
this.person = person;
person.address = this;
}
}

@Entity(name = "Person")
public static class Person {
@Id
private Long id;

@OneToOne
@MapsId
private Address address;
}
}
@@ -0,0 +1,118 @@
/*
* 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 org.hibernate.Session;
import org.hibernate.cfg.Environment;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;

import javax.persistence.*;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Map;

import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider;

import static org.junit.Assert.assertEquals;

/**
* @author Vlad Mihalcea
*/
@TestForIssue(jiraKey = "HHH-9864")
public class InsertOrderingWithBidirectionalOneToOne
extends BaseNonConfigCoreFunctionalTestCase {
private BatchCountingPreparedStatementObserver preparedStatementObserver = new BatchCountingPreparedStatementObserver();
private PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider(
preparedStatementObserver
);

@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 {
Session session = openSession();
session.getTransaction().begin();
{
Person worker = new Person();
Person homestay = new Person();

Address home = new Address();
Address office = new Address();

home.addPerson( homestay );

office.addPerson( worker );

session.persist( home );
session.persist( office );

connectionProvider.clear();
}
session.getTransaction().commit();
session.close();

PreparedStatement addressPreparedStatement = preparedStatementObserver.getPreparedStatement(
"insert into Address (ID) values (?)"
);
assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( addressPreparedStatement ) );

PreparedStatement personPreparedStatement = preparedStatementObserver.getPreparedStatement(
"insert into Person (address_ID, ID) values (?, ?)"
);
assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( personPreparedStatement ) );
}

@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;

@OneToOne(mappedBy = "address", cascade = CascadeType.PERSIST)
private Person person;

public void addPerson(Person person) {
this.person = 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;

@OneToOne
private Address address;
}
}

0 comments on commit 16a0f01

Please sign in to comment.