6
6
*/
7
7
package org .hibernate .test .querycache ;
8
8
9
+ import java .io .Serializable ;
9
10
import java .util .ArrayList ;
10
11
import java .util .List ;
11
12
import java .util .Map ;
13
+ import java .util .concurrent .Callable ;
14
+ import java .util .concurrent .CountDownLatch ;
15
+ import java .util .concurrent .ExecutionException ;
16
+ import java .util .concurrent .ExecutorService ;
17
+ import java .util .concurrent .Executors ;
18
+ import java .util .concurrent .Future ;
12
19
13
20
import org .hibernate .Criteria ;
21
+ import org .hibernate .EmptyInterceptor ;
14
22
import org .hibernate .Hibernate ;
15
23
import org .hibernate .Query ;
16
24
import org .hibernate .SQLQuery ;
17
25
import org .hibernate .Session ;
26
+ import org .hibernate .SessionBuilder ;
18
27
import org .hibernate .Transaction ;
19
28
import org .hibernate .cfg .AvailableSettings ;
20
29
import org .hibernate .criterion .Restrictions ;
26
35
import org .hibernate .testing .RequiresDialectFeature ;
27
36
import org .hibernate .testing .TestForIssue ;
28
37
import org .hibernate .testing .junit4 .BaseNonConfigCoreFunctionalTestCase ;
38
+ import org .hibernate .type .Type ;
29
39
import org .junit .Test ;
30
40
31
41
import static org .junit .Assert .assertEquals ;
39
49
public class QueryCacheTest extends BaseNonConfigCoreFunctionalTestCase {
40
50
41
51
private static final CompositeKey PK = new CompositeKey (1 , 2 );
52
+ private static final ExecutorService executor = Executors .newFixedThreadPool (4 );
42
53
43
54
@ Override
44
55
public String [] getMappings () {
@@ -63,6 +74,17 @@ protected void addSettings(Map settings) {
63
74
settings .put ( AvailableSettings .GENERATE_STATISTICS , "true" );
64
75
}
65
76
77
+ @ Override
78
+ protected void shutDown () {
79
+ super .shutDown ();
80
+ executor .shutdown ();
81
+ }
82
+
83
+ @ Override
84
+ protected boolean isCleanupTestDataRequired () {
85
+ return true ;
86
+ }
87
+
66
88
@ Override
67
89
protected String getCacheConcurrencyStrategy () {
68
90
return "nonstrict-read-write" ;
@@ -529,5 +551,114 @@ public void testScalarSQLQuery() {
529
551
// assertEquals(1, query.getResultList().size());
530
552
// }
531
553
554
+ @ Test
555
+ @ TestForIssue (jiraKey = "HHH-9962" )
556
+ /* Test courtesy of Giambattista Bloisi */
557
+ public void testDelayedLoad () throws InterruptedException , ExecutionException {
558
+ DelayLoadOperations interceptor = new DelayLoadOperations ();
559
+ final SessionBuilder sessionBuilder = sessionFactory ().withOptions ().interceptor (interceptor );
560
+ Item item1 = new Item ();
561
+ item1 .setName ("Item1" );
562
+ item1 .setDescription ("Washington" );
563
+ Session s1 = sessionBuilder .openSession ();
564
+ Transaction tx1 = s1 .beginTransaction ();
565
+ s1 .persist (item1 );
566
+ tx1 .commit ();
567
+ s1 .close ();
568
+
569
+ Item item2 = new Item ();
570
+ item2 .setName ("Item2" );
571
+ item2 .setDescription ("Chicago" );
572
+ Session s2 = sessionBuilder .openSession ();
573
+ Transaction tx2 = s2 .beginTransaction ();
574
+ s2 .persist (item2 );
575
+ tx2 .commit ();
576
+ s2 .close ();
577
+
578
+ interceptor .blockOnLoad ();
579
+
580
+ Future <Item > fetchedItem = executor .submit (new Callable <Item >() {
581
+ public Item call () throws Exception {
582
+ return findByDescription (sessionBuilder , "Washington" );
583
+ }
584
+ });
585
+
586
+ // wait for the onLoad listener to be called
587
+ interceptor .waitOnLoad ();
588
+
589
+ Session s3 = sessionBuilder .openSession ();
590
+ Transaction tx3 = s3 .beginTransaction ();
591
+ item1 .setDescription ("New York" );
592
+ item2 .setDescription ("Washington" );
593
+ s3 .update (item1 );
594
+ s3 .update (item2 );
595
+ tx3 .commit ();
596
+ s3 .close ();
597
+
598
+ interceptor .unblockOnLoad ();
599
+
600
+ // the concurrent query was executed before the data was amended so
601
+ // let's expect "Item1" to be returned as living in Washington
602
+ Item fetched = fetchedItem .get ();
603
+ assertEquals ("Item1" , fetched .getName ());
604
+
605
+ // Query again: now "Item2" is expected to live in Washington
606
+ fetched = findByDescription (sessionBuilder , "Washington" );
607
+ assertEquals ("Item2" , fetched .getName ());
608
+ }
609
+
610
+ protected Item findByDescription (SessionBuilder sessionBuilder , final String description ) {
611
+ Session s = sessionBuilder .openSession ();
612
+ try {
613
+ return (Item ) s .createCriteria (Item .class )
614
+ .setCacheable (true )
615
+ .setReadOnly (true )
616
+ .add (Restrictions .eq ("description" , description ))
617
+ .uniqueResult ();
618
+
619
+ } finally {
620
+ s .close ();
621
+ }
622
+ }
623
+
624
+ public class DelayLoadOperations extends EmptyInterceptor {
625
+
626
+ private volatile CountDownLatch blockLatch ;
627
+ private volatile CountDownLatch waitLatch ;
628
+
629
+ @ Override
630
+ public boolean onLoad (Object entity , Serializable id , Object [] state , String [] propertyNames , Type [] types ) {
631
+ // Synchronize load and update activities
632
+ try {
633
+ if (waitLatch != null ) {
634
+ waitLatch .countDown ();
635
+ waitLatch = null ;
636
+ }
637
+ if (blockLatch != null ) {
638
+ blockLatch .await ();
639
+ blockLatch = null ;
640
+ }
641
+ } catch (InterruptedException e ) {
642
+ Thread .currentThread ().interrupt ();
643
+ throw new RuntimeException (e );
644
+ }
645
+ return true ;
646
+ }
647
+
648
+ public void blockOnLoad () {
649
+ blockLatch = new CountDownLatch (1 );
650
+ waitLatch = new CountDownLatch (1 );
651
+ }
652
+
653
+ public void waitOnLoad () throws InterruptedException {
654
+ waitLatch .await ();
655
+ }
656
+
657
+ public void unblockOnLoad () {
658
+ if (blockLatch != null ) {
659
+ blockLatch .countDown ();
660
+ }
661
+ }
662
+ }
532
663
}
533
664
0 commit comments