Skip to content

Commit ea598c2

Browse files
mp911dechristophstrobl
authored andcommitted
DATAREDIS-542 - Fix key expiration for RedisCache.putIfAbsent.
RedisCache now expires keys using putIfAbsent if the key was set. Previously, the key was only expired if the value was already present and the value matched the value stored inside of Redis. Original Pull Request: spring-projects#224
1 parent 0b9b180 commit ea598c2

File tree

2 files changed

+72
-10
lines changed

2 files changed

+72
-10
lines changed

src/main/java/org/springframework/data/redis/cache/RedisCache.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.data.redis.cache;
1818

1919
import static org.springframework.util.Assert.*;
20-
import static org.springframework.util.ObjectUtils.*;
2120

2221
import java.lang.reflect.Constructor;
2322
import java.util.Arrays;
@@ -37,6 +36,7 @@
3736
import org.springframework.data.redis.serializer.RedisSerializer;
3837
import org.springframework.data.redis.serializer.StringRedisSerializer;
3938
import org.springframework.util.ClassUtils;
39+
import org.springframework.util.ObjectUtils;
4040

4141
/**
4242
* Cache implementation on top of Redis.
@@ -720,20 +720,27 @@ public RedisCachePutIfAbsentCallback(BinaryRedisCacheElement element, RedisCache
720720
public byte[] doInRedis(BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
721721

722722
waitForLock(connection);
723-
byte[] resultValue = put(element, connection);
724723

725-
if (nullSafeEquals(element.get(), resultValue)) {
724+
byte existingValue[] = null;
725+
726+
boolean keyMaintenance;
727+
728+
byte[] keyBytes = element.getKeyBytes();
729+
byte[] value = element.get();
730+
731+
if (connection.setNX(keyBytes, value)) {
732+
keyMaintenance = true;
733+
} else {
734+
existingValue = connection.get(keyBytes);
735+
keyMaintenance = ObjectUtils.nullSafeEquals(value, existingValue);
736+
}
737+
738+
if (keyMaintenance) {
726739
processKeyExpiration(element, connection);
727740
maintainKnownKeys(element, connection);
728741
}
729742

730-
return resultValue;
731-
}
732-
733-
private byte[] put(BinaryRedisCacheElement element, RedisConnection connection) {
734-
735-
boolean valueWasSet = connection.setNX(element.getKeyBytes(), element.get());
736-
return valueWasSet ? null : connection.get(element.getKeyBytes());
743+
return existingValue;
737744
}
738745
}
739746

src/test/java/org/springframework/data/redis/cache/RedisCacheUnitTests.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.redis.cache;
1717

18+
import static org.hamcrest.Matchers.is;
19+
import static org.hamcrest.Matchers.notNullValue;
20+
import static org.hamcrest.Matchers.nullValue;
1821
import static org.hamcrest.core.IsEqual.*;
1922
import static org.junit.Assert.*;
2023
import static org.mockito.Matchers.*;
@@ -30,6 +33,7 @@
3033
import org.junit.runner.RunWith;
3134
import org.mockito.Mock;
3235
import org.mockito.runners.MockitoJUnitRunner;
36+
import org.springframework.cache.Cache;
3337
import org.springframework.data.redis.RedisSystemException;
3438
import org.springframework.data.redis.connection.RedisClusterConnection;
3539
import org.springframework.data.redis.connection.RedisConnection;
@@ -40,6 +44,7 @@
4044

4145
/**
4246
* @author Christoph Strobl
47+
* @author Mark Paluch
4348
*/
4449
@SuppressWarnings("rawtypes")
4550
@RunWith(MockitoJUnitRunner.class)
@@ -154,6 +159,56 @@ public void putShouldNotExpireKnownKeysSetWhenTtlIsZero() {
154159
verify(connectionMock, never()).expire(eq(KNOWN_KEYS_SET_NAME_BYTES), anyLong());
155160
}
156161

162+
/**
163+
* @see DATAREDIS-542
164+
*/
165+
@Test
166+
public void putIfAbsentShouldExpireWhenValueWasSet() {
167+
168+
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(true);
169+
170+
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
171+
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
172+
173+
assertThat(valueWrapper, is(nullValue()));
174+
verify(connectionMock).setNX(KEY_BYTES, VALUE_BYTES);
175+
verify(connectionMock).expire(eq(KEY_BYTES), anyLong());
176+
}
177+
178+
/**
179+
* @see DATAREDIS-542
180+
*/
181+
@Test
182+
public void putIfAbsentShouldNotExpireWhenValueWasNotSetAndRedisContainsOtherData() {
183+
184+
String other = "other";
185+
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(false);
186+
when(connectionMock.get(KEY_BYTES)).thenReturn(other.getBytes());
187+
when(valueSerializerMock.deserialize(eq(other.getBytes()))).thenReturn(other);
188+
189+
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
190+
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
191+
192+
assertThat(valueWrapper, is(notNullValue()));
193+
verify(connectionMock, never()).expire(eq(KEY_BYTES), anyLong());
194+
}
195+
196+
/**
197+
* @see DATAREDIS-542
198+
*/
199+
@Test
200+
public void putIfAbsentShouldExpireWhenValueWasNotSetAndRedisContainsSameData() {
201+
202+
when(connectionMock.setNX(KEY_BYTES, VALUE_BYTES)).thenReturn(false);
203+
when(connectionMock.get(KEY_BYTES)).thenReturn(VALUE_BYTES);
204+
205+
cache = new RedisCache(CACHE_NAME, NO_PREFIX_BYTES, templateSpy, 10L);
206+
Cache.ValueWrapper valueWrapper = cache.putIfAbsent(KEY, VALUE);
207+
208+
assertThat(valueWrapper, is(notNullValue()));
209+
verify(connectionMock).expire(eq(KEY_BYTES), anyLong());
210+
}
211+
157212
/**
158213
* @see DATAREDIS-443
159214
*/

0 commit comments

Comments
 (0)