From c8c970cb9141e56ca48d21e68da41598f4770f0a Mon Sep 17 00:00:00 2001 From: agrgr Date: Thu, 13 Jun 2024 14:59:35 +0300 Subject: [PATCH] use hash code of key class when its object empty, add tests --- .../data/aerospike/cache/AerospikeCache.java | 6 +- ...AerospikeCacheManagerIntegrationTests.java | 118 +++++++++++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/data/aerospike/cache/AerospikeCache.java b/src/main/java/org/springframework/data/aerospike/cache/AerospikeCache.java index 4fbd506bb..1ce7809be 100644 --- a/src/main/java/org/springframework/data/aerospike/cache/AerospikeCache.java +++ b/src/main/java/org/springframework/data/aerospike/cache/AerospikeCache.java @@ -21,6 +21,7 @@ import com.aerospike.client.policy.RecordExistsAction; import com.aerospike.client.policy.WritePolicy; import org.springframework.cache.Cache; +import org.springframework.cache.interceptor.SimpleKey; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.data.aerospike.convert.AerospikeConverter; import org.springframework.data.aerospike.convert.AerospikeReadData; @@ -196,7 +197,10 @@ public ValueWrapper putIfAbsent(Object key, Object value) { } private Key getKey(Object key) { - return new Key(cacheConfiguration.getNamespace(), cacheConfiguration.getSet(), key.hashCode()); + int userKey = key.hashCode(); + // when no arguments are given return hash code of key's class (hash code of key itself can be equal to 1) + if (key instanceof SimpleKey && key.equals(SimpleKey.EMPTY)) userKey = key.getClass().hashCode(); + return new Key(cacheConfiguration.getNamespace(), cacheConfiguration.getSet(), userKey); } private void serializeAndPut(WritePolicy writePolicy, Object key, Object value) { diff --git a/src/test/java/org/springframework/data/aerospike/cache/AerospikeCacheManagerIntegrationTests.java b/src/test/java/org/springframework/data/aerospike/cache/AerospikeCacheManagerIntegrationTests.java index 76be342c8..1d4780920 100644 --- a/src/test/java/org/springframework/data/aerospike/cache/AerospikeCacheManagerIntegrationTests.java +++ b/src/test/java/org/springframework/data/aerospike/cache/AerospikeCacheManagerIntegrationTests.java @@ -33,6 +33,7 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.springframework.data.aerospike.util.AwaitilityUtils.awaitTenSecondsUntil; public class AerospikeCacheManagerIntegrationTests extends BaseBlockingIntegrationTests { @@ -54,18 +55,22 @@ public class AerospikeCacheManagerIntegrationTests extends BaseBlockingIntegrati AerospikeCacheManager aerospikeCacheManager; @BeforeEach - public void setup() { + public void setup() throws NoSuchMethodException { cachingComponent.reset(); deleteRecords(); } - private void deleteRecords() { + private void deleteRecords() throws NoSuchMethodException { List hashCodes = List.of( STRING_PARAM.hashCode(), STRING_PARAM_THAT_MATCHES_CONDITION.hashCode(), Long.hashCode(NUMERIC_PARAM), MAP_PARAM.hashCode(), - new SimpleKey(STRING_PARAM, NUMERIC_PARAM, MAP_PARAM).hashCode()); + new SimpleKey(STRING_PARAM, NUMERIC_PARAM, MAP_PARAM).hashCode(), + SimpleKey.class.hashCode(), + CachingComponent.class.hashCode(), + CachingComponent.class.getMethod("cacheableMethodWithMethodNameKey").hashCode() + ); for (int hash : hashCodes) { client.delete(null, new Key(getNameSpace(), DEFAULT_SET_NAME, hash)); } @@ -126,6 +131,73 @@ public void shouldCacheWithMultipleParams() { assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); } + @Test + public void shouldCacheWithNthParam() { + assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); + CachedObject response1 = cachingComponent.cacheableMethodWithNthParam(STRING_PARAM, NUMERIC_PARAM, + MAP_PARAM); + CachedObject response2 = cachingComponent.cacheableMethodWithNthParam(STRING_PARAM, NUMERIC_PARAM, + MAP_PARAM); + + assertThat(response1).isNotNull(); + assertThat(response1.getValue()).isEqualTo(VALUE); + assertThat(response2).isNotNull(); + assertThat(response2.getValue()).isEqualTo(VALUE); + assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); + } + + @Test + public void shouldCacheWithNoParams() { + assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); + CachedObject response1 = cachingComponent.cacheableMethodWithNoParams(); + CachedObject response2 = cachingComponent.cacheableMethodWithNoParams(); + + assertThat(response1).isNotNull(); + assertThat(response1.getValue()).isEqualTo(VALUE); + assertThat(response2).isNotNull(); + assertThat(response2.getValue()).isEqualTo(VALUE); + assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); + } + + @Test + public void shouldNotCacheDifferentMethodsWithNoParamsByDefault_NegativeTest() { + assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); + CachedObject response1 = cachingComponent.cacheableMethodWithNoParams(); + assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); + + // any two methods with no arguments will have the same cache key by default, + // to change it set the necessary Cacheable annotation parameters (e.g. "key") + assertThatThrownBy(() -> cachingComponent.anotherMethodWithNoParams()) + .isInstanceOf(ClassCastException.class) + .hasMessageMatching(".+CachedObject cannot be cast to class .+AnotherCachedObject.*"); + } + + @Test + public void shouldCacheWithTargetClassKey() { + assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); + CachedObject response1 = cachingComponent.cacheableMethodWithTargetClassKey(); + CachedObject response2 = cachingComponent.cacheableMethodWithTargetClassKey(); + + assertThat(response1).isNotNull(); + assertThat(response1.getValue()).isEqualTo(VALUE); + assertThat(response2).isNotNull(); + assertThat(response2.getValue()).isEqualTo(VALUE); + assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); + } + + @Test + public void shouldCacheWithMethodNameKey() { + assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); + CachedObject response1 = cachingComponent.cacheableMethodWithMethodNameKey(); + CachedObject response2 = cachingComponent.cacheableMethodWithMethodNameKey(); + + assertThat(response1).isNotNull(); + assertThat(response1.getValue()).isEqualTo(VALUE); + assertThat(response2).isNotNull(); + assertThat(response2.getValue()).isEqualTo(VALUE); + assertThat(cachingComponent.getNoOfCalls()).isEqualTo(1); + } + @Test public void shouldCacheUsingDefaultSet() { assertThat(aerospikeOperations.count(DEFAULT_SET_NAME)).isEqualTo(0); @@ -317,6 +389,36 @@ public CachedObject cacheableMethodWithMultipleParams(String param1, long param2 return new CachedObject(VALUE); } + @Cacheable(value = "TEST", key = "#root.args[1]") + public CachedObject cacheableMethodWithNthParam(String param1, long param2, Map param3) { + noOfCalls++; + return new CachedObject(VALUE); + } + + @Cacheable("TEST") + public CachedObject cacheableMethodWithNoParams() { + noOfCalls++; + return new CachedObject(VALUE); + } + + @Cacheable("TEST") + public AnotherCachedObject anotherMethodWithNoParams() { + noOfCalls++; + return new AnotherCachedObject(NUMERIC_PARAM); + } + + @Cacheable(value = "TEST", key = "#root.targetClass") + public CachedObject cacheableMethodWithTargetClassKey() { + noOfCalls++; + return new CachedObject(VALUE); + } + + @Cacheable(value = "TEST", key = "#root.method") + public CachedObject cacheableMethodWithMethodNameKey() { + noOfCalls++; + return new CachedObject(VALUE); + } + @Cacheable("TEST12345ABC") // Cache name not pre-configured in AerospikeCacheManager, so it goes to default set public CachedObject cacheableMethodDefaultCache(String param) { noOfCalls++; @@ -371,4 +473,14 @@ public Object getValue() { return value; } } + + @AllArgsConstructor + public static class AnotherCachedObject { + + private final long number; + + public long getNumber() { + return number; + } + } }