Skip to content

Commit f691bd8

Browse files
committed
[hibernate#2108] Add test for StatelessSession insertAll in batch does not do batching
1 parent 761fd34 commit f691bd8

File tree

2 files changed

+343
-0
lines changed

2 files changed

+343
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: Apache-2.0
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate;
7+
8+
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
9+
import org.hibernate.cfg.Configuration;
10+
import org.hibernate.reactive.BaseReactiveTest;
11+
import org.hibernate.reactive.testing.SqlStatementTracker;
12+
13+
import org.junit.jupiter.api.BeforeEach;
14+
import org.junit.jupiter.api.Test;
15+
16+
import io.vertx.junit5.Timeout;
17+
import io.vertx.junit5.VertxTestContext;
18+
import jakarta.persistence.Entity;
19+
import jakarta.persistence.Id;
20+
import jakarta.persistence.Table;
21+
import java.util.List;
22+
import java.util.Objects;
23+
import java.util.Set;
24+
25+
import static java.util.concurrent.TimeUnit.MINUTES;
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
@Timeout(value = 10, timeUnit = MINUTES)
29+
public class ReactiveStatelessBatchSizeTest extends BaseReactiveTest {
30+
private static SqlStatementTracker sqlTracker;
31+
32+
private static final String PIG_ONE_NAME = "One";
33+
private static final String PIG_TWO_NAME = "Two";
34+
private static final String PIG_THREE_NAME = "Three";
35+
private static final String PIG_FOUR_NAME = "Four";
36+
private static final String PIG_FIVE_NAME = "Five";
37+
private static final String PIG_SIX_NAME = "Six";
38+
39+
private static final GuineaPig PIG_ONE = new GuineaPig( 11, PIG_ONE_NAME );
40+
private static final GuineaPig PIG_TWO = new GuineaPig( 22, PIG_TWO_NAME );
41+
private static final GuineaPig PIG_THREE = new GuineaPig( 33, PIG_THREE_NAME );
42+
private static final GuineaPig PIG_FOUR = new GuineaPig( 44, PIG_FOUR_NAME );
43+
private static final GuineaPig PIG_FIVE = new GuineaPig( 55, PIG_FIVE_NAME );
44+
private static final GuineaPig PIG_SIX = new GuineaPig( 66, PIG_SIX_NAME );
45+
46+
private static final GuineaPig[] PIGS = { PIG_ONE, PIG_TWO, PIG_THREE, PIG_FOUR, PIG_FIVE, PIG_SIX, };
47+
48+
@Override
49+
protected Set<Class<?>> annotatedEntities() {
50+
return Set.of( GuineaPig.class );
51+
}
52+
53+
@Override
54+
protected Configuration constructConfiguration() {
55+
Configuration configuration = super.constructConfiguration();
56+
57+
// Construct a tracker that collects query statements via the SqlStatementLogger framework.
58+
// Pass in configuration properties to hand off any actual logging properties
59+
sqlTracker = new SqlStatementTracker(
60+
ReactiveStatelessBatchSizeTest::filter,
61+
configuration.getProperties()
62+
);
63+
return configuration;
64+
}
65+
66+
@BeforeEach
67+
public void clearTracker() {
68+
sqlTracker.clear();
69+
}
70+
71+
@Override
72+
protected void addServices(StandardServiceRegistryBuilder builder) {
73+
sqlTracker.registerService( builder );
74+
}
75+
76+
private static boolean filter(String s) {
77+
String[] accepted = { "select", "insert ", "update ", "delete " };
78+
for ( String valid : accepted ) {
79+
if ( s.toLowerCase().startsWith( valid ) ) {
80+
return true;
81+
}
82+
}
83+
return false;
84+
}
85+
86+
@Test
87+
public void testMutinyBatchingInsert(VertxTestContext context) {
88+
test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) )
89+
.invoke( () -> {
90+
// We expect only one insert query
91+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
92+
// Parameters are different for different dbs, so we cannot do an exact match
93+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) )
94+
.matches( "insert into pig \\(name,id\\) values (.*)" );
95+
} )
96+
);
97+
}
98+
99+
@Test
100+
public void testStageBatchingInsert(VertxTestContext context) {
101+
test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) )
102+
.thenAccept( v -> {
103+
// We expect only one insert query
104+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
105+
// Parameters are different for different dbs, so we cannot do an exact match
106+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) )
107+
.matches( "insert into pig \\(name,id\\) values (.*)" );
108+
} )
109+
);
110+
}
111+
112+
@Test
113+
public void testMutinyBatchingDelete(VertxTestContext context) {
114+
test( context, getMutinySessionFactory().withStatelessTransaction( s -> s.insertAll( 10, PIGS ) )
115+
.invoke( () -> sqlTracker.clear() )
116+
.chain( v -> getMutinySessionFactory().withStatelessTransaction(s -> s
117+
.createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList()
118+
)
119+
.invoke( pigs -> sqlTracker.clear() )
120+
.chain( pigs -> getMutinySessionFactory().withStatelessTransaction(
121+
s ->
122+
s.deleteAll( 10, new GuineaPig[] { pigs.get( 0 ), pigs.get( 1 ) } )
123+
)
124+
)
125+
.invoke( () -> {
126+
// We expect only one insert query
127+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
128+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "delete from pig where id=.*" );
129+
} )
130+
.chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s
131+
.createQuery( "select p from GuineaPig p", GuineaPig.class )
132+
.getResultList()
133+
)
134+
.invoke( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) )
135+
) )
136+
);
137+
}
138+
139+
@Test
140+
public void testStageBatchingDelete(VertxTestContext context) {
141+
test( context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) )
142+
.thenAccept( v -> sqlTracker.clear() )
143+
.thenCompose( v -> getSessionFactory().withStatelessTransaction( s -> s
144+
.createQuery( "select p from GuineaPig p", GuineaPig.class ).getResultList()
145+
.thenCompose( pigs -> {
146+
sqlTracker.clear();
147+
return s.delete( 10, new GuineaPig[] { pigs.get( 0 ), pigs.get( 1 ) } );
148+
}
149+
) )
150+
.thenAccept( vo -> {
151+
// We expect only one insert query
152+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
153+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) )
154+
.matches( "delete from pig where id=.*" );
155+
} )
156+
.thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s
157+
.createQuery( "select p from GuineaPig p", GuineaPig.class )
158+
.getResultList()
159+
)
160+
.thenAccept( guineaPigs -> assertThat( guineaPigs.size() ).isEqualTo( 4 ) )
161+
) )
162+
);
163+
}
164+
165+
@Test
166+
public void testMutinyBatchingUpdate(VertxTestContext context) {
167+
final String pigOneUpdatedName = "One updated";
168+
final String pigTwoUpdatedName = "Two updated";
169+
test( context, getMutinySessionFactory().withStatelessTransaction( s -> s .insertAll( 10, PIGS ))
170+
.invoke( () -> sqlTracker.clear() )
171+
.chain( v -> getMutinySessionFactory().withStatelessTransaction( s -> s
172+
.createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class )
173+
.getResultList()
174+
.invoke( pigs -> sqlTracker.clear() )
175+
.chain( pigs -> {
176+
GuineaPig guineaPigOne = pigs.get( 0 );
177+
guineaPigOne.setName( pigOneUpdatedName );
178+
GuineaPig guineaPigTwo = pigs.get( 1 );
179+
guineaPigTwo.setName( pigTwoUpdatedName );
180+
return s.updateAll( 10, new GuineaPig[] { guineaPigOne, guineaPigTwo } );
181+
} )
182+
) )
183+
.invoke( () -> {
184+
// We expect only one insert query
185+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
186+
// Parameters are different for different dbs, so we cannot do an exact match
187+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" );
188+
} )
189+
.chain( () -> getMutinySessionFactory().withStatelessTransaction( s -> s
190+
.createQuery( "select p from GuineaPig p order by id", GuineaPig.class )
191+
.getResultList()
192+
.invoke( guineaPigs -> {
193+
checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName );
194+
} ) ) )
195+
);
196+
}
197+
198+
@Test
199+
public void testStageBatchingUpdate(VertxTestContext context) {
200+
final String pigOneUpdatedName = "One updated";
201+
final String pigTwoUpdatedName = "Two updated";
202+
test(context, getSessionFactory().withStatelessTransaction( s -> s.insert( 10, PIGS ) )
203+
.thenAccept( v -> sqlTracker.clear() )
204+
.thenCompose( v -> getSessionFactory().withStatelessTransaction(s -> s
205+
.createQuery( "select p from GuineaPig p order by p.id", GuineaPig.class )
206+
.getResultList()
207+
.thenApply( pigs -> {
208+
sqlTracker.clear();
209+
GuineaPig guineaPigOne = pigs.get( 0 );
210+
guineaPigOne.setName( pigOneUpdatedName );
211+
GuineaPig guineaPigTwo = pigs.get( 1 );
212+
guineaPigTwo.setName( pigTwoUpdatedName );
213+
return s.update(
214+
10,
215+
new GuineaPig[] { guineaPigOne, guineaPigTwo }
216+
);
217+
} )
218+
)
219+
.thenAccept( vo -> {
220+
// We expect only one insert query
221+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
222+
// Parameters are different for different dbs, so we cannot do an exact match
223+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) ).matches( "update pig set name=.* where id=.*" );
224+
} )
225+
.thenCompose( vo -> getSessionFactory().withStatelessTransaction( s -> s
226+
.createQuery( "select p from GuineaPig p order by id", GuineaPig.class )
227+
.getResultList()
228+
.thenAccept( guineaPigs -> {
229+
checkPigsAreCorrectlyUpdated( guineaPigs, pigOneUpdatedName, pigTwoUpdatedName );
230+
} )
231+
) ) )
232+
);
233+
}
234+
235+
private static void checkPigsAreCorrectlyUpdated(List<GuineaPig> guineaPigs, String pigOneUpdatedName, String pigTwoUpdatedName) {
236+
assertThat( guineaPigs.get( 0 ).getName() ).isEqualTo( pigOneUpdatedName );
237+
assertThat( guineaPigs.get( 1 ).getName() ).isEqualTo( pigTwoUpdatedName );
238+
assertThat( guineaPigs.get( 2 ).getName() ).isEqualTo( PIG_THREE_NAME );
239+
assertThat( guineaPigs.get( 3 ).getName() ).isEqualTo( PIG_FOUR_NAME );
240+
assertThat( guineaPigs.get( 4 ).getName() ).isEqualTo( PIG_FIVE_NAME );
241+
assertThat( guineaPigs.get( 5 ).getName() ).isEqualTo( PIG_SIX_NAME );
242+
}
243+
244+
@Entity(name = "GuineaPig")
245+
@Table(name = "pig")
246+
public static class GuineaPig {
247+
@Id
248+
private Integer id;
249+
private String name;
250+
251+
public GuineaPig() {
252+
}
253+
254+
public GuineaPig(Integer id, String name) {
255+
this.id = id;
256+
this.name = name;
257+
}
258+
259+
public Integer getId() {
260+
return id;
261+
}
262+
263+
public void setId(Integer id) {
264+
this.id = id;
265+
}
266+
267+
public String getName() {
268+
return name;
269+
}
270+
271+
public void setName(String name) {
272+
this.name = name;
273+
}
274+
275+
@Override
276+
public String toString() {
277+
return id + ": " + name;
278+
}
279+
280+
@Override
281+
public boolean equals(Object o) {
282+
if ( this == o ) {
283+
return true;
284+
}
285+
if ( o == null || getClass() != o.getClass() ) {
286+
return false;
287+
}
288+
GuineaPig guineaPig = (GuineaPig) o;
289+
return Objects.equals( name, guineaPig.name );
290+
}
291+
292+
@Override
293+
public int hashCode() {
294+
return Objects.hash( name );
295+
}
296+
}
297+
}

hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java

+46
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,52 @@ public void testBatching(VertxTestContext context) {
145145
);
146146
}
147147

148+
@Test
149+
public void testBatchingWithStateless(VertxTestContext context) {
150+
final GuineaPig[] pigs = {
151+
new GuineaPig( 11, "One" ),
152+
new GuineaPig( 22, "Two" ),
153+
new GuineaPig( 33, "Three" ),
154+
new GuineaPig( 44, "Four" ),
155+
new GuineaPig( 55, "Five" ),
156+
new GuineaPig( 66, "Six" ),
157+
};
158+
test( context, getMutinySessionFactory()
159+
.withStatelessTransaction( s -> s.insertAll( 10, pigs ) )
160+
.invoke( () -> {
161+
// We expect only one insert query
162+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 );
163+
// Parameters are different for different dbs, so we cannot do an exact match
164+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) )
165+
.matches("insert into pig \\(name,version,id\\) values (.*)" );
166+
sqlTracker.clear();
167+
} )
168+
);
169+
}
170+
171+
@Test
172+
public void testInsertAllWithStateless(VertxTestContext context) {
173+
final GuineaPig[] pigs = {
174+
new GuineaPig( 11, "One" ),
175+
new GuineaPig( 22, "Two" ),
176+
new GuineaPig( 33, "Three" ),
177+
new GuineaPig( 44, "Four" ),
178+
new GuineaPig( 55, "Five" ),
179+
new GuineaPig( 66, "Six" ),
180+
};
181+
test( context, getMutinySessionFactory()
182+
.withStatelessTransaction( s -> s.insertAll( pigs ) )
183+
.invoke( () -> {
184+
// We only 2 insert queries, batch size is 5 and we have 6 inserts
185+
assertThat( sqlTracker.getLoggedQueries() ).hasSize( 2 );
186+
// Parameters are different for different dbs, so we cannot do an exact match
187+
assertThat( sqlTracker.getLoggedQueries().get( 0 ) )
188+
.matches("insert into pig \\(name,version,id\\) values (.*)" );
189+
sqlTracker.clear();
190+
} )
191+
);
192+
}
193+
148194
@Test
149195
public void testBatchingConnection(VertxTestContext context) {
150196
test( context, openSession()

0 commit comments

Comments
 (0)