Skip to content

Commit 1df0711

Browse files
rica-v3rica.rdo
andauthored
[ISSUE 7] ReadWriteAccessDomainDataStorageAccess checks transaction status for cacheEvict on cachePut (#9)
* Add test cases in ReadWriteAccessDomainDataRegionGroupTest * Modify loglevel in HibernateArcusStorageAccess.contains * Modify ReadWriteAccessDomainDataStorageAccess.putIntoCache to check tx status not lock * Fix a test in ReadWriteAccessDomainDataRegionGroupTest * Add tests for multiple queries in a single transaction in TransactionAccessDomainDataRegionGroupTest * Remove unused field in ReadWriteAccessDomainDataRegionGroupTest * Modify DomainDataHibernateArcusStorageAccess not evict its own region item if read-write access * Override evictData in ReadWriteAccessDomainDataStorageAccess * Remove unnecessary system log * Add null check test verification in testCacheEvictOnCachePut_whenDomainRegionOneEntityIsDeleted\ AndDomainRegionTwoIsUpdatedInTheSameTransaction_thenDomainRegionTwoShouldBeCacheMiss * Modify ReadWriteAccessDomainDataStorageAccess.evictData by reorder of conditions * Modify DomainDataHibernateArcusStorageAccess.evictData by reorder of conditions Co-authored-by: rica.rdo <rica.rdo@kakaopaycorp.com>
1 parent 6e94dd6 commit 1df0711

File tree

6 files changed

+249
-24
lines changed

6 files changed

+249
-24
lines changed

build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ dependencies {
2525
testCompile group: 'org.hibernate', name: 'hibernate-testing', version: '5.4.27.Final'
2626
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.18.1'
2727
testCompile group: 'com.h2database', name: 'h2', version: '1.4.200'
28-
29-
implementation group: 'com.google.guava', name: 'guava', version: '30.1-jre'
3028
}
3129
tasks.test {
3230
useJUnit()

src/main/java/com/devookim/hibernatearcus/storage/DomainDataHibernateArcusStorageAccess.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
@Slf4j
1616
public class DomainDataHibernateArcusStorageAccess extends HibernateArcusStorageAccess {
17-
private static final HashMap<String, DomainDataHibernateArcusStorageAccess> domainDataStorageAccesses = new HashMap<>();
17+
protected static final HashMap<String, DomainDataHibernateArcusStorageAccess> domainDataStorageAccesses = new HashMap<>();
1818

1919
private final HibernateArcusStorageConfig storageAccessConfig;
2020
public final String entityClassName;
@@ -63,19 +63,19 @@ && contains(key)) {
6363

6464
@Override
6565
public void evictData(Object key) {
66-
if (storageAccessConfig.regionGroupOnCacheEvict.contains(super.CACHE_REGION) && storageAccessConfig.enableCacheEvictOnCachePut) {
66+
if (storageAccessConfig.enableCacheEvictOnCachePut
67+
&& storageAccessConfig.regionGroupOnCacheEvict.contains(CACHE_REGION)) {
6768
String id = key.toString().split("#")[1];
6869
log.debug("regionGroupOnCacheEvict contains region: {}, id: {}", CACHE_REGION, id);
69-
domainDataStorageAccesses.forEach((s, storageAccess) -> {
70-
storageAccess.evictDataOnRegionGroupCacheEvict(new HibernateArcusCacheKeysFactory.EntityKey(storageAccess.entityClassName, id));
71-
});
70+
domainDataStorageAccesses.forEach((region, storageAccess) ->
71+
storageAccess.evictDataOnRegionGroupCacheEvict(new HibernateArcusCacheKeysFactory.EntityKey(storageAccess.entityClassName, id)));
7272
} else {
7373
super.evictData(key);
7474
}
7575
}
7676

7777
public void evictDataOnRegionGroupCacheEvict(Object key) {
78-
if (storageAccessConfig.regionGroupOnCacheEvict.contains(super.CACHE_REGION)) {
78+
if (storageAccessConfig.regionGroupOnCacheEvict.contains(CACHE_REGION)) {
7979
log.debug("cacheEvict {} by regionGroupOnCacheEvict", key);
8080
super.evictData(key);
8181
}

src/main/java/com/devookim/hibernatearcus/storage/HibernateArcusStorageAccess.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public boolean contains(Object key) {
7272
String generatedKey = generateKey(key);
7373
try {
7474
Object result = arcusClientFactory.getClientPool().get(generatedKey);
75-
log.info("containKey for {} contains: {}", generatedKey, result != null);
75+
log.trace("containKey for {} contains: {}", generatedKey, result != null);
7676
return result != null;
7777
} catch (Exception e) {
7878
log.error("fallbackEnabled: {} key: {} errorMsg: {}", arcusClientFactory.fallbackEnabled, generatedKey, e.getMessage());

src/main/java/com/devookim/hibernatearcus/storage/ReadWriteAccessDomainDataStorageAccess.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,51 @@
22

33
import com.devookim.hibernatearcus.client.HibernateArcusClientFactory;
44
import com.devookim.hibernatearcus.config.HibernateArcusStorageConfig;
5-
import com.google.common.cache.Cache;
6-
import com.google.common.cache.CacheBuilder;
5+
import com.devookim.hibernatearcus.factory.HibernateArcusCacheKeysFactory;
6+
import lombok.extern.slf4j.Slf4j;
77
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
88
import org.hibernate.cache.spi.support.AbstractReadWriteAccess;
99
import org.hibernate.engine.spi.SharedSessionContractImplementor;
10+
import org.hibernate.resource.transaction.spi.TransactionStatus;
1011

11-
import java.time.Duration;
12-
12+
@Slf4j
1313
public class ReadWriteAccessDomainDataStorageAccess extends DomainDataHibernateArcusStorageAccess {
14-
private final Cache<AbstractReadWriteAccess.SoftLockImpl, Object> readWriteAccessLocks;
1514
private final HibernateArcusStorageConfig storageAccessConfig;
1615

1716
public ReadWriteAccessDomainDataStorageAccess(HibernateArcusClientFactory arcusClientFactory, String regionName, HibernateArcusStorageConfig storageAccessConfig, DomainDataRegionConfig regionConfig) {
1817
super(arcusClientFactory, regionName, storageAccessConfig, regionConfig);
1918
this.storageAccessConfig = storageAccessConfig;
20-
readWriteAccessLocks = CacheBuilder.newBuilder()
21-
.expireAfterWrite(Duration.ofSeconds(10))
22-
.maximumSize(10000)
23-
.concurrencyLevel(1000)
24-
.build();
2519
}
2620

2721
@Override
2822
public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) {
2923
if (storageAccessConfig.enableCacheEvictOnCachePut
3024
&& value instanceof AbstractReadWriteAccess.SoftLockImpl
31-
&& readWriteAccessLocks.getIfPresent(value) == null) {
25+
&& isTransactionActive(session)) {
26+
log.debug("enableCacheEvictOnCachePut enabled. key: {}", key);
3227
evictData(key);
33-
readWriteAccessLocks.put((AbstractReadWriteAccess.SoftLockImpl) value, key);
3428
}
3529
super.putIntoCache(key, value, session);
3630
}
31+
32+
@Override
33+
public void evictData(Object key) {
34+
if (storageAccessConfig.enableCacheEvictOnCachePut &&
35+
storageAccessConfig.regionGroupOnCacheEvict.contains(CACHE_REGION)) {
36+
String id = key.toString().split("#")[1];
37+
log.debug("regionGroupOnCacheEvict contains region: {}, id: {}", CACHE_REGION, id);
38+
domainDataStorageAccesses.forEach((region, storageAccess) -> {
39+
if (!region.equals(CACHE_REGION)) {
40+
storageAccess.evictDataOnRegionGroupCacheEvict(new HibernateArcusCacheKeysFactory.EntityKey(storageAccess.entityClassName, id));
41+
}
42+
});
43+
} else {
44+
super.evictData(key);
45+
}
46+
}
47+
48+
private boolean isTransactionActive(SharedSessionContractImplementor session) {
49+
return session.getTransaction() != null
50+
&& session.getTransaction().getStatus().equals(TransactionStatus.ACTIVE);
51+
}
3752
}

src/test/java/hibernate/ReadWriteAccessDomainDataRegionGroupTest.java

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
import java.io.Serializable;
1818

1919
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNull;
2021

2122
public class ReadWriteAccessDomainDataRegionGroupTest extends BaseCoreFunctionalTestCase {
2223

23-
private final String collectionCacheRegionName = "hibernate.CollectionCacheTest$ParentDomainData.children";
24-
2524
@Override
2625
protected Class<?>[] getAnnotatedClasses() {
2726
return new Class[]{DomainRegionOne.class, DomainRegionTwo.class};
@@ -72,6 +71,7 @@ public void testCacheEvictOnCachePut_whenADomainRegionOneEntityIsDeleted_thenDom
7271
s.beginTransaction();
7372
DomainRegionOne domainRegionOneFromCache = s.get(DomainRegionOne.class, id);
7473
s.delete(domainRegionOneFromCache);
74+
s.flush();
7575
s.getTransaction().commit();
7676
s.close();
7777
System.out.println("============================");
@@ -82,8 +82,62 @@ public void testCacheEvictOnCachePut_whenADomainRegionOneEntityIsDeleted_thenDom
8282
s = openSession();
8383
s.beginTransaction();
8484
s.get(DomainRegionTwo.class, id);
85+
DomainRegionOne domainRegionOneAfterDelete = s.get(DomainRegionOne.class, id);
86+
s.getTransaction().commit();
87+
s.close();
88+
assertNull(domainRegionOneAfterDelete);
89+
assertEquals(1, regionTwoStat.getMissCount());
90+
}
91+
92+
/**
93+
* When update of regionOneEntity and delete of the regionTwo are executed in a transaction, delete is called after update and the cache item that the update put is evicted.
94+
* As a result, the next get operation of the updated entity will get cacheMiss
95+
*/
96+
@Test
97+
public void testCacheEvictOnCachePut_whenDomainRegionOneEntityIsDeletedAndDomainRegionTwoIsUpdatedInTheSameTransaction_thenDomainRegionTwoShouldBeCacheMiss() {
98+
CacheRegionStatistics regionOneStat = sessionFactory()
99+
.getStatistics().getDomainDataRegionStatistics(DomainRegionOne.regionName);
100+
CacheRegionStatistics regionTwoStat = sessionFactory()
101+
.getStatistics().getDomainDataRegionStatistics(DomainRegionTwo.regionName);
102+
final long id = System.currentTimeMillis();
103+
Session s = openSession();
104+
s.beginTransaction();
105+
DomainRegionOne regionOne = new DomainRegionOne(id);
106+
DomainRegionTwo regionTwo = new DomainRegionTwo(id);
107+
s.save(regionOne);
108+
s.save(regionTwo);
109+
s.flush();
110+
s.getTransaction().commit();
111+
s.close();
112+
System.out.println("============================");
113+
114+
assertEquals(1, regionOneStat.getPutCount());
115+
assertEquals(0, regionOneStat.getHitCount());
116+
assertEquals(1, regionTwoStat.getPutCount());
117+
118+
s = openSession();
119+
s.beginTransaction();
120+
DomainRegionOne domainRegionOneFromCache = s.get(DomainRegionOne.class, id);
121+
s.delete(domainRegionOneFromCache);
122+
DomainRegionTwo domainRegionTwoFromCache = s.get(DomainRegionTwo.class, id);
123+
domainRegionTwoFromCache.value = "updated_value";
124+
s.update(domainRegionTwoFromCache);
125+
s.flush();
126+
s.getTransaction().commit();
127+
s.close();
128+
System.out.println("============================");
129+
130+
assertEquals(1, regionTwoStat.getHitCount());
131+
assertEquals(0, regionTwoStat.getMissCount());
132+
133+
s = openSession();
134+
s.beginTransaction();
135+
DomainRegionOne domainRegionOneAfterDelete = s.get(DomainRegionOne.class, id);
136+
s.get(DomainRegionTwo.class, id);
85137
s.getTransaction().commit();
86138
s.close();
139+
assertNull(domainRegionOneAfterDelete);
140+
assertEquals(1, regionTwoStat.getHitCount());
87141
assertEquals(1, regionTwoStat.getMissCount());
88142
}
89143

@@ -129,6 +183,59 @@ public void testCacheEvictOnCachePut_whenADomainRegionOneEntityIsUpdated_thenDom
129183
assertEquals(1, regionTwoStat.getMissCount());
130184
}
131185

186+
/**
187+
* When updates of regionOne and regionTwo are executed in a transaction, the cache items of both region entities are put.
188+
* However the second update evicts the cache item of the first update.
189+
* As a result, the next get operation of regionOne will get cacheMiss, and regionTwo will get cacheHit
190+
*/
191+
@Test
192+
public void testCacheEvictOnCachePut_whenDomainRegionOneEntityAndDomainRegionOneEntityAreUpdatedInTheSameTransaction_thenDomainRegionOneShouldBeCacheMissAndDomainRegionTwoShouldBeCacheHit() {
193+
CacheRegionStatistics regionOneStat = sessionFactory()
194+
.getStatistics().getDomainDataRegionStatistics(DomainRegionOne.regionName);
195+
CacheRegionStatistics regionTwoStat = sessionFactory()
196+
.getStatistics().getDomainDataRegionStatistics(DomainRegionTwo.regionName);
197+
final long id = System.currentTimeMillis();
198+
Session s = openSession();
199+
s.beginTransaction();
200+
DomainRegionOne regionOne = new DomainRegionOne(id);
201+
DomainRegionTwo regionTwo = new DomainRegionTwo(id);
202+
s.save(regionOne);
203+
s.save(regionTwo);
204+
s.flush();
205+
s.getTransaction().commit();
206+
s.close();
207+
System.out.println("============================");
208+
209+
assertEquals(1, regionOneStat.getPutCount());
210+
assertEquals(1, regionTwoStat.getPutCount());
211+
212+
s = openSession();
213+
s.beginTransaction();
214+
DomainRegionOne domainRegionOneFromCache = s.get(DomainRegionOne.class, id);
215+
domainRegionOneFromCache.value = "updated-value";
216+
s.update(domainRegionOneFromCache);
217+
DomainRegionTwo domainRegionTwoFromCache = s.get(DomainRegionTwo.class, id);
218+
domainRegionTwoFromCache.value = "updated_value";
219+
s.update(domainRegionTwoFromCache);
220+
s.flush();
221+
s.getTransaction().commit();
222+
s.close();
223+
System.out.println("============================");
224+
225+
assertEquals(1, regionOneStat.getHitCount());
226+
assertEquals(1, regionTwoStat.getHitCount());
227+
228+
s = openSession();
229+
s.beginTransaction();
230+
s.get(DomainRegionOne.class, id);
231+
s.get(DomainRegionTwo.class, id);
232+
s.getTransaction().commit();
233+
s.close();
234+
assertEquals(1, regionOneStat.getHitCount());
235+
assertEquals(1, regionOneStat.getMissCount());
236+
assertEquals(2, regionTwoStat.getHitCount());
237+
}
238+
132239
@Entity
133240
@NoArgsConstructor
134241
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = DomainRegionOne.regionName)
@@ -151,6 +258,7 @@ public static class DomainRegionTwo implements Serializable {
151258

152259
@Id
153260
long id;
261+
public String value = "";
154262

155263
public DomainRegionTwo(long id) {
156264
this.id = id;

0 commit comments

Comments
 (0)