Skip to content

Commit cc18eb8

Browse files
committed
HHH-19420 - Support batch-size for collections in mapping.xml
1 parent 048d427 commit cc18eb8

File tree

9 files changed

+190
-227
lines changed

9 files changed

+190
-227
lines changed

hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/spi/JaxbPluralAttribute.java

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public interface JaxbPluralAttribute extends JaxbPersistentAttribute, JaxbLockab
2626
JaxbCollectionIdImpl getCollectionId();
2727
void setCollectionId(JaxbCollectionIdImpl id);
2828

29+
Integer getBatchSize();
30+
void setBatchSize(Integer size);
2931

3032
LimitedCollectionClassification getClassification();
3133
void setClassification(LimitedCollectionClassification value);

hibernate-core/src/main/java/org/hibernate/boot/models/xml/internal/attr/CommonPluralAttributeProcessing.java

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.boot.jaxb.mapping.spi.JaxbPluralFetchModeImpl;
1212
import org.hibernate.boot.models.HibernateAnnotations;
1313
import org.hibernate.boot.models.JpaAnnotations;
14+
import org.hibernate.boot.models.annotations.internal.BatchSizeAnnotation;
1415
import org.hibernate.boot.models.annotations.internal.FetchAnnotation;
1516
import org.hibernate.boot.models.annotations.internal.MapKeyClassJpaAnnotation;
1617
import org.hibernate.boot.models.annotations.internal.MapKeyColumnJpaAnnotation;
@@ -62,6 +63,14 @@ public static void applyPluralAttributeStructure(
6263
}
6364
}
6465

66+
if ( jaxbPluralAttribute.getBatchSize() != null ) {
67+
final BatchSizeAnnotation batchSizeAnnotation = (BatchSizeAnnotation) memberDetails.applyAnnotationUsage(
68+
HibernateAnnotations.BATCH_SIZE,
69+
buildingContext
70+
);
71+
batchSizeAnnotation.size( jaxbPluralAttribute.getBatchSize() );
72+
}
73+
6574
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6675
// collection-structure
6776

hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-7.0.xsd

+3
Original file line numberDiff line numberDiff line change
@@ -3313,6 +3313,9 @@
33133313
</xsd:sequence>
33143314
</xsd:choice>
33153315

3316+
<!-- @BatchSize -->
3317+
<xsd:element name="batch-size" type="xsd:int" minOccurs="0"/>
3318+
33163319
<xsd:element name="sql-restriction" type="xsd:string" minOccurs="0"/>
33173320

33183321
<xsd:element name="sql-insert" type="orm:custom-sql" minOccurs="0"/>

hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/BatchFetchTest.java

+102-150
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
*/
55
package org.hibernate.orm.test.batchfetch;
66

7-
import java.util.ArrayList;
8-
import java.util.Iterator;
9-
import java.util.List;
10-
117
import org.hibernate.Hibernate;
12-
import org.hibernate.cfg.AvailableSettings;
13-
148
import org.hibernate.testing.orm.junit.DomainModel;
159
import org.hibernate.testing.orm.junit.ServiceRegistry;
1610
import org.hibernate.testing.orm.junit.SessionFactory;
@@ -19,6 +13,12 @@
1913
import org.junit.jupiter.api.AfterEach;
2014
import org.junit.jupiter.api.Test;
2115

