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:
committed by
Mark Paluch
parent
eb3ad1e351
commit
a9c2b1cdda
@@ -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));
|
||||
}
|
||||
}
|
||||
151
src/test/java/org/springframework/data/redis/cache/CacheTestParams.java
vendored
Normal file
151
src/test/java/org/springframework/data/redis/cache/CacheTestParams.java
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
275
src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java
vendored
Normal file
275
src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
256
src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java
vendored
Normal file
256
src/test/java/org/springframework/data/redis/cache/RedisCacheTests.java
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user