Skip to content

Commit c4ddc9e

Browse files
christophstroblmp911de
authored andcommitted
DATAREDIS-523 - Read back TTL into property.
We now read back TTL value from Redis for explicitly @timetolive annotated properties. Original pull request: #208.
1 parent e8a30a7 commit c4ddc9e

File tree

5 files changed

+164
-18
lines changed

5 files changed

+164
-18
lines changed

src/main/asciidoc/reference/redis-repositories.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ public class TimeToLiveOnMethod {
453453
----
454454
====
455455

456+
NOTE: Annotating a property explicitly with `@TimeToLive` will read back the actual `TTL` or `PTTL` value from Redis. -1 indicates that the object has no expire associated.
456457

457458
The repository implementation ensures subscription to http://redis.io/topics/notifications[Redis keyspace notifications] via `RedisMessageListenerContainer`.
458459

src/main/java/org/springframework/data/redis/core/RedisKeyValueAdapter.java

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.Map;
2424
import java.util.Map.Entry;
2525
import java.util.Set;
26+
import java.util.concurrent.TimeUnit;
2627
import java.util.concurrent.atomic.AtomicReference;
2728

2829
import org.slf4j.Logger;
@@ -39,6 +40,7 @@
3940
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
4041
import org.springframework.data.keyvalue.core.KeyValueAdapter;
4142
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
43+
import org.springframework.data.mapping.PersistentProperty;
4244
import org.springframework.data.redis.connection.Message;
4345
import org.springframework.data.redis.connection.MessageListener;
4446
import org.springframework.data.redis.connection.RedisConnection;
@@ -58,6 +60,7 @@
5860
import org.springframework.data.redis.util.ByteUtils;
5961
import org.springframework.data.util.CloseableIterator;
6062
import org.springframework.util.Assert;
63+
import org.springframework.util.NumberUtils;
6164
import org.springframework.util.ObjectUtils;
6265

6366
/**
@@ -295,7 +298,7 @@ public Map<byte[], byte[]> doInRedis(RedisConnection connection) throws DataAcce
295298
data.setId(stringId);
296299
data.setKeyspace(stringKeyspace);
297300

298-
return converter.read(type, data);
301+
return readBackTimeToLiveIfSet(binId, converter.read(type, data));
299302
}
300303

301304
/*
@@ -346,29 +349,18 @@ public List<?> getAllOf(final Serializable keyspace) {
346349

347350
final byte[] binKeyspace = toBytes(keyspace);
348351

349-
List<Map<byte[], byte[]>> raw = redisOps.execute(new RedisCallback<List<Map<byte[], byte[]>>>() {
352+
Set<byte[]> ids = redisOps.execute(new RedisCallback<Set<byte[]>>() {
350353

351354
@Override
352-
public List<Map<byte[], byte[]>> doInRedis(RedisConnection connection) throws DataAccessException {
353-
354-
final List<Map<byte[], byte[]>> rawData = new ArrayList<Map<byte[], byte[]>>();
355-
356-
Set<byte[]> members = connection.sMembers(binKeyspace);
357-
358-
for (byte[] id : members) {
359-
rawData.add(connection
360-
.hGetAll(createKey(asString(keyspace), getConverter().getConversionService().convert(id, String.class))));
361-
}
362-
363-
return rawData;
355+
public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
356+
return connection.sMembers(binKeyspace);
364357
}
365358
});
366359

367-
List<Object> result = new ArrayList<Object>(raw.size());
368-
for (Map<byte[], byte[]> rawData : raw) {
369-
result.add(converter.read(Object.class, new RedisData(rawData)));
360+
List<Object> result = new ArrayList<Object>();
361+
for (byte[] key : ids) {
362+
result.add(get(key, keyspace));
370363
}
371-
372364
return result;
373365
}
374366

@@ -579,6 +571,49 @@ public byte[] toBytes(Object source) {
579571
return converter.getConversionService().convert(source, byte[].class);
580572
}
581573

574+
/**
575+
* Read back and set {@link TimeToLive} for the property.
576+
*
577+
* @param key
578+
* @param target
579+
* @return
580+
*/
581+
@SuppressWarnings({ "unchecked", "rawtypes" })
582+
private <T> T readBackTimeToLiveIfSet(final byte[] key, T target) {
583+
584+
if (target == null || key == null) {
585+
return target;
586+
}
587+
588+
RedisPersistentEntity<?> entity = this.converter.getMappingContext().getPersistentEntity(target.getClass());
589+
if (entity.hasExplictTimeToLiveProperty()) {
590+
591+
PersistentProperty<?> ttlProperty = entity.getExplicitTimeToLiveProperty();
592+
593+
final TimeToLive ttl = ttlProperty.findAnnotation(TimeToLive.class);
594+
595+
Long timeout = redisOps.execute(new RedisCallback<Long>() {
596+
597+
@Override
598+
public Long doInRedis(RedisConnection connection) throws DataAccessException {
599+
600+
if (ObjectUtils.nullSafeEquals(TimeUnit.SECONDS, ttl.unit())) {
601+
return connection.ttl(key);
602+
}
603+
604+
return connection.pTtl(key, ttl.unit());
605+
}
606+
});
607+
608+
if (timeout != null || (timeout == null && !ttlProperty.getType().isPrimitive())) {
609+
entity.getPropertyAccessor(target).setProperty(ttlProperty,
610+
NumberUtils.convertNumberToTargetClass(timeout, (Class) ttlProperty.getType()));
611+
}
612+
}
613+
614+
return target;
615+
}
616+
582617
/**
583618
* Configure usage of {@link KeyExpirationEventMessageListener}.
584619
*

src/main/java/org/springframework/data/redis/core/mapping/BasicRedisPersistentEntity.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
2121
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
2222
import org.springframework.data.mapping.model.MappingException;
23+
import org.springframework.data.redis.core.TimeToLive;
2324
import org.springframework.data.redis.core.TimeToLiveAccessor;
2425
import org.springframework.data.util.TypeInformation;
2526
import org.springframework.util.Assert;
@@ -59,6 +60,24 @@ public TimeToLiveAccessor getTimeToLiveAccessor() {
5960
return this.timeToLiveAccessor;
6061
}
6162

63+
/*
64+
* (non-Javadoc)
65+
* @see org.springframework.data.redis.core.mapping.RedisPersistentEntity#hasExplictTimeToLiveProperty()
66+
*/
67+
@Override
68+
public boolean hasExplictTimeToLiveProperty() {
69+
return getExplicitTimeToLiveProperty() != null;
70+
}
71+
72+
/*
73+
* (non-Javadoc)
74+
* @see org.springframework.data.redis.core.mapping.RedisPersistentEntity#getExplicitTimeToLiveProperty()
75+
*/
76+
@Override
77+
public RedisPersistentProperty getExplicitTimeToLiveProperty() {
78+
return (RedisPersistentProperty) this.getPersistentProperty(TimeToLive.class);
79+
}
80+
6281
/*
6382
* (non-Javadoc)
6483
* @see org.springframework.data.mapping.model.BasicPersistentEntity#returnPropertyIfBetterIdPropertyCandidateOrNull(org.springframework.data.mapping.PersistentProperty)

src/main/java/org/springframework/data/redis/core/mapping/RedisPersistentEntity.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
1919
import org.springframework.data.mapping.PersistentEntity;
20+
import org.springframework.data.mapping.PersistentProperty;
2021
import org.springframework.data.redis.core.TimeToLiveAccessor;
2122

2223
/**
@@ -35,4 +36,18 @@ public interface RedisPersistentEntity<T> extends KeyValuePersistentEntity<T> {
3536
*/
3637
TimeToLiveAccessor getTimeToLiveAccessor();
3738

39+
/**
40+
* @return {@literal true} when a property is annotated with {@link org.springframework.data.redis.core.TimeToLive}.
41+
* @since 1.8
42+
*/
43+
boolean hasExplictTimeToLiveProperty();
44+
45+
/**
46+
* Get the {@link PersistentProperty}
47+
*
48+
* @return can be {@null}.
49+
* @since 1.8
50+
*/
51+
PersistentProperty<? extends PersistentProperty<?>> getExplicitTimeToLiveProperty();
52+
3853
}

