DATAREDIS-553 - Support caching null values via RedisCache using callable.

Store `NullValue` placeholders when null value caching is allowed. Remove cache values when null value caching is disabled and the value loader returns a `null` value.

Original pull request: #221.
This commit is contained in:
Mark Paluch
2016-10-05 14:13:34 +02:00
parent 4c54bf972a
commit f26441d48d
3 changed files with 146 additions and 9 deletions

View File

@@ -51,6 +51,7 @@ import org.springframework.data.redis.core.AbstractOperationsTestParams;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import edu.umd.cs.mtc.MultithreadedTestCase;
@@ -381,6 +382,71 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
assertThat(cache.get(key, String.class), is(nullValue()));
}
/**
* @see DATAREDIS-553
*/
@Test
public void testCacheGetSynchronizedNullAllowingNull() {
assumeThat(getAllowCacheNullValues(), is(true));
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
Object value = cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
assertThat(value, is(nullValue()));
assertThat(cache.get(key).get(), is(nullValue()));
}
/**
* @see DATAREDIS-553
*/
@Test
public void testCacheGetSynchronizedNullNotAllowingNull() {
assumeThat(getAllowCacheNullValues(), is(false));
assumeThat(cache, instanceOf(RedisCache.class));
assumeThat(template.getValueSerializer(), not(instanceOf(StringRedisSerializer.class)));
Object key = getKey();
Object value = cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
assertThat(value, is(nullValue()));
assertThat(cache.get(key), is(nullValue()));
}
/**
* @see DATAREDIS-553
*/
@Test
public void testCacheGetSynchronizedNullWithStoredNull() {
assumeThat(getAllowCacheNullValues(), is(true));
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
cache.put(key, null);
Object cachedValue = cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
assertThat(cachedValue, is(nullValue()));
}
@SuppressWarnings("unused")
private static class CacheGetWithValueLoaderIsThreadSafe extends MultithreadedTestCase {

View File

@@ -34,6 +34,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.cache.Cache;
import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
@@ -138,7 +139,7 @@ public class RedisCacheUnitTests {
* @see DATAREDIS-369
*/
@Test
public void clearShouldCallLuaScritpToRemoveKeysWhenPrefixIsSet() {
public void clearShouldCallLuaScriptToRemoveKeysWhenPrefixIsSet() {
cache = new RedisCache(CACHE_NAME, PREFIX_BYTES, templateSpy, EXPIRATION);
cache.clear();
@@ -214,7 +215,7 @@ public class RedisCacheUnitTests {
*/
@Test
@SuppressWarnings("unchecked")
public void getWithCallable() throws ClassNotFoundException, LinkageError {
public void getWithCallable() throws ClassNotFoundException {
if (isPresent("org.springframework.cache.Cache$ValueRetrievalException", getDefaultClassLoader())) {
exception.expect((Class<? extends Throwable>) forName("org.springframework.cache.Cache$ValueRetrievalException",
@@ -235,6 +236,51 @@ public class RedisCacheUnitTests {
});
}
/**
* @see DATAREDIS-553
*/
@Test
@SuppressWarnings("unchecked")
public void getWithCallableShouldStoreNullNotAllowingNull() throws ClassNotFoundException {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L, false);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
verify(connectionMock, times(1)).get(eq(KEY_BYTES));
verify(connectionMock, times(1)).multi();
verify(connectionMock, times(1)).del(eq(KEY_BYTES));
verify(connectionMock, times(1)).exec();
}
/**
* @see DATAREDIS-553
*/
@Test
@SuppressWarnings("unchecked")
public void getWithCallableShouldStoreNullAllowingNull() throws ClassNotFoundException {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L, true);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
return null;
}
});
verify(valueSerializerMock).serialize(isA(NullValue.class));
verify(connectionMock, times(1)).get(eq(KEY_BYTES));
verify(connectionMock, times(1)).multi();
verify(connectionMock, times(1)).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(connectionMock, times(1)).exec();
}
/**
* @see DATAREDIS-443
*/