Skip to content

DATAREDIS-523 - Read back TTL into property. #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.0.BUILD-SNAPSHOT</version>
<version>1.8.0.DATAREDIS-523-SNAPSHOT</version>

<name>Spring Data Redis</name>

Expand Down
1 change: 1 addition & 0 deletions src/main/asciidoc/reference/redis-repositories.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ public class TimeToLiveOnMethod {
----
====

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.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
Expand All @@ -39,6 +40,7 @@
import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
import org.springframework.data.keyvalue.core.KeyValueAdapter;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnection;
Expand All @@ -58,6 +60,7 @@
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.data.util.CloseableIterator;
import org.springframework.util.Assert;
import org.springframework.util.NumberUtils;
import org.springframework.util.ObjectUtils;

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

return converter.read(type, data);
return readBackTimeToLiveIfSet(binId, converter.read(type, data));
}

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

final byte[] binKeyspace = toBytes(keyspace);

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

@Override
public List<Map<byte[], byte[]>> doInRedis(RedisConnection connection) throws DataAccessException {

final List<Map<byte[], byte[]>> rawData = new ArrayList<Map<byte[], byte[]>>();

Set<byte[]> members = connection.sMembers(binKeyspace);

for (byte[] id : members) {
rawData.add(connection
.hGetAll(createKey(asString(keyspace), getConverter().getConversionService().convert(id, String.class))));
}

return rawData;
public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
return connection.sMembers(binKeyspace);
}
});

List<Object> result = new ArrayList<Object>(raw.size());
for (Map<byte[], byte[]> rawData : raw) {
result.add(converter.read(Object.class, new RedisData(rawData)));
List<Object> result = new ArrayList<Object>();
for (byte[] key : ids) {
result.add(get(key, keyspace));
}

return result;
}

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

/**
* Read back and set {@link TimeToLive} for the property.
*
* @param key
* @param target
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private <T> T readBackTimeToLiveIfSet(final byte[] key, T target) {

if (target == null || key == null) {
return target;
}

RedisPersistentEntity<?> entity = this.converter.getMappingContext().getPersistentEntity(target.getClass());
if (entity.hasExplictTimeToLiveProperty()) {

PersistentProperty<?> ttlProperty = entity.getExplicitTimeToLiveProperty();

final TimeToLive ttl = ttlProperty.findAnnotation(TimeToLive.class);

Long timeout = redisOps.execute(new RedisCallback<Long>() {

@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {

if (ObjectUtils.nullSafeEquals(TimeUnit.SECONDS, ttl.unit())) {
return connection.ttl(key);
}

return connection.pTtl(key, ttl.unit());
}
});

if (timeout != null || !ttlProperty.getType().isPrimitive()) {
entity.getPropertyAccessor(target).setProperty(ttlProperty,
converter.getConversionService().convert(timeout, ttlProperty.getType()));
}
}

return target;
}

/**
* Configure usage of {@link KeyExpirationEventMessageListener}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.TimeToLiveAccessor;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -59,6 +60,24 @@ public TimeToLiveAccessor getTimeToLiveAccessor() {
return this.timeToLiveAccessor;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.mapping.RedisPersistentEntity#hasExplictTimeToLiveProperty()
*/
@Override
public boolean hasExplictTimeToLiveProperty() {
return getExplicitTimeToLiveProperty() != null;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.mapping.RedisPersistentEntity#getExplicitTimeToLiveProperty()
*/
@Override
public RedisPersistentProperty getExplicitTimeToLiveProperty() {
return (RedisPersistentProperty) this.getPersistentProperty(TimeToLive.class);
}

/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.BasicPersistentEntity#returnPropertyIfBetterIdPropertyCandidateOrNull(org.springframework.data.mapping.PersistentProperty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ public Long getTimeToLive(final Object source) {
} else if (ttlProperty != null) {

RedisPersistentEntity entity = mappingContext.getPersistentEntity(type);
Long timeout = (Long) entity.getPropertyAccessor(source).getProperty(ttlProperty);
Number timeout = (Number) entity.getPropertyAccessor(source).getProperty(ttlProperty);
if (timeout != null) {
return TimeUnit.SECONDS.convert(timeout, unit);
return TimeUnit.SECONDS.convert(timeout.longValue(), unit);
}

} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
Expand All @@ -17,6 +17,7 @@

