Skip to content

Commit f90845c

Browse files
Vlad MihalceaVlad Mihalcea
authored andcommitted
HHH-11585 - Batch ordering fails for bidirectional one-to-one associations
1 parent c8cbb8f commit f90845c

File tree

4 files changed

+263
-22
lines changed

4 files changed

+263
-22
lines changed

hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.hibernate.engine.internal.NonNullableTransientDependencies;
4545
import org.hibernate.internal.CoreLogging;
4646
import org.hibernate.internal.CoreMessageLogger;
47+
import org.hibernate.metadata.ClassMetadata;
4748
import org.hibernate.proxy.HibernateProxy;
4849
import org.hibernate.proxy.LazyInitializer;
4950
import org.hibernate.type.CollectionType;
@@ -1134,23 +1135,32 @@ public void sort(List<AbstractEntityInsertAction> insertions) {
11341135
*/
11351136
private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) {
11361137
Object[] propertyValues = action.getState();
1137-
Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes();
1138-
1139-
for ( int i = 0; i < propertyValues.length; i++ ) {
1140-
Object value = propertyValues[i];
1141-
Type type = propertyTypes[i];
1142-
if ( type.isEntityType() && value != null ) {
1143-
EntityType entityType = (EntityType) type;
1144-
String entityName = entityType.getName();
1145-
batchIdentifier.getParentEntityNames().add( entityName );
1146-
}
1147-
else if ( type.isCollectionType() && value != null ) {
1148-
CollectionType collectionType = (CollectionType) type;
1149-
final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() )
1150-
.getSessionFactory();
1151-
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
1152-
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
1153-
batchIdentifier.getChildEntityNames().add( entityName );
1138+
ClassMetadata classMetadata = action.getPersister().getClassMetadata();
1139+
if ( classMetadata != null ) {
1140+
Type[] propertyTypes = classMetadata.getPropertyTypes();
1141+
1142+
for ( int i = 0; i < propertyValues.length; i++ ) {
1143+
Object value = propertyValues[i];
1144+
Type type = propertyTypes[i];
1145+
if ( type.isEntityType() && value != null ) {
1146+
EntityType entityType = (EntityType) type;
1147+
String entityName = entityType.getName();
1148+
1149+
if ( entityType.isOneToOne() ) {
1150+
batchIdentifier.getChildEntityNames().add( entityName );
1151+
}
1152+
else {
1153+
batchIdentifier.getParentEntityNames().add( entityName );
1154+
}
1155+
}
1156+
else if ( type.isCollectionType() && value != null ) {
1157+
CollectionType collectionType = (CollectionType) type;
1158+
final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() )
1159+
.getSessionFactory();
1160+
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
1161+
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
1162+
batchIdentifier.getChildEntityNames().add( entityName );
1163+
}
11541164
}
11551165
}
11561166
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.insertordering;
8+
9+
import org.hibernate.cfg.Environment;
10+
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
11+
import org.hibernate.testing.TestForIssue;
12+
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
13+
import org.junit.Test;
14+
15+
import javax.persistence.*;
16+
import java.sql.PreparedStatement;
17+
import java.sql.SQLException;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
23+
import static org.mockito.Mockito.times;
24+
import static org.mockito.Mockito.verify;
25+
26+
/**
27+
* @author Vlad Mihalcea
28+
*/
29+
@TestForIssue(jiraKey = "HHH-9864")
30+
public class InsertOrderingWithBidirectionalOneToOne
31+
extends BaseNonConfigCoreFunctionalTestCase {
32+
33+
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
34+
35+
@Override
36+
protected Class[] getAnnotatedClasses() {
37+
return new Class[] { Address.class, Person.class };
38+
}
39+
40+
@Override
41+
protected void addSettings(Map settings) {
42+
settings.put( Environment.ORDER_INSERTS, "true" );
43+
settings.put( Environment.STATEMENT_BATCH_SIZE, "10" );
44+
settings.put(
45+
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
46+
connectionProvider
47+
);
48+
}
49+
50+
@Override
51+
public void releaseResources() {
52+
super.releaseResources();
53+
connectionProvider.stop();
54+
}
55+
56+
@Test
57+
public void testBatching() throws SQLException {
58+
doInHibernate( this::sessionFactory, session -> {
59+
Person worker = new Person();
60+
Person homestay = new Person();
61+
62+
Address home = new Address();
63+
Address office = new Address();
64+
65+
home.addPerson( homestay );
66+
67+
office.addPerson( worker );
68+
69+
session.persist( home );
70+
session.persist( office );
71+
72+
connectionProvider.clear();
73+
} );
74+
75+
PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement(
76+
"insert into Address (ID) values (?)" );
77+
verify( addressPreparedStatement, times( 2 ) ).addBatch();
78+
PreparedStatement personPreparedStatement = connectionProvider.getPreparedStatement(
79+
"insert into Person (address_ID, ID) values (?, ?)" );
80+
verify( personPreparedStatement, times( 2 ) ).addBatch();
81+
}
82+
83+
@Entity(name = "Address")
84+
public static class Address {
85+
@Id
86+
@Column(name = "ID", nullable = false)
87+
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
88+
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
89+
private Long id;
90+
91+
@OneToOne(mappedBy = "address", cascade = CascadeType.PERSIST)
92+
private Person person;
93+
94+
public void addPerson(Person person) {
95+
this.person = person;
96+
person.address = this;
97+
}
98+
}
99+
100+
@Entity(name = "Person")
101+
public static class Person {
102+
@Id
103+
@Column(name = "ID", nullable = false)
104+
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
105+
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
106+
private Long id;
107+
108+
@OneToOne
109+
private Address address;
110+
}
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.test.insertordering;
8+
9+
import java.sql.PreparedStatement;
10+
import java.sql.SQLException;
11+
import java.util.Map;
12+
import javax.persistence.CascadeType;
13+
import javax.persistence.Column;
14+
import javax.persistence.Entity;
15+
import javax.persistence.GeneratedValue;
16+
import javax.persistence.GenerationType;
17+
import javax.persistence.Id;
18+
import javax.persistence.OneToOne;
19+
import javax.persistence.SequenceGenerator;
20+
21+
import org.hibernate.cfg.Environment;
22+
23+
import org.hibernate.testing.TestForIssue;
24+
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
25+
import org.hibernate.test.util.jdbc.PreparedStatementSpyConnectionProvider;
26+
import org.junit.Test;
27+
28+
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
29+
import static org.mockito.Mockito.times;
30+
import static org.mockito.Mockito.verify;
31+
32+
/**
33+
* @author Vlad Mihalcea
34+
*/
35+
@TestForIssue(jiraKey = "HHH-9864")
36+
public class InsertOrderingWithUnidirectionalOneToOne
37+
extends BaseNonConfigCoreFunctionalTestCase {
38+
39+
private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider();
40+
41+
@Override
42+
protected Class[] getAnnotatedClasses() {
43+
return new Class[] { Address.class, Person.class };
44+
}
45+
46+
@Override
47+
protected void addSettings(Map settings) {
48+
settings.put( Environment.ORDER_INSERTS, "true" );
49+
settings.put( Environment.STATEMENT_BATCH_SIZE, "10" );
50+
settings.put(
51+
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
52+
connectionProvider
53+
);
54+
}
55+
56+
@Override
57+
public void releaseResources() {
58+
super.releaseResources();
59+
connectionProvider.stop();
60+
}
61+
62+
@Test
63+
public void testBatching() throws SQLException {
64+
doInHibernate( this::sessionFactory, session -> {
65+
Person worker = new Person();
66+
Person homestay = new Person();
67+
68+
Address home = new Address();
69+
Address office = new Address();
70+
71+
home.addPerson( homestay );
72+
73+
office.addPerson( worker );
74+
75+
session.persist( home );
76+
session.persist( office );
77+
78+
connectionProvider.clear();
79+
} );
80+
81+
PreparedStatement addressPreparedStatement = connectionProvider.getPreparedStatement(
82+
"insert into Address (person_ID, ID) values (?, ?)" );
83+
verify( addressPreparedStatement, times( 2 ) ).addBatch();
84+
PreparedStatement personPreparedStatement = connectionProvider.getPreparedStatement(
85+
"insert into Person (ID) values (?)" );
86+
verify( personPreparedStatement, times( 2 ) ).addBatch();
87+
}
88+
89+
@Entity(name = "Address")
90+
public static class Address {
91+
@Id
92+
@Column(name = "ID", nullable = false)
93+
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
94+
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
95+
private Long id;
96+
97+
@OneToOne( cascade = CascadeType.PERSIST )
98+
private Person person;
99+
100+
public void addPerson(Person person) {
101+
this.person = person;
102+
}
103+
}
104+
105+
@Entity(name = "Person")
106+
public static class Person {
107+
@Id
108+
@Column(name = "ID", nullable = false)
109+
@SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ")
110+
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID")
111+
private Long id;
112+
}
113+
}

hibernate-core/src/test/java/org/hibernate/test/legacy/MasterDetailTest.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,16 @@
99
import java.io.Serializable;
1010
import java.sql.Connection;
1111
import java.sql.SQLException;
12-
import java.util.ArrayList;
13-
import java.util.Collection;
14-
import java.util.HashSet;
15-
import java.util.Iterator;
16-
import java.util.List;
12+
import java.util.*;
1713

1814
import org.hibernate.Hibernate;
1915
import org.hibernate.LockMode;
2016
import org.hibernate.ObjectNotFoundException;
2117
import org.hibernate.Query;
2218
import org.hibernate.Session;
2319
import org.hibernate.Transaction;
20+
import org.hibernate.cfg.Configuration;
21+
import org.hibernate.cfg.Environment;
2422
import org.hibernate.criterion.Example;
2523
import org.hibernate.criterion.Restrictions;
2624
import org.hibernate.dialect.HSQLDialect;
@@ -54,6 +52,15 @@ public String[] getMappings() {
5452
};
5553
}
5654

55+
@Override
56+
public void configure(Configuration cfg) {
57+
super.configure(cfg);
58+
Properties props = new Properties();
59+
props.put( Environment.ORDER_INSERTS, "true" );
60+
props.put( Environment.STATEMENT_BATCH_SIZE, "10" );
61+
cfg.addProperties( props );
62+
}
63+
5764
@Test
5865
public void testOuterJoin() throws Exception {
5966
Session s = openSession();

0 commit comments

Comments
 (0)