diff --git a/src/main/java/org/springframework/data/redis/core/RedisOperations.java b/src/main/java/org/springframework/data/redis/core/RedisOperations.java index 819ee745c..75cb2190a 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisOperations.java +++ b/src/main/java/org/springframework/data/redis/core/RedisOperations.java @@ -169,6 +169,8 @@ public interface RedisOperations { Long getExpire(K key); + Long getExpire(K key, TimeUnit timeUnit); + void watch(K keys); void watch(Collection keys); diff --git a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java index 4491d7f0f..9c05a16d8 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisTemplate.java +++ b/src/main/java/org/springframework/data/redis/core/RedisTemplate.java @@ -638,27 +638,35 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation } - public Boolean expire(K key, long timeout, TimeUnit unit) { + public Boolean expire(K key, final long timeout, final TimeUnit unit) { final byte[] rawKey = rawKey(key); - final long rawTimeout = TimeoutUtils.toSeconds(timeout, unit); + final long rawTimeout = TimeoutUtils.toMillis(timeout, unit); return execute(new RedisCallback() { public Boolean doInRedis(RedisConnection connection) { - return connection.expire(rawKey, rawTimeout); + try { + return connection.pExpire(rawKey, rawTimeout); + } catch(Exception e) { + // Driver may not support pExpire or we may be running on Redis 2.4 + return connection.expire(rawKey, TimeoutUtils.toSeconds(timeout, unit)); + } } }, true); } - public Boolean expireAt(K key, Date date) { + public Boolean expireAt(K key, final Date date) { final byte[] rawKey = rawKey(key); - final long rawTimeout = date.getTime() / 1000; return execute(new RedisCallback() { public Boolean doInRedis(RedisConnection connection) { - return connection.expireAt(rawKey, rawTimeout); + try { + return connection.pExpireAt(rawKey, date.getTime()); + } catch(Exception e) { + return connection.expireAt(rawKey, date.getTime() / 1000); + } } }, true); } @@ -696,6 +704,22 @@ public class RedisTemplate extends RedisAccessor implements RedisOperation }, true); } + public Long getExpire(K key, final TimeUnit timeUnit) { + final byte[] rawKey = rawKey(key); + + return execute(new RedisCallback() { + + public Long doInRedis(RedisConnection connection) { + try { + return timeUnit.convert(connection.pTtl(rawKey), TimeUnit.MILLISECONDS); + } catch(Exception e) { + // Driver may not support pTtl or we may be running on Redis 2.4 + return timeUnit.convert(connection.ttl(rawKey), TimeUnit.SECONDS); + } + } + }, true); + } + @SuppressWarnings("unchecked") public Set keys(K pattern) { final byte[] rawKey = rawKey(pattern); diff --git a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java index 504ec1ca6..3cb28c386 100644 --- a/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java +++ b/src/test/java/org/springframework/data/redis/core/RedisTemplateTests.java @@ -53,6 +53,7 @@ import org.springframework.data.redis.TestCondition; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.StringRedisConnection; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.srp.SrpConnectionFactory; import org.springframework.data.redis.core.ZSetOperations.TypedTuple; import org.springframework.data.redis.core.query.SortQueryBuilder; @@ -430,14 +431,13 @@ public class RedisTemplateTests { } @Test - public void testExpireAndGetExpire() { - assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "2.6") && - RedisTestProfileValueSource.matches("runLongTests", "true")); + public void testExpireAndGetExpireMillis() { final K key1 = keyFactory.instance(); V value1 = valueFactory.instance(); redisTemplate.boundValueOps(key1).set(value1); - redisTemplate.expire(key1, 1, TimeUnit.SECONDS); - assertTrue(redisTemplate.getExpire(key1) > 0l); + redisTemplate.expire(key1, 10, TimeUnit.MILLISECONDS); + assertTrue(redisTemplate.getExpire(key1, TimeUnit.MILLISECONDS) > 0l); + // Timeout is longer because expire will be 1 sec if pExpire not supported waitFor(new TestCondition() { public boolean passes() { return (!redisTemplate.hasKey(key1)); @@ -445,9 +445,70 @@ public class RedisTemplateTests { }, 1500l); } + @Test + public void testExpireMillisNotSupported() throws Exception { + assumeTrue(RedisTestProfileValueSource.matches("runLongTests", "true")); + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + assumeTrue(key1 instanceof String && value1 instanceof String); + // Jedis does not support pExpire + JedisConnectionFactory factory = new JedisConnectionFactory(); + factory.setHostName(SettingsUtils.getHost()); + factory.setPort(SettingsUtils.getPort()); + factory.afterPropertiesSet(); + final StringRedisTemplate template2 = new StringRedisTemplate(factory); + template2.boundValueOps((String)key1).set((String)value1); + template2.expire((String)key1, 10, TimeUnit.MILLISECONDS); + Thread.sleep(15); + // 10 millis should get rounded up to 1 sec if pExpire not supported + assertTrue(template2.hasKey((String)key1)); + waitFor(new TestCondition() { + public boolean passes() { + return (!template2.hasKey((String)key1)); + } + }, 1000l); + } + + @Test + public void testGetExpireNoTimeUnit() { + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + redisTemplate.boundValueOps(key1).set(value1); + redisTemplate.expire(key1, 2, TimeUnit.SECONDS); + Long expire = redisTemplate.getExpire(key1); + // Default behavior is to return seconds + assertTrue(expire > 0l && expire <= 2l); + } + + @Test + public void testGetExpireSeconds() { + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + redisTemplate.boundValueOps(key1).set(value1); + redisTemplate.expire(key1, 1500, TimeUnit.MILLISECONDS); + assertEquals(Long.valueOf(1), redisTemplate.getExpire(key1, TimeUnit.SECONDS)); + } + + @Test + public void testGetExpireMillisNotSupported() { + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + assumeTrue(key1 instanceof String && value1 instanceof String); + // Jedis does not support pTtl + JedisConnectionFactory factory = new JedisConnectionFactory(); + factory.setHostName(SettingsUtils.getHost()); + factory.setPort(SettingsUtils.getPort()); + factory.afterPropertiesSet(); + final StringRedisTemplate template2 = new StringRedisTemplate(factory); + template2.boundValueOps((String)key1).set((String)value1); + template2.expire((String)key1, 5, TimeUnit.SECONDS); + long expire = template2.getExpire((String)key1, TimeUnit.MILLISECONDS); + // we should still get expire in milliseconds if requested + assertTrue(expire > 1000 && expire <= 5000); + } + @Test public void testExpireAt() { - assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "2.6")); final K key1 = keyFactory.instance(); V value1 = valueFactory.instance(); redisTemplate.boundValueOps(key1).set(value1); @@ -459,16 +520,38 @@ public class RedisTemplateTests { }, 5l); } + @Test + public void testExpireAtMillisNotSupported() { + assumeTrue(RedisTestProfileValueSource.matches("runLongTests", "true")); + final K key1 = keyFactory.instance(); + V value1 = valueFactory.instance(); + assumeTrue(key1 instanceof String && value1 instanceof String); + // Jedis does not support pExpireAt + JedisConnectionFactory factory = new JedisConnectionFactory(); + factory.setHostName(SettingsUtils.getHost()); + factory.setPort(SettingsUtils.getPort()); + factory.afterPropertiesSet(); + final StringRedisTemplate template2 = new StringRedisTemplate(factory); + template2.boundValueOps((String)key1).set((String)value1); + template2.expireAt((String)key1, new Date(System.currentTimeMillis() + 5l)); + // Just ensure this works as expected, pExpireAt just adds some precision over expireAt + waitFor(new TestCondition() { + public boolean passes() { + return (!template2.hasKey((String)key1)); + } + }, 5l); + } + @Test public void testPersist() throws Exception { - assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "2.6") && - RedisTestProfileValueSource.matches("runLongTests", "true")); + // Test is meaningless in Redis 2.4 because key won't expire after 10 ms + assumeTrue(RedisTestProfileValueSource.matches("redisVersion", "2.6")); final K key1 = keyFactory.instance(); V value1 = valueFactory.instance(); redisTemplate.opsForValue().set(key1, value1); - redisTemplate.expire(key1, 1, TimeUnit.SECONDS); + redisTemplate.expire(key1, 10, TimeUnit.MILLISECONDS); redisTemplate.persist(key1); - Thread.sleep(1500); + Thread.sleep(10); assertTrue(redisTemplate.hasKey(key1)); }