import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.redis.core.TimeToLiveAccessor;

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

/**
* @return {@literal true} when a property is annotated with {@link org.springframework.data.redis.core.TimeToLive}.
* @since 1.8
*/
boolean hasExplictTimeToLiveProperty();

/**
* Get the {@link PersistentProperty} that is annotated with {@link org.springframework.data.redis.core.TimeToLive}.
*
* @return can be {@null}.
* @since 1.8
*/
PersistentProperty<? extends PersistentProperty<?>> getExplicitTimeToLiveProperty();

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2016 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.
Expand All @@ -16,6 +16,7 @@
package org.springframework.data.redis.core;

import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;

import java.util.ArrayList;
Expand Down Expand Up @@ -43,10 +44,14 @@
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.util.ObjectUtils;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* Integration tests for {@link RedisKeyValueTemplate}.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@RunWith(Parameterized.class)
public class RedisKeyValueTemplateTests {
Expand Down Expand Up @@ -865,6 +870,91 @@ public Void doInRedis(RedisConnection connection) throws DataAccessException {
});
}

/**
* @see DATAREDIS-523
*/
@Test
public void shouldReadBackExplicitTimeToLive() throws InterruptedException {

WithTtl source = new WithTtl();
source.id = "ttl-1";
source.ttl = 5L;
source.value = "5 seconds";

template.insert(source);

Thread.sleep(1100);

WithTtl target = template.findById(source.id, WithTtl.class);
assertThat(target.ttl, is(notNullValue()));
assertThat(target.ttl.doubleValue(), is(closeTo(3D, 1D)));
}

/**
* @see DATAREDIS-523
*/
@Test
public void shouldReadBackExplicitTimeToLiveToPrimitiveField() throws InterruptedException {

WithPrimitiveTtl source = new WithPrimitiveTtl();
source.id = "ttl-1";
source.ttl = 5;
source.value = "5 seconds";

template.insert(source);

Thread.sleep(1100);

WithPrimitiveTtl target = template.findById(source.id, WithPrimitiveTtl.class);
assertThat((double) target.ttl, is(closeTo(3D, 1D)));
}

/**
* @see DATAREDIS-523
*/
@Test
public void shouldReadBackExplicitTimeToLiveWhenFetchingList() throws InterruptedException {

WithTtl source = new WithTtl();
source.id = "ttl-1";
source.ttl = 5L;
source.value = "5 seconds";

template.insert(source);

Thread.sleep(1100);

WithTtl target = template.findAll(WithTtl.class).iterator().next();

assertThat(target.ttl, is(notNullValue()));
assertThat(target.ttl.doubleValue(), is(closeTo(3D, 1D)));
}

/**
* @see DATAREDIS-523
*/
@Test
public void shouldReadBackExplicitTimeToLiveAndSetItToMinusOnelIfPersisted() throws InterruptedException {

WithTtl source = new WithTtl();
source.id = "ttl-1";
source.ttl = 5L;
source.value = "5 seconds";

template.insert(source);

nativeTemplate.execute(new RedisCallback<Boolean>() {

@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.persist((WithTtl.class.getName() + ":ttl-1").getBytes());
}
});

WithTtl target = template.findById(source.id, WithTtl.class);
assertThat(target.ttl, is(-1L));
}

@EqualsAndHashCode
@RedisHash("template-test-type-mapping")
static class VariousTypes {
Expand Down Expand Up @@ -973,4 +1063,20 @@ public String toString() {
}

}

@Data
static class WithTtl {

@Id String id;
String value;
@TimeToLive Long ttl;
}

@Data
static class WithPrimitiveTtl {

@Id String id;
String value;
@TimeToLive int ttl;
}
}