DATAREDIS-481 - Revise RedisCache.

RedisCache now operates directly with RedisConnectionFactory allowing each Cache to use its own SerializationPair for cache key and value conversion.

A RedisCacheManager with default settings is created with RedisCacheManager.create(connectionFactory). Use RedisCacheManager.builder(…) to obtain a builder to customize cache settings.

RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
  .cacheDefaults(defaultCacheConfig())
  .initialCacheConfigurations(singletonMap("predefined", defaultCacheConfig().disableCachingNullValues()))
  .transactionAware()
  .build();

The behavior of a single RedisCache is defined via its RedisCacheConfiguration. Starting from defaultCacheConfig() the configuration can be altered as needed.

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  .entryTtl(Duration.ofSeconds(1))
  .disableCachingNullValues();

RedisCacheManager defaults to a lock-free RedisCacheWriter for reading & writing binary values. Lock-free caching improves throughput. The lack of entry locking can lead to overlapping, non atomic commands, for putIfAbsent and clean methods as those require multiple commands sent to Redis. The locking counterpart prevents command overlap by setting an explicit lock key and checking against presence of this key, which leads to additional requests and potential command wait times.

Original pull request: #252.
This commit is contained in:
Christoph Strobl
2017-05-30 10:59:32 +02:00
committed by Mark Paluch
parent eb3ad1e351
commit a9c2b1cdda
22 changed files with 1901 additions and 2570 deletions

View File