src/test/java/org/springframework/data/redis/core/RedisKeyValueTemplateTests.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.redis.core;
1717

1818
import static org.hamcrest.CoreMatchers.*;
19+
import static org.hamcrest.number.IsCloseTo.*;
1920
import static org.junit.Assert.*;
2021

2122
import java.util.ArrayList;
@@ -43,6 +44,7 @@
4344
import org.springframework.data.redis.core.mapping.RedisMappingContext;
4445
import org.springframework.util.ObjectUtils;
4546

47+
import lombok.Data;
4648
import lombok.EqualsAndHashCode;
4749

4850
/**
@@ -865,6 +867,72 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
865867
});
866868
}
867869

870+
/**
871+
* @see DATAREDIS-523
872+
*/
873+
@Test
874+
public void shouldReadBackExplicitTimeToLive() throws InterruptedException {
875+
876+
WithTtl source = new WithTtl();
877+
source.id = "ttl-1";
878+
source.ttl = 5L;
879+
source.value = "5 seconds";
880+
881+
template.insert(source);
882+
883+
Thread.sleep(1100);
884+
885+
WithTtl target = template.findById(source.id, WithTtl.class);
886+
assertThat(target.ttl, is(notNullValue()));
887+
assertThat(target.ttl.doubleValue(), is(closeTo(3D, 1D)));
888+
}
889+
890+
/**
891+
* @see DATAREDIS-523
892+
*/
893+
@Test
894+
public void shouldReadBackExplicitTimeToLiveWhenFetchingList() throws InterruptedException {
895+
896+
WithTtl source = new WithTtl();
897+
source.id = "ttl-1";
898+
source.ttl = 5L;
899+
source.value = "5 seconds";
900+
901+
template.insert(source);
902+
903+
Thread.sleep(1100);
904+
905+
WithTtl target = template.findAll(WithTtl.class).iterator().next();
906+
907+
assertThat(target.ttl, is(notNullValue()));
908+
assertThat(target.ttl.doubleValue(), is(closeTo(3D, 1D)));
909+
}
910+
911+
/**
912+
* @see DATAREDIS-523
913+
*/
914+
@Test
915+
public void shouldReadBackExplicitTimeToLiveAndSetItToMinusOnelIfPersisted() throws InterruptedException {
916+
917+
WithTtl source = new WithTtl();
918+
source.id = "ttl-1";
919+
source.ttl = 5L;
920+
source.value = "5 seconds";
921+
922+
template.insert(source);
923+
924+
nativeTemplate.execute(new RedisCallback<Boolean>() {
925+
926+
@Override
927+
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
928+
return connection.persist((WithTtl.class.getName() + ":ttl-1").getBytes());
929+
}
930+
});
931+
932+
WithTtl target = template.findById(source.id, WithTtl.class);
933+
assertThat(target.ttl, is(-1L));
934+
}
935+
868936
@EqualsAndHashCode
869937
@RedisHash("template-test-type-mapping")
870938
static class VariousTypes {
@@ -973,4 +1041,12 @@ public String toString() {
9731041
}
9741042

9751043
}
1044+
1045+
@Data
1046+
static class WithTtl {
1047+
1048+
@Id String id;
1049+
String value;
1050+
@TimeToLive Long ttl;
1051+
}
9761052
}

0 commit comments

Comments
 (0)