3
3
import org .hibernate .PessimisticLockException ;
4
4
import org .hibernate .Session ;
5
5
import org .hibernate .StaleStateException ;
6
+ import org .hibernate .cache .infinispan .impl .BaseTransactionalDataRegion ;
6
7
import org .hibernate .cache .infinispan .util .Caches ;
7
8
import org .hibernate .cache .infinispan .util .VersionedEntry ;
8
9
import org .hibernate .cache .spi .entry .CacheEntry ;
9
10
import org .hibernate .engine .spi .SessionImplementor ;
10
11
import org .hibernate .test .cache .infinispan .functional .entities .Item ;
12
+ import org .hibernate .test .cache .infinispan .functional .entities .OtherItem ;
13
+ import org .infinispan .AdvancedCache ;
14
+ import org .infinispan .commands .write .PutKeyValueCommand ;
11
15
import org .infinispan .commons .util .ByRef ;
16
+ import org .infinispan .context .Flag ;
17
+ import org .infinispan .context .InvocationContext ;
18
+ import org .infinispan .interceptors .base .BaseCustomInterceptor ;
12
19
import org .junit .Test ;
13
20
14
21
import javax .transaction .Synchronization ;
17
24
import java .util .Collections ;
18
25
import java .util .List ;
19
26
import java .util .Map ;
27
+ import java .util .Set ;
20
28
import java .util .concurrent .CountDownLatch ;
21
29
import java .util .concurrent .CyclicBarrier ;
22
30
import java .util .concurrent .Future ;
23
31
import java .util .concurrent .TimeUnit ;
32
+ import java .util .concurrent .atomic .AtomicBoolean ;
24
33
import java .util .concurrent .atomic .AtomicReference ;
25
34
import java .util .function .BiConsumer ;
26
35
@@ -41,6 +50,11 @@ public List<Object[]> getParameters() {
41
50
return Arrays .asList (NONSTRICT_REPLICATED , NONSTRICT_DISTRIBUTED );
42
51
}
43
52
53
+ @ Override
54
+ protected boolean getUseQueryCache () {
55
+ return false ;
56
+ }
57
+
44
58
@ Test
45
59
public void testTwoRemoves () throws Exception {
46
60
CyclicBarrier loadBarrier = new CyclicBarrier (2 );
@@ -220,6 +234,98 @@ public void testEvictUpdateExpiration() throws Exception {
220
234
assertSingleCacheEntry ();
221
235
}
222
236
237
+ @ Test
238
+ public void testCollectionUpdate () throws Exception {
239
+ // the first insert puts VersionedEntry(null, null, timestamp), so we have to wait a while to cache the entry
240
+ TIME_SERVICE .advance (1 );
241
+
242
+ withTxSession (s -> {
243
+ Item item = s .load (Item .class , itemId );
244
+ OtherItem otherItem = new OtherItem ();
245
+ otherItem .setName ("Other 1" );
246
+ s .persist (otherItem );
247
+ item .addOtherItem (otherItem );
248
+ });
249
+ withTxSession (s -> {
250
+ Item item = s .load (Item .class , itemId );
251
+ Set <OtherItem > otherItems = item .getOtherItems ();
252
+ assertFalse (otherItems .isEmpty ());
253
+ otherItems .remove (otherItems .iterator ().next ());
254
+ });
255
+
256
+ AdvancedCache collectionCache = ((BaseTransactionalDataRegion ) sessionFactory ().getSecondLevelCacheRegion (Item .class .getName () + ".otherItems" )).getCache ();
257
+ CountDownLatch putFromLoadLatch = new CountDownLatch (1 );
258
+ AtomicBoolean committing = new AtomicBoolean (false );
259
+ CollectionUpdateTestInterceptor collectionUpdateTestInterceptor = new CollectionUpdateTestInterceptor (putFromLoadLatch );
260
+ AnotherCollectionUpdateTestInterceptor anotherInterceptor = new AnotherCollectionUpdateTestInterceptor (putFromLoadLatch , committing );
261
+ collectionCache .addInterceptor (collectionUpdateTestInterceptor , collectionCache .getInterceptorChain ().size () - 1 );
262
+ collectionCache .addInterceptor (anotherInterceptor , 0 );
263
+
264
+ TIME_SERVICE .advance (1 );
265
+ Future <Boolean > addFuture = executor .submit (() -> withTxSessionApply (s -> {
266
+ collectionUpdateTestInterceptor .updateLatch .await ();
267
+ Item item = s .load (Item .class , itemId );
268
+ OtherItem otherItem = new OtherItem ();
269
+ otherItem .setName ("Other 2" );
270
+ s .persist (otherItem );
271
+ item .addOtherItem (otherItem );
272
+ committing .set (true );
273
+ return true ;
274
+ }));
275
+
276
+ Future <Boolean > readFuture = executor .submit (() -> withTxSessionApply (s -> {
277
+ Item item = s .load (Item .class , itemId );
278
+ assertTrue (item .getOtherItems ().isEmpty ());
279
+ return true ;
280
+ }));
281
+
282
+ addFuture .get ();
283
+ readFuture .get ();
284
+ collectionCache .removeInterceptor (CollectionUpdateTestInterceptor .class );
285
+ collectionCache .removeInterceptor (AnotherCollectionUpdateTestInterceptor .class );
286
+
287
+ withTxSession (s -> assertFalse (s .load (Item .class , itemId ).getOtherItems ().isEmpty ()));
288
+ }
289
+
290
+ private class CollectionUpdateTestInterceptor extends BaseCustomInterceptor {
291
+ final AtomicBoolean firstPutFromLoad = new AtomicBoolean (true );
292
+ final CountDownLatch putFromLoadLatch ;
293
+ final CountDownLatch updateLatch = new CountDownLatch (1 );
294
+
295
+ public CollectionUpdateTestInterceptor (CountDownLatch putFromLoadLatch ) {
296
+ this .putFromLoadLatch = putFromLoadLatch ;
297
+ }
298
+
299
+ @ Override
300
+ public Object visitPutKeyValueCommand (InvocationContext ctx , PutKeyValueCommand command ) throws Throwable {
301
+ if (command .hasFlag (Flag .ZERO_LOCK_ACQUISITION_TIMEOUT )) {
302
+ if (firstPutFromLoad .compareAndSet (true , false )) {
303
+ updateLatch .countDown ();
304
+ putFromLoadLatch .await ();
305
+ }
306
+ }
307
+ return super .visitPutKeyValueCommand (ctx , command );
308
+ }
309
+ }
310
+
311
+ private class AnotherCollectionUpdateTestInterceptor extends BaseCustomInterceptor {
312
+ final CountDownLatch putFromLoadLatch ;
313
+ final AtomicBoolean committing ;
314
+
315
+ public AnotherCollectionUpdateTestInterceptor (CountDownLatch putFromLoadLatch , AtomicBoolean committing ) {
316
+ this .putFromLoadLatch = putFromLoadLatch ;
317
+ this .committing = committing ;
318
+ }
319
+
320
+ @ Override
321
+ public Object visitPutKeyValueCommand (InvocationContext ctx , PutKeyValueCommand command ) throws Throwable {
322
+ if (committing .get () && !command .hasFlag (Flag .ZERO_LOCK_ACQUISITION_TIMEOUT )) {
323
+ putFromLoadLatch .countDown ();
324
+ }
325
+ return super .visitPutKeyValueCommand (ctx , command );
326
+ }
327
+ }
328
+
223
329
protected void assertSingleEmpty () {
224
330
Map contents = Caches .entrySet (entityCache ).toMap ();
225
331
Object value ;
0 commit comments