@@ -1,108 +0,0 @@
/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.junit.Assert.*;
import static org.springframework.data.redis.matcher.RedisTestMatchers.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueWrapper;
/**
* Test for native cache implementations.
*
* @author Costin Leau
*/
public abstract class AbstractNativeCacheTest<T> {
private T nativeCache;
protected Cache cache;
protected final static String CACHE_NAME = "testCache";
private final boolean allowCacheNullValues;
protected AbstractNativeCacheTest(boolean allowCacheNullValues) {
this.allowCacheNullValues = allowCacheNullValues;
}
@Before
public void setUp() throws Exception {
nativeCache = createNativeCache();
cache = createCache(nativeCache, allowCacheNullValues);
cache.clear();
}
protected abstract T createNativeCache() throws Exception;
protected abstract Cache createCache(T nativeCache, boolean allowCacheNullValues);
protected abstract Object getKey();
protected abstract Object getValue();
protected boolean getAllowCacheNullValues() {
return allowCacheNullValues;
}
@Test
public void testCacheName() throws Exception {
assertEquals(CACHE_NAME, cache.getName());
}
@Test
public void testNativeCache() throws Exception {
assertSame(nativeCache, cache.getNativeCache());
}
@Test
public void testCachePut() throws Exception {
Object key = getKey();
Object value = getValue();
assertNotNull(value);
assertNull(cache.get(key));
cache.put(key, value);
ValueWrapper valueWrapper = cache.get(key);
if (valueWrapper != null) {
assertThat(valueWrapper.get(), isEqual(value));
}
// keeps failing on the CI server so do
else {
// Thread.sleep(200);
// assertNotNull(cache.get(key));
// ignore for now
}
}
@Test
public void testCacheClear() throws Exception {
Object key1 = getKey();
Object value1 = getValue();
Object key2 = getKey();
Object value2 = getValue();
assertNull(cache.get(key1));
cache.put(key1, value1);
assertNull(cache.get(key2));
cache.put(key2, value2);
cache.clear();
assertNull(cache.get(key2));
assertNull(cache.get(key1));
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Delegate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.runners.model.Statement;
import org.springframework.data.redis.SettingsUtils;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.OxmSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.test.util.RedisClusterRule;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
*/
class CacheTestParams {
private static Collection<RedisConnectionFactory> connectionFactories() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
config.setHostName(SettingsUtils.getHost());
config.setPort(SettingsUtils.getPort());
List<RedisConnectionFactory> factoryList = new ArrayList<>(3);
// Jedis Standalone
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(config);
jedisConnectionFactory.afterPropertiesSet();
factoryList.add(new FixDamnedJunitParameterizedNameForConnectionFactory(jedisConnectionFactory, ""));
// Lettuce Standalone
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(config);
lettuceConnectionFactory.afterPropertiesSet();
factoryList.add(new FixDamnedJunitParameterizedNameForConnectionFactory(lettuceConnectionFactory, ""));
if (clusterAvailable()) {
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.addClusterNode(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_1_PORT));
// Jedis Cluster
JedisConnectionFactory jedisClusterConnectionFactory = new JedisConnectionFactory(clusterConfiguration);
jedisClusterConnectionFactory.afterPropertiesSet();
factoryList
.add(new FixDamnedJunitParameterizedNameForConnectionFactory(jedisClusterConnectionFactory, "cluster"));
// Lettuce Cluster
LettuceConnectionFactory lettuceClusterConnectionFactory = new LettuceConnectionFactory(clusterConfiguration);
lettuceClusterConnectionFactory.afterPropertiesSet();
factoryList
.add(new FixDamnedJunitParameterizedNameForConnectionFactory(lettuceClusterConnectionFactory, "cluster"));
}
return factoryList;
}
static Collection<Object[]> justConnectionFactories() {
return connectionFactories().stream().map(factory -> new Object[] { factory }).collect(Collectors.toList());
}
static Collection<Object[]> connectionFactoriesAndSerializers() {
// XStream serializer
XStreamMarshaller xstream = new XStreamMarshaller();
xstream.afterPropertiesSet();
OxmSerializer oxmSerializer = new OxmSerializer(xstream, xstream);
GenericJackson2JsonRedisSerializer jackson2Serializer = new GenericJackson2JsonRedisSerializer();
JdkSerializationRedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
return connectionFactories()
.stream().flatMap(factory -> Arrays
.asList( //
new Object[] { factory, new FixDamnedJunitParameterizedNameForRedisSerializer(jdkSerializer) }, //
new Object[] { factory, new FixDamnedJunitParameterizedNameForRedisSerializer(jackson2Serializer) }, //
new Object[] { factory, new FixDamnedJunitParameterizedNameForRedisSerializer(oxmSerializer) })
.stream())
.collect(Collectors.toList());
}
@RequiredArgsConstructor
static class FixDamnedJunitParameterizedNameForConnectionFactory/* ¯\_(ツ)_/¯ */ implements RedisConnectionFactory {
final @Delegate RedisConnectionFactory connectionFactory;
final String addon;
@Override // Why Junit? Why?
public String toString() {
return connectionFactory.getClass().getSimpleName() + (StringUtils.hasText(addon) ? " - [" + addon + "]" : "");
}
}
@RequiredArgsConstructor
static class FixDamnedJunitParameterizedNameForRedisSerializer/* ¯\_(ツ)_/¯ */ implements RedisSerializer {
final @Delegate RedisSerializer serializer;
@Override // Why Junit? Why?
public String toString() {
return serializer.getClass().getSimpleName();
}
}
private static boolean clusterAvailable() {
try {
new RedisClusterRule().apply(new Statement() {
@Override
public void evaluate() throws Throwable {
}
}, null).evaluate();
} catch (Throwable throwable) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.redis.cache.DefaultRedisCacheWriter.*;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.types.Expiration;
/**
* @author Christoph Strobl
*/
@RunWith(Parameterized.class)
public class DefaultRedisCacheWriterTests {
static final String CACHE_NAME = "default-redis-cache-writer-tests";
String key = "key-1";
String cacheKey = CACHE_NAME + "::" + key;
byte[] binaryCacheKey = cacheKey.getBytes(Charset.forName("UTF-8"));
byte[] binaryCacheValue = "value".getBytes(Charset.forName("UTF-8"));
RedisConnectionFactory connectionFactory;
public DefaultRedisCacheWriterTests(RedisConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
ConnectionFactoryTracker.add(connectionFactory);
}
@Parameters(name = "{index}: {0}")
public static Collection<Object[]> testParams() {
return CacheTestParams.justConnectionFactories();
}
@AfterClass
public static void cleanUpResources() {
ConnectionFactoryTracker.cleanUp();
}
@Before
public void setUp() {
JedisConnectionFactory cf = new JedisConnectionFactory();
cf.afterPropertiesSet();
connectionFactory = cf;
doWithConnection(RedisConnection::flushAll);
}
@Test // DATAREDIS-481
public void putShouldAddEternalEntry() {
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
assertThat(connection.ttl(binaryCacheKey)).isEqualTo(-1);
});
}
@Test // DATAREDIS-481
public void putShouldAddExpiringEntry() {
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ofSeconds(1));
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
assertThat(connection.ttl(binaryCacheKey)).isGreaterThan(0);
});
}
@Test // DATAREDIS-481
public void putShouldOverwriteExistingEternalEntry() {
doWithConnection(connection -> connection.set(binaryCacheKey, "foo".getBytes()));
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
assertThat(connection.ttl(binaryCacheKey)).isEqualTo(-1);
});
}
@Test // DATAREDIS-481
public void putShouldOverwriteExistingExpiringEntryAndResetTtl() {
doWithConnection(connection -> connection.set(binaryCacheKey, "foo".getBytes(),
Expiration.from(1, TimeUnit.MINUTES), SetOption.upsert()));
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ofSeconds(5));
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
assertThat(connection.ttl(binaryCacheKey)).isGreaterThan(3).isLessThan(6);
});
}
@Test // DATAREDIS-481
public void getShouldReturnValue() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
assertThat(nonLockingRedisCacheWriter(connectionFactory).get(CACHE_NAME, binaryCacheKey))
.isEqualTo(binaryCacheValue);
}
@Test // DATAREDIS-481
public void getShouldReturnNullWhenKeyDoesNotExist() {
assertThat(nonLockingRedisCacheWriter(connectionFactory).get(CACHE_NAME, binaryCacheKey)).isNull();
}
@Test // DATAREDIS-481
public void putIfAbsentShouldAddEternalEntryWhenKeyDoesNotExist() {
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ZERO)).isNull();
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
});
}
@Test // DATAREDIS-481
public void putIfAbsentShouldNotAddEternalEntryWhenKeyAlreadyExist() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, "foo".getBytes(),
Duration.ZERO)).isEqualTo(binaryCacheValue);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
});
}
@Test // DATAREDIS-481
public void putIfAbsentShouldAddExpiringEntryWhenKeyDoesNotExist() {
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ofSeconds(5))).isNull();
doWithConnection(connection -> {
assertThat(connection.ttl(binaryCacheKey)).isGreaterThan(3).isLessThan(6);
});
}
@Test // DATAREDIS-481
public void removeShouldDeleteEntry() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
nonLockingRedisCacheWriter(connectionFactory).remove(CACHE_NAME, binaryCacheKey);
doWithConnection(connection -> assertThat(connection.exists(binaryCacheKey)).isFalse());
}
@Test // DATAREDIS-418
public void cleanShouldRemoveAllKeysByPattern() {
doWithConnection(connection -> {
connection.set(binaryCacheKey, binaryCacheValue);
connection.set("foo".getBytes(), "bar".getBytes());
});
nonLockingRedisCacheWriter(connectionFactory).clean(CACHE_NAME,
(CACHE_NAME + "::*").getBytes(Charset.forName("UTF-8")));
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isFalse();
assertThat(connection.exists("foo".getBytes())).isTrue();
});
}
@Test // DATAREDIS-481
public void nonLockingCacheWriterShouldIgnoreExistingLock() {
lockingRedisCacheWriter(connectionFactory).lock(CACHE_NAME);
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
});
}
@Test // DATAREDIS-481
public void lockingCacheWriterShouldIgnoreExistingLockOnDifferenceCache() {
lockingRedisCacheWriter(connectionFactory).lock(CACHE_NAME);
lockingRedisCacheWriter(connectionFactory).put(CACHE_NAME + "-no-the-other-cache", binaryCacheKey, binaryCacheValue,
Duration.ZERO);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
});
}
@Test // DATAREDIS-481
public void lockingCacheWriterShouldWaitForLockRelease() throws InterruptedException {
DefaultRedisCacheWriter cw = lockingRedisCacheWriter(connectionFactory);
cw.lock(CACHE_NAME);
Thread th = new Thread(() -> {
lockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
});
th.start();
try {
Thread.sleep(200);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isFalse();
});
cw.unlock(CACHE_NAME);
Thread.sleep(200);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
});
} finally {
th.interrupt();
}
}
void doWithConnection(Consumer<RedisConnection> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
callback.accept(connection);
} finally {
connection.close();
}
}
}

View File