16+
import java.util.ArrayList;
17+
import java.util.Iterator;
18+
import java.util.List;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.hibernate.cfg.CacheSettings.USE_SECOND_LEVEL_CACHE;
2222
import static org.junit.jupiter.api.Assertions.assertEquals;
2323
import static org.junit.jupiter.api.Assertions.assertFalse;
2424
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,166 +27,118 @@
2727
/**
2828
* @author Gavin King
2929
*/
30+
@SuppressWarnings("JUnitMalformedDeclaration")
3031
@DomainModel(
31-
xmlMappings = "org/hibernate/orm/test/batchfetch/ProductLine.hbm.xml",
32+
xmlMappings = "org/hibernate/orm/test/batchfetch/ProductLine.xml",
3233
annotatedClasses = BatchLoadableEntity.class
3334
)
34-
@SessionFactory(
35-
generateStatistics = true
36-
)
37-
@ServiceRegistry(
38-
settings = {
39-
@Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "false")
40-
}
41-
)
35+
@SessionFactory(generateStatistics = true)
36+
@ServiceRegistry(settings = @Setting(name = USE_SECOND_LEVEL_CACHE, value = "false"))
4237
public class BatchFetchTest {
4338

4439
@SuppressWarnings("unchecked")
4540
@Test
4641
public void testBatchFetch(SessionFactoryScope scope) {
47-
ProductLine ossProductLine = new ProductLine();
48-
Model hibernateModel = new Model( ossProductLine );
49-
scope.inTransaction(
50-
session -> {
51-
ProductLine cars = new ProductLine();
52-
cars.setDescription( "Cars" );
53-
Model monaro = new Model( cars );
54-
monaro.setName( "monaro" );
55-
monaro.setDescription( "Holden Monaro" );
56-
Model hsv = new Model( cars );
57-
hsv.setName( "hsv" );
58-
hsv.setDescription( "Holden Commodore HSV" );
59-
session.persist( cars );
60-
61-
ossProductLine.setDescription( "OSS" );
62-
Model jboss = new Model( ossProductLine );
63-
jboss.setName( "JBoss" );
64-
jboss.setDescription( "JBoss Application Server" );
65-
66-
hibernateModel.setName( "Hibernate" );
67-
hibernateModel.setDescription( "Hibernate" );
68-
Model cache = new Model( ossProductLine );
69-
cache.setName( "JBossCache" );
70-
cache.setDescription( "JBoss TreeCache" );
71-
session.persist( ossProductLine );
72-
}
73-
);
74-
75-
scope.getSessionFactory().getCache().evictEntityData( Model.class );
76-
scope.getSessionFactory().getCache().evictEntityData( ProductLine.class );
77-
78-
scope.inTransaction(
79-
session -> {
80-
List<ProductLine> list = session.createQuery( "from ProductLine pl order by pl.description" )
81-
.list();
82-
ProductLine cars = list.get( 0 );
83-
ProductLine oss = list.get( 1 );
84-
assertFalse( Hibernate.isInitialized( cars.getModels() ) );
85-
assertFalse( Hibernate.isInitialized( oss.getModels() ) );
86-
assertEquals( 2, cars.getModels().size() ); //fetch both collections
87-
assertTrue( Hibernate.isInitialized( cars.getModels() ) );
88-
assertTrue( Hibernate.isInitialized( oss.getModels() ) );
89-
90-
session.clear();
91-
92-
List<Model> models = session.createQuery( "from Model m" ).list();
93-
Model hibernate = session.get( Model.class, hibernateModel.getId() );
94-
hibernate.getProductLine().getId();
95-
for ( Model aList : models ) {
96-
assertFalse( Hibernate.isInitialized( aList.getProductLine() ) );
97-
}
98-
assertEquals( hibernate.getProductLine().getDescription(), "OSS" ); //fetch both productlines
99-
100-
session.clear();
101-
102-
Iterator<Model> iter = session.createQuery( "from Model" ).list().iterator();
103-
models = new ArrayList();
104-
while ( iter.hasNext() ) {
105-
models.add( iter.next() );
106-
}
107-
Model m = models.get( 0 );
108-
m.getDescription(); //fetch a batch of 4
109-
110-
session.clear();
111-
112-
list = session.createQuery( "from ProductLine" ).list();
113-
ProductLine pl = list.get( 0 );
114-
ProductLine pl2 = list.get( 1 );
115-
session.evict( pl2 );
116-
pl.getModels().size(); //fetch just one collection! (how can we write an assertion for that??)
117-
}
118-
);
119-
120-
scope.inTransaction(
121-
session -> {
122-
List<ProductLine> list = session.createQuery( "from ProductLine pl order by pl.description" )
123-
.list();
124-
ProductLine cars = list.get( 0 );
125-
ProductLine oss = list.get( 1 );
126-
assertEquals( cars.getModels().size(), 2 );
127-
assertEquals( oss.getModels().size(), 3 );
128-
session.remove( cars );
129-
session.remove( oss );
130-
}
131-
);
42+
ProductLine ossProductLine = new ProductLine( "OSS" );
43+
Model hibernateModel = new Model( "Hibernate", "Hibernate", ossProductLine );
44+
scope.inTransaction( (session) -> {
45+
ProductLine cars = new ProductLine( "Cars" );
46+
new Model( "monaro", "Holden Monaro", cars );
47+
new Model( "hsv", "Holden Commodore HSV", cars );
48+
session.persist( cars );
49+
50+
ossProductLine.setDescription( "OSS" );
51+
new Model( "JBoss", "JBoss Application Server", ossProductLine );
52+
new Model( "JBossCache", "JBoss TreeCache", ossProductLine );
53+
session.persist( ossProductLine );
54+
} );
55+
56+
scope.inTransaction( (session) -> {
57+
List<ProductLine> list = session.createQuery( "from ProductLine pl order by pl.description" ).list();
58+
ProductLine cars = list.get( 0 );
59+
ProductLine oss = list.get( 1 );
60+
assertFalse( Hibernate.isInitialized( cars.getModels() ) );
61+
assertFalse( Hibernate.isInitialized( oss.getModels() ) );
62+
assertEquals( 2, cars.getModels().size() ); //fetch both collections
63+
assertTrue( Hibernate.isInitialized( cars.getModels() ) );
64+
assertTrue( Hibernate.isInitialized( oss.getModels() ) );
65+
} );
66+
67+
scope.inTransaction( (session) -> {
68+
List<Model> models = session.createQuery( "from Model m" ).list();
69+
Model hibernate = session.find( Model.class, hibernateModel.getId() );
70+
hibernate.getProductLine().getId();
71+
for ( Model aList : models ) {
72+
assertFalse( Hibernate.isInitialized( aList.getProductLine() ) );
73+
}
74+
//fetch both product lines
75+
assertThat( hibernate.getProductLine().getDescription() ).isEqualTo( "OSS" );
76+
} );
77+
78+
scope.inTransaction( (session) -> {
79+
Iterator<Model> iter = session.createQuery( "from Model" ).list().iterator();
80+
ArrayList<Model> models = new ArrayList<>();
81+
while ( iter.hasNext() ) {
82+
models.add( iter.next() );
83+
}
84+
Model m = models.get( 0 );
85+
m.getDescription(); //fetch a batch of 4
86+
87+
session.clear();
88+
89+
List<ProductLine> list = session.createQuery( "from ProductLine" ).list();
90+
ProductLine pl = list.get( 0 );
91+
ProductLine pl2 = list.get( 1 );
92+
session.evict( pl2 );
93+
pl.getModels().size(); //fetch just one collection! (how can we write an assertion for that??)
94+
} );
95+
96+
scope.inTransaction( (session) -> {
97+
List<ProductLine> list = session.createQuery( "from ProductLine pl order by pl.description" ).list();
98+
ProductLine cars = list.get( 0 );
99+
ProductLine oss = list.get( 1 );
100+
assertThat( cars.getModels().size() ).isEqualTo( 2 );
101+
assertThat( oss.getModels().size() ).isEqualTo( 3 );
102+
} );
132103
}
133104

134105
@Test
135-
@SuppressWarnings("unchecked")
136106
public void testBatchFetch2(SessionFactoryScope scope) {
137107
int size = 32 + 14;
138-
scope.inTransaction(
139-
session -> {
140-
for ( int i = 0; i < size; i++ ) {
141-
session.persist( new BatchLoadableEntity( i ) );
142-
}
143-
}
144-
);
145-
146-
scope.inTransaction(
147-
session -> {
148-
// load them all as proxies
149-
for ( int i = 0; i < size; i++ ) {
150-
BatchLoadableEntity entity = session.getReference( BatchLoadableEntity.class, i );
151-
assertFalse( Hibernate.isInitialized( entity ) );
152-
}
153-
scope.getSessionFactory().getStatistics().clear();
154-
// now start initializing them...
155-
for ( int i = 0; i < size; i++ ) {
156-
BatchLoadableEntity entity = session.getReference( BatchLoadableEntity.class, i );
157-
Hibernate.initialize( entity );
158-
assertTrue( Hibernate.isInitialized( entity ) );
159-
}
160-
// so at this point, all entities are initialized. see how many fetches were performed.
161-
final int expectedFetchCount;
162-
// if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.LEGACY ) {
163-
// expectedFetchCount = 3; // (32 + 10 + 4)
164-
// }
165-
// else if ( sessionFactory().getSettings().getBatchFetchStyle() == BatchFetchStyle.DYNAMIC ) {
166-
// expectedFetchCount = 2; // (32 + 14) : because we limited batch-size to 32
167-
// }
168-
// else {
169-
// PADDED
170-
expectedFetchCount = 2; // (32 + 16*) with the 16 being padded
171-
// }
172-
assertEquals(
173-
expectedFetchCount,
174-
scope.getSessionFactory().getStatistics()
175-
.getEntityStatistics( BatchLoadableEntity.class.getName() )
176-
.getFetchCount()
177-
);
178-
}
179-
);
108+
scope.inTransaction( (session) -> {
109+
for ( int i = 0; i < size; i++ ) {
110+
session.persist( new BatchLoadableEntity( i ) );
111+
}
112+
} );
113+
114+
scope.inTransaction( (session) -> {
115+
// load them all as proxies
116+
for ( int i = 0; i < size; i++ ) {
117+
BatchLoadableEntity entity = session.getReference( BatchLoadableEntity.class, i );
118+
assertFalse( Hibernate.isInitialized( entity ) );
119+
}
120+
scope.getSessionFactory().getStatistics().clear();
121+
// now start initializing them...
122+
for ( int i = 0; i < size; i++ ) {
123+
BatchLoadableEntity entity = session.getReference( BatchLoadableEntity.class, i );
124+
Hibernate.initialize( entity );
125+
assertTrue( Hibernate.isInitialized( entity ) );
126+
}
127+
// so at this point, all entities are initialized. see how many fetches were performed.
128+
final int expectedFetchCount;
129+
expectedFetchCount = 2; // (32 + 16*) with the 16 being padded
130+
131+
assertEquals(
132+
expectedFetchCount,
133+
scope.getSessionFactory().getStatistics()
134+
.getEntityStatistics( BatchLoadableEntity.class.getName() )
135+
.getFetchCount()
136+
);
137+
} );
180138
}
181139

182140
@AfterEach
183141
public void tearDown(SessionFactoryScope scope) {
184-
scope.inTransaction(
185-
session -> {
186-
session.createQuery( "delete BatchLoadableEntity" ).executeUpdate();
187-
session.createQuery( "delete Model" ).executeUpdate();
188-
session.createQuery( "delete ProductLine" ).executeUpdate();
189-
}
190-
);
142+
scope.dropData();
191143
}
192144
}

hibernate-core/src/test/java/org/hibernate/orm/test/batchfetch/Model.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
* @author Gavin King
1010
*/
1111
public class Model {
12-
private String id;
12+
private Integer id;
1313
private String name;
1414
private String description;
1515
private ProductLine productLine;
1616

17-
Model() {}
17+
Model() {
18+
}
1819

19-
public Model(ProductLine pl) {
20-
this.productLine = pl;
21-
pl.getModels().add(this);
20+
public Model(String name, String description, ProductLine productLine) {
21+
this.name = name;
22+
this.description = description;
23+
this.productLine = productLine;
24+
productLine.getModels().add(this);
2225
}
2326

2427
public String getDescription() {
@@ -27,10 +30,10 @@ public String getDescription() {
2730
public void setDescription(String description) {
2831
this.description = description;
2932
}
30-
public String getId() {
33+
public Integer getId() {
3134
return id;
3235
}
33-
public void setId(String id) {
36+
public void setId(Integer id) {
3437
this.id = id;
3538
}
3639
public String getName() {

0 commit comments

Comments
 (0)