DATAREDIS-481 - Polishing.
Reduce visibility of DefaultRedisCacheWriter as trivial implementation. Replace ConnectionCallback with Java 8 functions. Increase visibility of the RedisCache constructor to allow subclassing. Move factory methods to create DefaultRedisCacheWriter to RedisCacheWriter interface. Refactor RedisCacheManagerConfigurator to stateful RedisCacheManagerBuilder. Remove RedisCache.setTransactionAware override in RedisCacheManagerConfigurator removing behavior not compliant with AbstractTransactionSupportingCacheManager. Terminate interrupted cache wait loop with exception. Javadoc, formatting, reorder methods. Allow ConversionService configuration via RedisCacheConfiguration. Accept cache keys that are either convertible to String or that override toString to obtain the String representation of the cache key. Original pull request: #252.
This commit is contained in:
@@ -16,12 +16,15 @@
|
||||
package org.springframework.data.redis.cache;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.redis.cache.DefaultRedisCacheWriter.*;
|
||||
import static org.springframework.data.redis.cache.RedisCacheWriter.*;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
@@ -39,6 +42,7 @@ import org.springframework.data.redis.core.types.Expiration;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class DefaultRedisCacheWriterTests {
|
||||
@@ -47,9 +51,9 @@ public class DefaultRedisCacheWriterTests {
|
||||
|
||||
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"));
|
||||
byte[] binaryCacheKey = cacheKey.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] binaryCacheValue = "value".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
RedisConnectionFactory connectionFactory;
|
||||
|
||||
@@ -211,7 +215,7 @@ public class DefaultRedisCacheWriterTests {
|
||||
@Test // DATAREDIS-481
|
||||
public void nonLockingCacheWriterShouldIgnoreExistingLock() {
|
||||
|
||||
lockingRedisCacheWriter(connectionFactory).lock(CACHE_NAME);
|
||||
((DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory)).lock(CACHE_NAME);
|
||||
|
||||
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
|
||||
|
||||
@@ -223,7 +227,7 @@ public class DefaultRedisCacheWriterTests {
|
||||
@Test // DATAREDIS-481
|
||||
public void lockingCacheWriterShouldIgnoreExistingLockOnDifferenceCache() {
|
||||
|
||||
lockingRedisCacheWriter(connectionFactory).lock(CACHE_NAME);
|
||||
((DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory)).lock(CACHE_NAME);
|
||||
|
||||
lockingRedisCacheWriter(connectionFactory).put(CACHE_NAME + "-no-the-other-cache", binaryCacheKey, binaryCacheValue,
|
||||
Duration.ZERO);
|
||||
@@ -236,15 +240,25 @@ public class DefaultRedisCacheWriterTests {
|
||||
@Test // DATAREDIS-481
|
||||
public void lockingCacheWriterShouldWaitForLockRelease() throws InterruptedException {
|
||||
|
||||
DefaultRedisCacheWriter cw = lockingRedisCacheWriter(connectionFactory);
|
||||
DefaultRedisCacheWriter cw = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory);
|
||||
cw.lock(CACHE_NAME);
|
||||
|
||||
CountDownLatch beforeWrite = new CountDownLatch(1);
|
||||
CountDownLatch afterWrite = new CountDownLatch(1);
|
||||
|
||||
Thread th = new Thread(() -> {
|
||||
lockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
|
||||
|
||||
RedisCacheWriter writer = lockingRedisCacheWriter(connectionFactory);
|
||||
beforeWrite.countDown();
|
||||
writer.put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
|
||||
afterWrite.countDown();
|
||||
});
|
||||
th.start();
|
||||
|
||||
try {
|
||||
|
||||
beforeWrite.await();
|
||||
|
||||
Thread.sleep(200);
|
||||
|
||||
doWithConnection(connection -> {
|
||||
@@ -253,8 +267,7 @@ public class DefaultRedisCacheWriterTests {
|
||||
|
||||
cw.unlock(CACHE_NAME);
|
||||
|
||||
Thread.sleep(200);
|
||||
|
||||
afterWrite.await();
|
||||
doWithConnection(connection -> {
|
||||
assertThat(connection.exists(binaryCacheKey)).isTrue();
|
||||
});
|
||||
@@ -263,7 +276,49 @@ public class DefaultRedisCacheWriterTests {
|
||||
}
|
||||
}
|
||||
|
||||
void doWithConnection(Consumer<RedisConnection> callback) {
|
||||
@Test // DATAREDIS-481
|
||||
public void lockingCacheWriterShouldExitWhenInterruptedWaitForLockRelease() throws InterruptedException {
|
||||
|
||||
DefaultRedisCacheWriter cw = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory);
|
||||
cw.lock(CACHE_NAME);
|
||||
|
||||
CountDownLatch beforeWrite = new CountDownLatch(1);
|
||||
CountDownLatch afterWrite = new CountDownLatch(1);
|
||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
||||
|
||||
Thread th = new Thread(() -> {
|
||||
|
||||
DefaultRedisCacheWriter writer = new DefaultRedisCacheWriter(connectionFactory, Duration.ofMillis(50)) {
|
||||
|
||||
@Override
|
||||
boolean doCheckLock(String name, RedisConnection connection) {
|
||||
beforeWrite.countDown();
|
||||
return super.doCheckLock(name, connection);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
writer.put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
|
||||
} catch (Exception e) {
|
||||
exceptionRef.set(e);
|
||||
} finally {
|
||||
afterWrite.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
th.start();
|
||||
beforeWrite.await();
|
||||
|
||||
th.interrupt();
|
||||
|
||||
afterWrite.await();
|
||||
|
||||
assertThat(exceptionRef.get()).hasMessageContaining("Interrupted while waiting to unlock")
|
||||
.hasCauseInstanceOf(InterruptedException.class);
|
||||
}
|
||||
|
||||
private void doWithConnection(Consumer<RedisConnection> callback) {
|
||||
|
||||
RedisConnection connection = connectionFactory.getConnection();
|
||||
try {
|
||||
callback.accept(connection);
|
||||
@@ -271,5 +326,4 @@ public class DefaultRedisCacheWriterTests {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RedisCacheManager}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RedisCacheManagerUnitTests {
|
||||
@@ -42,17 +43,39 @@ public class RedisCacheManagerUnitTests {
|
||||
|
||||
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
|
||||
|
||||
RedisCacheManager cm = RedisCacheManager.usingCacheWriter(cacheWriter).withCacheDefaults(configuration).createAndGet();
|
||||
RedisCacheManager cm = RedisCacheManager.builder(cacheWriter).cacheDefaults(configuration).build();
|
||||
cm.afterPropertiesSet();
|
||||
|
||||
assertThat(cm.getMissingCache("new-cache").getCacheConfiguration()).isEqualTo(configuration);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-481
|
||||
public void appliesDefaultConfigurationToInitialCache() {
|
||||
|
||||
RedisCacheConfiguration withPrefix = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
|
||||
RedisCacheConfiguration withoutPrefix = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
|
||||
|
||||
RedisCacheManager cm = RedisCacheManager.builder(cacheWriter).cacheDefaults(withPrefix) //
|
||||
.initialCacheNames(Collections.singleton("first-cache")) //
|
||||
.cacheDefaults(withoutPrefix) //
|
||||
.initialCacheNames(Collections.singleton("second-cache")) //
|
||||
.build();
|
||||
|
||||
cm.afterPropertiesSet();
|
||||
|
||||
assertThat(((RedisCache) cm.getCache("first-cache")).getCacheConfiguration()).isEqualTo(withPrefix);
|
||||
assertThat(((RedisCache) cm.getCache("second-cache")).getCacheConfiguration()).isEqualTo(withoutPrefix);
|
||||
assertThat(((RedisCache) cm.getCache("other-cache")).getCacheConfiguration()).isEqualTo(withoutPrefix);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-481
|
||||
public void predefinedCacheShouldBeCreatedWithSpecificConfig() {
|
||||
|
||||
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().disableKeyPrefix();
|
||||
|
||||
RedisCacheManager cm = RedisCacheManager.usingCacheWriter(cacheWriter)
|
||||
.withInitialCacheConfigurations(Collections.singletonMap("predefined-cache", configuration)).createAndGet();
|
||||
RedisCacheManager cm = RedisCacheManager.builder(cacheWriter)
|
||||
.withInitialCacheConfigurations(Collections.singletonMap("predefined-cache", configuration)).build();
|
||||
cm.afterPropertiesSet();
|
||||
|
||||
assertThat(((RedisCache) cm.getCache("predefined-cache")).getCacheConfiguration()).isEqualTo(configuration);
|
||||
assertThat(cm.getMissingCache("new-cache").getCacheConfiguration()).isNotEqualTo(configuration);
|
||||
@@ -61,20 +84,9 @@ public class RedisCacheManagerUnitTests {
|
||||
@Test // DATAREDIS-481
|
||||
public void transactionAwareCacheManagerShouldDecoracteCache() {
|
||||
|
||||
Cache cache = RedisCacheManager.usingCacheWriter(cacheWriter).transactionAware().createAndGet()
|
||||
.getCache("decoracted-cache");
|
||||
Cache cache = RedisCacheManager.builder(cacheWriter).transactionAware().build().getCache("decoracted-cache");
|
||||
|
||||
assertThat(cache).isInstanceOfAny(TransactionAwareCacheDecorator.class);
|
||||
assertThat(ReflectionTestUtils.getField(cache, "targetCache")).isInstanceOf(RedisCache.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
|
||||
return RedisCacheManager.usingRawConnectionFactory(connectionFactory)
|
||||
.withCacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
|
||||
.withInitialCacheConfigurations(Collections.singletonMap("predefined", RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
|
||||
.transactionAware()
|
||||
.createAndGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -34,6 +35,7 @@ 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.interceptor.SimpleKey;
|
||||
import org.springframework.cache.support.NullValue;
|
||||
import org.springframework.data.redis.ConnectionFactoryTracker;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
@@ -41,13 +43,13 @@ 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
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class RedisCacheTests {
|
||||
@@ -59,8 +61,7 @@ public class RedisCacheTests {
|
||||
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);
|
||||
byte[] binaryNullValue = new JdkSerializationRedisSerializer().serialize(NullValue.INSTANCE);
|
||||
|
||||
RedisConnectionFactory connectionFactory;
|
||||
RedisSerializer serializer;
|
||||
@@ -179,6 +180,38 @@ public class RedisCacheTests {
|
||||
assertThat(result.get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-481
|
||||
public void shouldReadAndWriteSimpleCacheKey() {
|
||||
|
||||
SimpleKey key = new SimpleKey("param-1", "param-2");
|
||||
|
||||
cache.put(key, sample);
|
||||
|
||||
ValueWrapper result = cache.get(key);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class) // DATAREDIS-481
|
||||
public void shouldRejectNonInvalidKey() {
|
||||
|
||||
InvalidKey key = new InvalidKey(sample.getFirstame(), sample.getBirthdate());
|
||||
|
||||
cache.put(key, sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-481
|
||||
public void shouldAllowComplexKeyWithToStringMethod() {
|
||||
|
||||
ComplexKey key = new ComplexKey(sample.getFirstame(), sample.getBirthdate());
|
||||
|
||||
cache.put(key, sample);
|
||||
|
||||
ValueWrapper result = cache.get(key);
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.get()).isEqualTo(sample);
|
||||
}
|
||||
|
||||
@Test // DATAREDIS-481
|
||||
public void getShouldReturnNullWhenKeyDoesNotExist() {
|
||||
assertThat(cache.get(key)).isNull();
|
||||
@@ -253,4 +286,16 @@ public class RedisCacheTests {
|
||||
Date birthdate;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor // toString not overridden
|
||||
static class InvalidKey implements Serializable {
|
||||
final String firstame;
|
||||
final Date birthdate;
|
||||
}
|
||||
|
||||
@Data
|
||||
@RequiredArgsConstructor
|
||||
static class ComplexKey implements Serializable {
|
||||
final String firstame;
|
||||
final Date birthdate;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user