@@ -13,13 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static edu.umd.cs.mtc.TestFramework.*;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*;
import static org.hamcrest.core.IsInstanceOf.*;
import static org.hamcrest.core.IsNot.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsSame.*;
@@ -27,18 +25,18 @@ import static org.junit.Assert.*;
import static org.junit.Assume.*;
import static org.springframework.data.redis.matcher.RedisTestMatchers.*;
import edu.umd.cs.mtc.MultithreadedTestCase;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hamcrest.core.IsInstanceOf;
import org.junit.AfterClass;
import org.junit.AssumptionViolatedException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -48,16 +46,14 @@ import org.springframework.cache.Cache.ValueRetrievalException;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.ObjectFactory;
import org.springframework.data.redis.StringObjectFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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;
/**
* Tests moved over from 1.x line RedisCache implementation. Just removed somme of the limitations/assumtions previously
* required.
*
* @author Costin Leau
* @author Jennifer Hickey
* @author Christoph Strobl
@@ -65,21 +61,26 @@ import edu.umd.cs.mtc.MultithreadedTestCase;
*/
@SuppressWarnings("rawtypes")
@RunWith(Parameterized.class)
public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
public class LegacyRedisCacheTests {
private ObjectFactory<Object> keyFactory;
private ObjectFactory<Object> valueFactory;
private RedisTemplate template;
final static String CACHE_NAME = "testCache";
ObjectFactory<Object> keyFactory;
ObjectFactory<Object> valueFactory;
RedisConnectionFactory connectionFactory;
final boolean allowCacheNullValues;
public RedisCacheTest(RedisTemplate template, ObjectFactory<Object> keyFactory, ObjectFactory<Object> valueFactory,
boolean allowCacheNullValues) {
RedisCache cache;
super(allowCacheNullValues);
public LegacyRedisCacheTests(RedisTemplate template, ObjectFactory<Object> keyFactory,
ObjectFactory<Object> valueFactory, boolean allowCacheNullValues) {
this.connectionFactory = template.getConnectionFactory();
this.keyFactory = keyFactory;
this.valueFactory = valueFactory;
this.template = template;
ConnectionFactoryTracker.add(template.getConnectionFactory());
this.allowCacheNullValues = allowCacheNullValues;
ConnectionFactoryTracker.add(connectionFactory);
cache = createCache();
}
@Parameters
@@ -103,35 +104,23 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
return target;
}
@SuppressWarnings("unchecked")
protected RedisCache createCache(RedisTemplate nativeCache, boolean allowCacheNullValues) {
return new RedisCache(CACHE_NAME, CACHE_NAME.concat(":").getBytes(), nativeCache, TimeUnit.MINUTES.toSeconds(10),
allowCacheNullValues);
}
protected RedisTemplate createNativeCache() throws Exception {
return template;
}
@Before
public void setUp() throws Exception {
if (!(template.getValueSerializer() instanceof JdkSerializationRedisSerializer
|| template.getValueSerializer() instanceof GenericJackson2JsonRedisSerializer
|| template.getValueSerializer() == null) && getAllowCacheNullValues()) {
throw new AssumptionViolatedException(
"Null values can only be cachend with the Jdk or GenericJackson2 serialization");
}
ConnectionFactoryTracker.add(template.getConnectionFactory());
super.setUp();
}
@AfterClass
public static void cleanUp() {
ConnectionFactoryTracker.cleanUp();
}
@SuppressWarnings("unchecked")
private RedisCache createCache() {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(10));
if (!allowCacheNullValues) {
cacheConfiguration = cacheConfiguration.disableCachingNullValues();
}
return new RedisCache(CACHE_NAME, new DefaultRedisCacheWriter(connectionFactory), cacheConfiguration);
}
protected Object getValue() {
return valueFactory.instance();
}
@@ -140,8 +129,40 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
return keyFactory.instance();
}
@Test
public void testCachePut() throws Exception {
Object key = getKey();
Object value = getValue();
assertNotNull(value);
assertNull(cache.get(key));
cache.put(key, value);
ValueWrapper valueWrapper = cache.get(key);
if (valueWrapper != null) {
assertThat(valueWrapper.get(), isEqual(value));
}
}
@Test
public void testCacheClear() throws Exception {
Object key1 = getKey();
Object value1 = getValue();
Object key2 = getKey();
Object value2 = getValue();
assertNull(cache.get(key1));
cache.put(key1, value1);
assertNull(cache.get(key2));
cache.put(key2, value2);
cache.clear();
assertNull(cache.get(key2));
assertNull(cache.get(key1));
}
@Test
public void testConcurrentRead() throws Exception {
final Object key1 = getKey();
final Object value1 = getValue();
@@ -187,20 +208,9 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
assertThat(valueWrapper.get(), isEqual(v1));
}
@Test
public void testCacheName() throws Exception {
RedisCacheManager redisCM = new RedisCacheManager(template);
redisCM.afterPropertiesSet();
String cacheName = "s2gx11";
Cache cache = redisCM.getCache(cacheName);
assertNotNull(cache);
assertTrue(redisCM.getCacheNames().contains(cacheName));
}
@Test
public void testGetWhileClear() throws InterruptedException {
final Object key1 = getKey();
final Object value1 = getValue();
int numTries = 10;
@@ -232,69 +242,56 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@Test // DATAREDIS-243
public void testCacheGetShouldReturnCachedInstance() {
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
Object value = getValue();
cache.put(key, value);
assertThat(value, isEqual(((RedisCache) cache).get(key, Object.class)));
assertThat(value, isEqual(cache.get(key, Object.class)));
}
@Test // DATAREDIS-243
public void testCacheGetShouldRetunInstanceOfCorrectType() {
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
Object value = getValue();
cache.put(key, value);
RedisCache redisCache = (RedisCache) cache;
assertThat(redisCache.get(key, value.getClass()), IsInstanceOf.<Object>instanceOf(value.getClass()));
assertThat(cache.get(key, value.getClass()), IsInstanceOf.<Object> instanceOf(value.getClass()));
}
@Test(expected = ClassCastException.class) // DATAREDIS-243
@Test(expected = IllegalStateException.class) // DATAREDIS-243
public void testCacheGetShouldThrowExceptionOnInvalidType() {
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
Object value = getValue();
cache.put(key, value);
RedisCache redisCache = (RedisCache) cache;
@SuppressWarnings("unused")
Cache retrievedObject = redisCache.get(key, Cache.class);
Cache retrievedObject = cache.get(key, Cache.class);
}
@Test // DATAREDIS-243
public void testCacheGetShouldReturnNullIfNoCachedValueFound() {
assumeThat(cache, instanceOf(RedisCache.class));
Object key = getKey();
Object value = getValue();
cache.put(key, value);
RedisCache redisCache = (RedisCache) cache;
Object invalidKey = template.getKeySerializer() == null ? "spring-data-redis".getBytes() : "spring-data-redis";
assertThat(redisCache.get(invalidKey, value.getClass()), nullValue());
Object invalidKey = "spring-data-redis".getBytes();
assertThat(cache.get(invalidKey, value.getClass()), nullValue());
}
@Test // DATAREDIS-344, DATAREDIS-416
public void putIfAbsentShouldSetValueOnlyIfNotPresent() {
assumeThat(cache, instanceOf(RedisCache.class));
RedisCache redisCache = (RedisCache) cache;
Object key = getKey();
template.delete(key);
Object value = getValue();
assertThat(redisCache.putIfAbsent(key, value), nullValue());
assertThat(cache.putIfAbsent(key, value), nullValue());
ValueWrapper wrapper = redisCache.putIfAbsent(key, value);
ValueWrapper wrapper = cache.putIfAbsent(key, value);
if (!(value instanceof Number)) {
assertThat(wrapper.get(), not(sameInstance(value)));
@@ -306,7 +303,7 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@Test(expected = IllegalArgumentException.class) // DATAREDIS-510, DATAREDIS-606
public void cachePutWithNullShouldNotAddStuffToRedis() {
assumeThat(getAllowCacheNullValues(), is(false));
assumeThat("Only suitable when cache does NOT allow null values.", allowCacheNullValues, is(false));
Object key = getKey();
@@ -316,7 +313,7 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@Test // DATAREDIS-510, DATAREDIS-606
public void cachePutWithNullShouldErrorAndLeaveExistingKeyUntouched() {
assumeThat(getAllowCacheNullValues(), is(false));
assumeThat("Only suitable when cache does NOT allow null values.", allowCacheNullValues, is(false));
Object key = getKey();
Object value = getValue();
@@ -336,17 +333,13 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@Test // DATAREDIS-443, DATAREDIS-452
public void testCacheGetSynchronized() throws Throwable {
assumeThat(cache, instanceOf(RedisCache.class));
assumeThat(valueFactory, instanceOf(StringObjectFactory.class));
runOnce(new CacheGetWithValueLoaderIsThreadSafe((RedisCache) cache));
runOnce(new CacheGetWithValueLoaderIsThreadSafe(cache));
}
@Test // DATAREDIS-553
public void cachePutWithNullShouldAddStuffToRedisWhenCachingNullIsEnabled() {
assumeThat(getAllowCacheNullValues(), is(true));
assumeThat("Only suitable when cache does allow null values.", allowCacheNullValues, is(true));
Object key = getKey();
Object value = getValue();
@@ -359,8 +352,7 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@Test // DATAREDIS-553
public void testCacheGetSynchronizedNullAllowingNull() {
assumeThat(getAllowCacheNullValues(), is(true));
assumeThat(cache, instanceOf(RedisCache.class));
assumeThat("Only suitable when cache does allow null values.", allowCacheNullValues, is(true));
Object key = getKey();
Object value = cache.get(key, new Callable<Object>() {
@@ -374,12 +366,10 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
assertThat(cache.get(key).get(), is(nullValue()));
}
@Test(expected = ValueRetrievalException.class) // DATAREDIS-553, DATAREDIS-606
@Test(expected = IllegalArgumentException.class) // DATAREDIS-553, DATAREDIS-606
public void testCacheGetSynchronizedNullNotAllowingNull() {
assumeThat(getAllowCacheNullValues(), is(false));
assumeThat(cache, instanceOf(RedisCache.class));
assumeThat(template.getValueSerializer(), not(instanceOf(StringRedisSerializer.class)));
assumeThat("Only suitable when cache does NOT allow null values.", allowCacheNullValues, is(false));
Object key = getKey();
Object value = cache.get(key, new Callable<Object>() {
@@ -390,11 +380,22 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
});
}
@Test(expected = ValueRetrievalException.class)
public void testCacheGetSynchronizedThrowsExceptionInValueLoader() {
Object key = getKey();
Object value = cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
throw new RuntimeException("doh!");
}
});
}
@Test // DATAREDIS-553
public void testCacheGetSynchronizedNullWithStoredNull() {
assumeThat(getAllowCacheNullValues(), is(true));
assumeThat(cache, instanceOf(RedisCache.class));
assumeThat("Only suitable when cache does allow null values.", allowCacheNullValues, is(true));
Object key = getKey();
cache.put(key, null);
@@ -412,10 +413,10 @@ public class RedisCacheTest extends AbstractNativeCacheTest<RedisTemplate> {
@SuppressWarnings("unused")
private static class CacheGetWithValueLoaderIsThreadSafe extends MultithreadedTestCase {
RedisCache redisCache;
Cache redisCache;
TestCacheLoader<String> cacheLoader;
public CacheGetWithValueLoaderIsThreadSafe(RedisCache redisCache) {
public CacheGetWithValueLoaderIsThreadSafe(Cache redisCache) {
this.redisCache = redisCache;

View File

@@ -1,121 +0,0 @@
/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.test.util.RelaxedJUnit4ClassRunner;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Thomas Darimont
*/
@RunWith(RelaxedJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "transactionManager")
public class RedisCacheManagerTransactionalUnitTests {
@Autowired protected CacheManager cacheManager;
private final static String cacheName = "cache-name";
@Configuration
@EnableCaching
public static class Config {
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
DataSourceTransactionManager txmgr = new DataSourceTransactionManager();
txmgr.setDataSource(dataSource());
txmgr.afterPropertiesSet();
return txmgr;
}
@Bean
public DataSource dataSource() throws SQLException {
DataSource dataSourceMock = mock(DataSource.class);
when(dataSourceMock.getConnection()).thenReturn(mock(Connection.class));
return dataSourceMock;
}
@Bean
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setTransactionAware(true);
cacheManager.setCacheNames(Arrays.asList(cacheName));
return cacheManager;
}
@SuppressWarnings({ "rawtypes" })
@Bean
public RedisTemplate redisTemplate() {
RedisConnection connectionMock = mock(RedisConnection.class);
RedisConnectionFactory factoryMock = mock(RedisConnectionFactory.class);
when(factoryMock.getConnection()).thenReturn(connectionMock);
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factoryMock);
return template;
}
}
@Test // DATAREDIS-375
public void testCacheIsNotDecoratedTwiceWithTransactionAwareCacheDecorator() {
Cache cache = cacheManager.getCache(cacheName);
// the cache must be decorated with the TransactionAwareCacheDecorator
assertTrue(cache instanceof TransactionAwareCacheDecorator);
TransactionAwareCacheDecorator transactionalCache = (TransactionAwareCacheDecorator) cache;
// get the target cache via reflection
Object targetCache = new DirectFieldAccessor(transactionalCache).getPropertyValue("targetCache");
// the target cache must be an instance of RedisCache and not TransactionAwareCacheDecorator
assertTrue(targetCache instanceof RedisCache);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2017 the original author or authors.
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,181 +15,66 @@
*/
package org.springframework.data.redis.cache;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsSame.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.hamcrest.core.IsCollectionContaining;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cache.Cache;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.util.ReflectionTestUtils;
/**
* @author Christoph Strobl
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class RedisCacheManagerUnitTests {
private @Mock RedisConnection redisConnectionMock;
private @Mock RedisConnectionFactory redisConnectionFactoryMock;
@Mock RedisCacheWriter cacheWriter;
@SuppressWarnings("rawtypes")//
private RedisTemplate redisTemplate;
private RedisCacheManager cacheManager;
@Test // DATAREDIS-481
public void missingCacheShouldBeCreatedWithDefaultConfiguration() {
@SuppressWarnings("rawtypes")
@Before
public void setUp() {
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
when(redisConnectionFactoryMock.getConnection()).thenReturn(redisConnectionMock);
redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactoryMock);
redisTemplate.afterPropertiesSet();
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.afterPropertiesSet();
RedisCacheManager cm = RedisCacheManager.usingCacheWriter(cacheWriter).withCacheDefaults(configuration).createAndGet();
assertThat(cm.getMissingCache("new-cache").getCacheConfiguration()).isEqualTo(configuration);
}
@Test // DATAREDIS-246
public void testGetCacheReturnsNewCacheWhenRequestedCacheIsNotAvailable() {
@Test // DATAREDIS-481
public void predefinedCacheShouldBeCreatedWithSpecificConfig() {
Cache cache = cacheManager.getCache("not-available");
assertThat(cache, notNullValue());
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
RedisCacheManager cm = RedisCacheManager.usingCacheWriter(cacheWriter)
.withInitialCacheConfigurations(Collections.singletonMap("predefined-cache", configuration)).createAndGet();
assertThat(((RedisCache) cm.getCache("predefined-cache")).getCacheConfiguration()).isEqualTo(configuration);
assertThat(cm.getMissingCache("new-cache").getCacheConfiguration()).isNotEqualTo(configuration);
}
@Test // DATAREDIS-246
public void testGetCacheReturnsExistingCacheWhenRequested() {
@Test // DATAREDIS-481
public void transactionAwareCacheManagerShouldDecoracteCache() {
Cache cache = cacheManager.getCache("cache");
assertThat(cacheManager.getCache("cache"), sameInstance(cache));
Cache cache = RedisCacheManager.usingCacheWriter(cacheWriter).transactionAware().createAndGet()
.getCache("decoracted-cache");
assertThat(cache).isInstanceOfAny(TransactionAwareCacheDecorator.class);
assertThat(ReflectionTestUtils.getField(cache, "targetCache")).isInstanceOf(RedisCache.class);
}
@Test // DATAREDIS-246
public void testCacheInitSouldNotRequestRemoteKeysByDefault() {
Mockito.verifyZeroInteractions(redisConnectionMock);
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.usingRawConnectionFactory(connectionFactory)
.withCacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
.withInitialCacheConfigurations(Collections.singletonMap("predefined", RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
.transactionAware()
.createAndGet();
}
@Test // DATAREDIS-246
public void testCacheInitShouldFetchAllCacheKeysWhenLoadingRemoteCachesOnStartupIsEnabled() {
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setLoadRemoteCachesOnStartup(true);
cacheManager.afterPropertiesSet();
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
verify(redisConnectionMock, times(1)).keys(captor.capture());
assertThat(redisTemplate.getKeySerializer().deserialize(captor.getValue()).toString(), is("*~keys"));
}
@SuppressWarnings("unchecked")
@Test // DATAREDIS-246
public void testCacheInitShouldInitializeRemoteCachesCorrectlyWhenLoadingRemoteCachesOnStartupIsEnabled() {
Set<byte[]> keys = new HashSet<byte[]>(Arrays.asList(redisTemplate.getKeySerializer()
.serialize("remote-cache~keys")));
when(redisConnectionMock.keys(any(byte[].class))).thenReturn(keys);
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setLoadRemoteCachesOnStartup(true);
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCacheNames(), IsCollectionContaining.hasItem("remote-cache"));
}
@Test // DATAREDIS-246
public void testCacheInitShouldNotInitialzeCachesWhenLoadingRemoteCachesOnStartupIsEnabledAndNoCachesAvailableOnRemoteServer() {
when(redisConnectionMock.keys(any(byte[].class))).thenReturn(Collections.<byte[]> emptySet());
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setLoadRemoteCachesOnStartup(true);
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCacheNames().isEmpty(), is(true));
}
/**
* see DATAREDIS-246
*/
@Test
public void testCacheManagerShouldNotDynamicallyCreateCachesWhenInStaticMode() {
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCache("redis"), nullValue());
}
/**
* see DATAREDIS-246
*/
@Test
public void testCacheManagerShouldRetrunRegisteredCacheWhenInStaticMode() {
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCache("spring"), notNullValue());
}
/**
* see DATAREDIS-246
*/
@Test
public void testPuttingCacheManagerIntoStaticModeShouldNotRemoveAlreadyRegisteredCaches() {
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.getCache("redis");
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCache("redis"), notNullValue());
}
@Test // DATAREDIS-283
public void testRetainConfiguredCachesAfterBeanInitialization() {
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCache("spring"), notNullValue());
assertThat(cacheManager.getCache("data"), notNullValue());
}
@Test // DATAREDIS-283
public void testRetainConfiguredCachesAfterBeanInitializationWithLoadingOfRemoteKeys() {
Set<byte[]> keys = new HashSet<byte[]>(Arrays.asList(redisTemplate.getKeySerializer()
.serialize("remote-cache~keys")));
when(redisConnectionMock.keys(any(byte[].class))).thenReturn(keys);
cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
cacheManager.setLoadRemoteCachesOnStartup(true);
cacheManager.afterPropertiesSet();
assertThat(cacheManager.getCache("spring"), notNullValue());
assertThat(cacheManager.getCache("data"), notNullValue());
assertThat(cacheManager.getCacheNames(), IsCollectionContaining.hasItem("remote-cache"));
}
}

View File

@@ -0,0 +1,256 @@
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.assertj.core.api.Assertions.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Date;
import java.util.function.Consumer;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.cache.Cache.ValueWrapper;
import org.springframework.cache.support.NullValue;
import org.springframework.data.redis.ConnectionFactoryTracker;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Tests for {@link RedisCache} with {@link DefaultRedisCacheWriter} using different {@link RedisSerializer} and
* {@link RedisConnectionFactory} pairs.
*
* @author Christoph Strobl
*/
@RunWith(Parameterized.class)
public class RedisCacheTests {
String key = "key-1";
String cacheKey = "cache::" + key;
byte[] binaryCacheKey = cacheKey.getBytes(Charset.forName("UTF-8"));
Person sample = new Person("calmity", new Date());
byte[] binarySample;
NullValue nullValue = NullValue.class.cast(ReflectionTestUtils.getField(NullValue.class, "INSTANCE"));
byte[] binaryNullValue = new JdkSerializationRedisSerializer().serialize(nullValue);
RedisConnectionFactory connectionFactory;
RedisSerializer serializer;
RedisCache cache;
public RedisCacheTests(RedisConnectionFactory connectionFactory, RedisSerializer serializer) {
this.connectionFactory = connectionFactory;
this.serializer = serializer;
this.binarySample = serializer.serialize(sample);
ConnectionFactoryTracker.add(connectionFactory);
}
@Parameters(name = "{index}: {0} & {1}")
public static Collection<Object[]> testParams() {
return CacheTestParams.connectionFactoriesAndSerializers();
}
@AfterClass
public static void cleanUpResources() {
ConnectionFactoryTracker.cleanUp();
}
@Before
public void setUp() {
doWithConnection(RedisConnection::flushAll);
cache = new RedisCache("cache", new DefaultRedisCacheWriter(connectionFactory),
RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(SerializationPair.fromSerializer(serializer)));
}
@Test // DATAREDIS-481
public void putShouldAddEntry() {
cache.put("key-1", sample);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
});
}
@Test // DATAREDIS-481
public void putNullShouldAddEntryForNullValue() {
cache.put("key-1", null);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryNullValue);
});
}
@Test // DATAREDIS-481
public void putIfAbsentShouldAddEntryIfNotExists() {
cache.putIfAbsent("key-1", sample);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
assertThat(connection.get(binaryCacheKey)).isEqualTo(binarySample);
});
}
@Test // DATAREDIS-481
public void putIfAbsentWithNullShouldAddNullValueEntryIfNotExists() {
assertThat(cache.putIfAbsent("key-1", null)).isNull();
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryNullValue);
});
}
@Test // DATAREDIS-481
public void putIfAbsentShouldReturnExistingIfExists() {
doWithConnection(connection -> connection.set(binaryCacheKey, binarySample));
ValueWrapper result = cache.putIfAbsent("key-1", "this-should-not-be-set");
assertThat(result).isNotNull();
assertThat(result.get()).isEqualTo(sample);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binarySample);
});
}
@Test // DATAREDIS-481
public void putIfAbsentShouldReturnExistingNullValueIfExists() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryNullValue));
ValueWrapper result = cache.putIfAbsent("key-1", "this-should-not-be-set");
assertThat(result).isNotNull();
assertThat(result.get()).isNull();
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryNullValue);
});
}
@Test // DATAREDIS-481
public void getShouldRetrieveEntry() {
doWithConnection(connection -> {
connection.set(binaryCacheKey, binarySample);
});
ValueWrapper result = cache.get(key);
assertThat(result).isNotNull();
assertThat(result.get()).isEqualTo(sample);
}
@Test // DATAREDIS-481
public void getShouldReturnNullWhenKeyDoesNotExist() {
assertThat(cache.get(key)).isNull();
}
@Test // DATAREDIS-481
public void getShouldReturnValueWrapperHoldingNullIfNullValueStored() {
doWithConnection(connection -> {
connection.set(binaryCacheKey, binaryNullValue);
});
ValueWrapper result = cache.get(key);
assertThat(result).isNotNull();
assertThat(result.get()).isEqualTo(null);
}
@Test // DATAREDIS-481
public void evictShouldRemoveKey() {
doWithConnection(connection -> {
connection.set(binaryCacheKey, binaryNullValue);
});
cache.evict(key);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isFalse();
});
}
@Test // DATAREDIS-481
public void getWithCallableShouldResolveValueIfNotPresent() {
assertThat(cache.get(key, () -> sample)).isEqualTo(sample);
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
assertThat(connection.get(binaryCacheKey)).isEqualTo(binarySample);
});
}
@Test // DATAREDIS-481
public void getWithCallableShouldNotResolveValueIfPresent() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryNullValue));
cache.get(key, () -> {
throw new IllegalStateException("Why call the value loader when we've got a cache entry?");
});
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryNullValue);
});
}
void doWithConnection(Consumer<RedisConnection> callback) {
RedisConnection connection = connectionFactory.getConnection();
try {
callback.accept(connection);
} finally {
connection.close();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class Person implements Serializable {
String firstame;
Date birthdate;
}
}

View File

@@ -1,340 +0,0 @@
/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.util.ClassUtils.*;
import java.util.concurrent.Callable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.cache.Cache;
import org.springframework.cache.Cache.ValueRetrievalException;
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;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* @author Christoph Strobl
* @author Mark Paluch
*/
@SuppressWarnings("rawtypes")
@RunWith(MockitoJUnitRunner.Silent.class)
public class RedisCacheUnitTests {
private static final String CACHE_NAME = "foo";
private static final String PREFIX = "prefix:";
private static final byte[] PREFIX_BYTES = "prefix:".getBytes();
private static final byte[] KNOWN_KEYS_SET_NAME_BYTES = (CACHE_NAME + "~keys").getBytes();
private static final String KEY = "key";
private static final byte[] KEY_BYTES = KEY.getBytes();
private static final byte[] KEY_WITH_PREFIX_BYTES = (PREFIX + KEY).getBytes();
private static final String VALUE = "value";
private static final byte[] VALUE_BYTES = VALUE.getBytes();
private static final byte[] NO_PREFIX_BYTES = new byte[] {};
private static final long EXPIRATION = 1000;
RedisTemplate<?, ?> templateSpy;
@Mock RedisSerializer keySerializerMock;
@Mock RedisSerializer valueSerializerMock;
@Mock RedisConnectionFactory connectionFactoryMock;
@Mock RedisConnection connectionMock;
RedisCache cache;
public @Rule ExpectedException exception = ExpectedException.none();
@SuppressWarnings("unchecked")
@Before
public void setUp() {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(connectionFactoryMock);
template.setKeySerializer(keySerializerMock);
template.setValueSerializer(valueSerializerMock);
template.afterPropertiesSet();
templateSpy = spy(template);
when(connectionFactoryMock.getConnection()).thenReturn(connectionMock);
when(keySerializerMock.serialize(any())).thenReturn(KEY_BYTES);
when(valueSerializerMock.serialize(any())).thenReturn(VALUE_BYTES);
when(valueSerializerMock.deserialize(eq(VALUE_BYTES))).thenReturn(VALUE);
}
@Test // DATAREDIS-369
public void putShouldNotKeepTrackOfKnownKeysWhenPrefixIsSet() {
cache = new RedisCache(CACHE_NAME, PREFIX_BYTES, templateSpy, EXPIRATION);
cache.put(KEY, VALUE);
verify(connectionMock).set(eq(KEY_WITH_PREFIX_BYTES), eq(VALUE_BYTES));
verify(connectionMock).expire(eq(KEY_WITH_PREFIX_BYTES), eq(EXPIRATION));
verify(connectionMock, never()).zAdd(eq(KNOWN_KEYS_SET_NAME_BYTES), eq(0D), any(byte[].class));
}
@Test // DATAREDIS-369
public void putShouldKeepTrackOfKnownKeysWhenNoPrefixIsSet() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, EXPIRATION);
cache.put(KEY, VALUE);
verify(connectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(connectionMock).expire(eq(KEY_BYTES), eq(EXPIRATION));
verify(connectionMock).zAdd(eq(KNOWN_KEYS_SET_NAME_BYTES), eq(0D), eq(KEY_BYTES));
}
@Test // DATAREDIS-369
public void clearShouldRemoveKeysUsingKnownKeysWhenNoPrefixIsSet() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, EXPIRATION);
cache.clear();
verify(connectionMock).zRange(eq(KNOWN_KEYS_SET_NAME_BYTES), eq(0L), eq(127L));
}
@Test // DATAREDIS-369
public void clearShouldCallLuaScriptToRemoveKeysWhenPrefixIsSet() {
cache = new RedisCache(CACHE_NAME, PREFIX_BYTES, templateSpy, EXPIRATION);
cache.clear();
verify(connectionMock).eval(any(byte[].class), eq(ReturnType.INTEGER), eq(0),
eq((PREFIX + "*").getBytes()));
}
@Test // DATAREDIS-402
public void putShouldNotExpireKnownKeysSetWhenTtlIsZero() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
cache.put(KEY, VALUE);
verify(connectionMock, never()).expire(eq(KNOWN_KEYS_SET_NAME_BYTES), anyLong());
}
@Test // DATAREDIS-542
public void putIfAbsentShouldExpireWhenValueWasSet() {
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(true);
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
assertThat(valueWrapper, is(nullValue()));
verify(connectionMock).setNX(KEY_BYTES, VALUE_BYTES);
verify(connectionMock).expire(eq(KEY_BYTES), anyLong());
}
@Test // DATAREDIS-542
public void putIfAbsentShouldNotExpireWhenValueWasNotSetAndRedisContainsOtherData() {
String other = "other";
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(false);
when(connectionMock.get(KEY_BYTES)).thenReturn(other.getBytes());
when(valueSerializerMock.deserialize(eq(other.getBytes()))).thenReturn(other);
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
assertThat(valueWrapper, is(notNullValue()));
verify(connectionMock, never()).expire(eq(KEY_BYTES), anyLong());
}
@Test // DATAREDIS-542
public void putIfAbsentShouldNotSetExpireWhenValueWasNotSetAndRedisContainsSameData() {
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(false);
when(connectionMock.get(KEY_BYTES)).thenReturn(VALUE_BYTES);
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
assertThat(valueWrapper, is(notNullValue()));
verify(connectionMock, never()).expire(eq(KEY_BYTES), anyLong());
}
@Test // DATAREDIS-443
@SuppressWarnings("unchecked")
public void getWithCallable() throws ClassNotFoundException {
if (isPresent("org.springframework.cache.Cache$ValueRetrievalException", getDefaultClassLoader())) {
exception.expect((Class<? extends Throwable>) forName("org.springframework.cache.Cache$ValueRetrievalException",
getDefaultClassLoader()));
} else {
exception.expect(RedisSystemException.class);
}
exception.expectMessage("Value for key 'key' could not be loaded");
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
throw new UnsupportedOperationException("Expected exception");
}
});
}
@Test(expected = ValueRetrievalException.class) // DATAREDIS-553, DATAREDIS-606
@SuppressWarnings("unchecked")
public void getWithCallableShouldThrowExceptionSotringNullWhenNotAllowingNull() 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;
}
});
}
@Test // DATAREDIS-553
@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).get(eq(KEY_BYTES));
verify(connectionMock).multi();
verify(connectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(connectionMock).exec();
}
@Test // DATAREDIS-443, DATAREDIS-592
public void getWithCallableShouldReadValueFromCallableAddToCache() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
return VALUE;
}
});
verify(connectionMock).get(eq(KEY_BYTES));
verify(connectionMock).multi();
verify(connectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(connectionMock, never()).expire(any(byte[].class), anyLong());
verify(connectionMock).exec();
}
@Test // DATAREDIS-592
public void getWithCallableShouldReadValueFromCallableAddToCacheWithTtl() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 100L);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
return VALUE;
}
});
verify(connectionMock).get(eq(KEY_BYTES));
verify(connectionMock).multi();
verify(connectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(connectionMock).expire(eq(KEY_BYTES), eq(100L));
verify(connectionMock).exec();
}
@Test // DATAREDIS-443
@SuppressWarnings("unchecked")
public void getWithCallableShouldNotReadValueFromCallableWhenAlreadyPresent() {
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
Callable<Object> callableMock = mock(Callable.class);
when(connectionMock.exists(KEY_BYTES)).thenReturn(true);
when(connectionMock.get(KEY_BYTES)).thenReturn(VALUE_BYTES);
assertThat((String) cache.get(KEY, callableMock), equalTo(VALUE));
verifyZeroInteractions(callableMock);
}
@Test // DATAREDIS-468
public void noMultiExecForCluster() {
RedisClusterConnection clusterConnectionMock = mock(RedisClusterConnection.class);
when(connectionFactoryMock.getConnection()).thenReturn(clusterConnectionMock);
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
when(connectionMock.exists(KEY_BYTES)).thenReturn(true);
when(connectionMock.get(KEY_BYTES)).thenReturn(null).thenReturn(VALUE_BYTES);
cache.put(KEY, VALUE);
verify(clusterConnectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(clusterConnectionMock, never()).multi();
verify(clusterConnectionMock, never()).exec();
verifyZeroInteractions(connectionMock);
}
@Test // DATAREDIS-468
public void getWithCallableForCluster() {
RedisClusterConnection clusterConnectionMock = mock(RedisClusterConnection.class);
when(connectionFactoryMock.getConnection()).thenReturn(clusterConnectionMock);
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 0L);
cache.get(KEY, new Callable<Object>() {
@Override
public Object call() throws Exception {
return VALUE;
}
});
verify(clusterConnectionMock).get(eq(KEY_BYTES));
verify(clusterConnectionMock).set(eq(KEY_BYTES), eq(VALUE_BYTES));
verify(clusterConnectionMock, never()).multi();
verify(clusterConnectionMock, never()).exec();
verifyZeroInteractions(connectionMock);
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
/**
* The idea would have been to provide {@link Configuration} here and just set {@link Rollback} accordingly in extending
* classes. Unfortunately this does not work when running build with gradle under java6. It was also not possible to add
* {@link Configuration} here and import it using {@link ContextConfiguration#classes()}. <br />
* <br />
* Therefore {@link ContextConfiguration} had to be duplicated in
* {@link TransactionalRedisCacheManagerWithCommitUnitTests} and
* {@link TransactionalRedisCacheManagerWithRollbackUnitTests}.
*
* @author Christoph Strobl
*/
public abstract class TransactionalRedisCacheManagerTestBase {
static class FooService {
private @Autowired BarRepository repo;
@Transactional
public String foo() {
return "foo" + repo.bar();
}
}
static class BarRepository {
@Cacheable("bar")
public String bar() {
return "bar";
}
}
}

View File

@@ -1,136 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.mockito.Mockito.*;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.hamcrest.core.IsEqual;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.BarRepository;
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.FooService;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.test.util.RelaxedJUnit4ClassRunner;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Christoph Strobl
*/
@RunWith(RelaxedJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "transactionManager")
public class TransactionalRedisCacheManagerWithCommitUnitTests {
@SuppressWarnings("rawtypes") //
protected @Autowired RedisTemplate redisTemplate;
protected @Autowired FooService transactionalService;
@Configuration
@EnableCaching
public static class Config {
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
DataSourceTransactionManager txmgr = new DataSourceTransactionManager();
txmgr.setDataSource(dataSource());
txmgr.afterPropertiesSet();
return txmgr;
}
@Bean
public DataSource dataSource() throws SQLException {
DataSource dataSourceMock = mock(DataSource.class);
when(dataSourceMock.getConnection()).thenReturn(mock(Connection.class));
return dataSourceMock;
}
@Bean
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setTransactionAware(true);
return cacheManager;
}
@SuppressWarnings({ "rawtypes" })
@Bean
public RedisTemplate redisTemplate() {
RedisConnection connectionMock = mock(RedisConnection.class);
RedisConnectionFactory factoryMock = mock(RedisConnectionFactory.class);
when(factoryMock.getConnection()).thenReturn(connectionMock);
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factoryMock);
return template;
}
@Bean
public FooService fooService() {
return new FooService();
}
@Bean
public BarRepository barRepository() {
return new BarRepository();
}
}
@AfterTransaction
public void assertThatValuesHaveBeenAddedToRedis() {
ArgumentCaptor<byte[]> keyCaptor = ArgumentCaptor.forClass(byte[].class);
ArgumentCaptor<byte[]> valueCaptor = ArgumentCaptor.forClass(byte[].class);
verify(redisTemplate.getConnectionFactory().getConnection(), times(1)).zAdd(keyCaptor.capture(), eq(0D),
valueCaptor.capture());
Assert.assertThat(new StringRedisSerializer().deserialize(keyCaptor.getValue()).toString(),
IsEqual.equalTo("bar~keys"));
}
@Rollback(false)
@Test // DATAREDIS-246
public void testValuesAddedToCacheWhenTransactionIsCommited() {
transactionalService.foo();
}
}

View File

@@ -1,127 +0,0 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.cache;
import static org.mockito.Mockito.*;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.BarRepository;
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.FooService;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.test.util.RelaxedJUnit4ClassRunner;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Christoph Strobl
*/
@RunWith(RelaxedJUnit4ClassRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "transactionManager")
public class TransactionalRedisCacheManagerWithRollbackUnitTests {
@SuppressWarnings("rawtypes") //
protected @Autowired RedisTemplate redisTemplate;
protected @Autowired FooService transactionalService;
@Configuration
@EnableCaching
public static class Config {
@Bean
public PlatformTransactionManager transactionManager() throws SQLException {
DataSourceTransactionManager txmgr = new DataSourceTransactionManager();
txmgr.setDataSource(dataSource());
txmgr.afterPropertiesSet();
return txmgr;
}
@Bean
public DataSource dataSource() throws SQLException {
DataSource dataSourceMock = mock(DataSource.class);
when(dataSourceMock.getConnection()).thenReturn(mock(Connection.class));
return dataSourceMock;
}
@Bean
public CacheManager cacheManager() {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setTransactionAware(true);
return cacheManager;
}
@SuppressWarnings({ "rawtypes" })
@Bean
public RedisTemplate redisTemplate() {
RedisConnection connectionMock = mock(RedisConnection.class);
RedisConnectionFactory factoryMock = mock(RedisConnectionFactory.class);
when(factoryMock.getConnection()).thenReturn(connectionMock);
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(factoryMock);
return template;
}
@Bean
public FooService fooService() {
return new FooService();
}
@Bean
public BarRepository barRepository() {
return new BarRepository();
}
}
@AfterTransaction
public void assertThatValuesNeverAddedToRedis() {
verify(redisTemplate.getConnectionFactory().getConnection(), times(0)).zAdd(any(byte[].class), eq(0D),
any(byte[].class));
}
@Rollback(true)
@Test // DATAREDIS-246
public void tesValuesNotAddedToCacheWhenTransactionIsRolledBack() {
transactionalService.foo();
}
}