Spring Data Redis Expire Key
Asked Answered
S

11

28

I have a Spring Hibernate Application. In my application, Recently i am implemented Spring data Redis.

spring-servlet.xml
<!-- redis connection factory -->
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:use-pool="true"/>

<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" 
    p:connection-factory-ref="jedisConnFactory"/>

And this redisTemplate use in my ServiceImpl class.

RedisServiceImpl

@Autowired
private RedisTemplate<String, T> redisTemplate;

public RedisTemplate<String, T> getRedisTemplate() {
    return redisTemplate;
}

public void setRedisTemplate(RedisTemplate<String, T> redisTemplate) {
    this.redisTemplate = redisTemplate;
}

Now I added data in redisServer like this

public void putData(String uniqueKey, String key, Object results) {
        
    redisTemplate.opsForHash().put(uniqueKey, key, results);
}

Now i want to remove Expire key.

I search in Google, But in google all are saying like this

redisTemplate.expire(key, timeout, TimeUnit);

In this expire method, We need to provide uniqueKey instead of key. But I need to Expire key instead of uniqueKey.

So Please help me what can i do for expire Key?

Sensitive answered 20/1, 2016 at 6:49 Comment(3)
docs.spring.io/spring-data/data-redis/docs/1.5.2.RELEASE/api/… Doesn't say anything about a uniqueKey. You can pass the key to expireBeem
Yes, you are right. In this url doesn't say anything about uniqueKey. But In implementation when i use uniqueKey then this uniqueKey is expired. But when i use key then this key is not expired.Sensitive
The unique key is considered for the whole hash... so can expire the key for this hash. Unless you need to remove the hash key and value manually.Belie
E
20

I am using Spring Data Redis.

I am using @Redishash(timeToLive=300) annotation to expire my Entities after 300 seconds.

Here is the excerpt from my pom.xml

...
...
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
</parent>
...
...
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
...
...

My RedisConfig.class

@Configuration
@Log4j2
@EnableRedisRepositories(basePackageClasses = ConsentOTP.class)
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private Integer port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        log.info("=================================================================");
        log.info("redis config : {} : {} ", host, port);
        log.info("=================================================================");

        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
        config.setPassword(RedisPassword.of(password));
        return new JedisConnectionFactory(config);
    }

}

And my entity class ConsentOtp.class

@RedisHash(value = "ConsentOTP", timeToLive = 300)
@Data
@NoArgsConstructor
public class ConsentOTP implements Serializable {

    private static final long serialVersionUID = 1708925807375596799L;

    private String id;
    private LocalDateTime timestamp;
    private String otp;

    public ConsentOTP(String personId, LocalDateTime timestamp, String otp) {
        this.id = personId;
        this.timestamp = timestamp;
        this.otp = otp;
    }
}

Here is my Redis repository

public interface ConsentOtpRepository extends CrudRepository<ConsentOTP, String> {
    
}
Erotica answered 2/10, 2019 at 13:21 Comment(2)
this solution adds phantom keys in Redis and other app like python reading these keys will get two values for same key(1 orignal and 1 phantom)...Dimity
Yes it does add a phantom key. But it wont be a problem if one is accessing the store from Spring boot or the likes.Erotica
R
12

I am using Redis Version 3.2.100.

Instead of redis template ,Use Redis Cache Manager, pass redistemplate to cacheManager and use its set expires property to which is basically map of String & Long , you can add cache name and set its expiry time i.e time to live (TTL).

You can use setDefaultExpiration method of cacheManager to set same expiry time to all the cache.

@SuppressWarnings({ "rawtypes", "unused" })
@Configuration
@EnableCaching(proxyTargetClass = true, mode = AdviceMode.ASPECTJ, order = 1)
@PropertySource("classpath:/application.properties")
public class CacheConfigImpl extends CachingConfigurerSupport {

    private @Value("${redis.ip}") String redisHost;
    private @Value("${redis.port}") int redisPort;

     private static final Map<String, Long> cacheMap = new HashMap<String, Long>();
    static {
        cacheMap.put("method1cache", 600L);
        cacheMap.put("method2cache", 600L);
        cacheMap.put("method3cache", 800L);
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        redisConnectionFactory.setHostName(CustomPropertyLoader.getProperty("redis.ip"));
        redisConnectionFactory.setPort(Integer.parseInt(CustomPropertyLoader.getProperty("redis.port")));
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean(name = "RCacheManager")
    public CacheManager cacheManager(RedisTemplate redisTemplate) {

        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        cacheManager.setExpires(cacheMap);
        cacheManager.setUsePrefix(true);
        final String redis_client_name = CustomPropertyLoader.getProperty("redis.client.name");
        cacheManager.setCachePrefix(new RedisCachePrefix() {
            private final RedisSerializer<String> serializer = new StringRedisSerializer();
            private final String delimiter = ":";

            public byte[] prefix(String cacheName) {
                return this.serializer
                        .serialize(redis_client_name.concat(this.delimiter).concat(cacheName).concat(this.delimiter));
            }
        });
        return cacheManager;
    }
    }
Rh answered 21/4, 2017 at 15:20 Comment(0)
C
10

Actually you can do it with Redisson Redis Java Client using RMapCache object. It provides ability to set ttl and maxIdle per map entry. Example:

// implements java.util.concurrent.ConcurrentMap interface
RMapCache<String, SomeObject> map = redisson.getMapCache("anyMap");

// ttl = 10 minutes, 
map.put("key1", new SomeObject(), 10, TimeUnit.MINUTES);

// ttl = 10 minutes, maxIdleTime = 10 seconds
map.put("key1", new SomeObject(), 10, TimeUnit.MINUTES, 10, TimeUnit.SECONDS);
Crust answered 14/9, 2016 at 8:39 Comment(0)
S
5

Actually You cannot expire or set the TTL for individual keys inside the Redis Hash. You can only expire or set TTL the complete hash. if you want to support this you have to change your data structure.

Here is the link for why it is not possible; and below are some excerpts from Redis expire

As far as i know redis cares for performance than features. It will defeat the purpose of memory efficient hash implementation in redis. Since hash key-value fields are not always represented as full featured redis object (they could be stored as linear array when hash is small to save memory), so the hash key field cannot have a TTL.

Also this link Allow to set an expiration on hash field might help you to change your data structure to handle expiry

Straggle answered 20/1, 2016 at 9:53 Comment(0)
G
5

To set TTL for keys, you may create multiple beans of cacheManager and set TTL for individual bean. Then as per your use, you can use required cachemanager. Here is what I have implemented.

@Configuration("cacheConfig")
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport{


    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        System.out.println("redisConnectionFactory");
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();

        // Defaults
        redisConnectionFactory.setHostName("127.0.0.1");
        redisConnectionFactory.setPort(6379);
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
        System.out.println("redisTemplate");
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(cf);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    @Bean
    @Primary
    public CacheManager cacheManager2(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(20);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }


    @Bean
    public CacheManager cacheManager1(RedisTemplate redisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
        // Number of seconds before expiration. Defaults to unlimited (0)
        cacheManager.setDefaultExpiration(60);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

}

To use above created cachemanager beans,

@Cacheable(value = "users", key = "#userId.toString()", cacheManager ="cacheManager2")
    @RequestMapping(value = "/{userId}", method = RequestMethod.GET)
    public User getUser(@PathVariable String userId) {
        LOG.info("Getting user with ID {}.: "+userId);
      return userService.fetchUserDataonUsers(userId);
    }


@Cacheable(value = "users", key = "#userId.toString()", cacheManager ="cacheManager1")
    @RequestMapping(value = "data/{userId}", method = RequestMethod.GET)
    public String getUserData(@PathVariable String userId) {
        LOG.info("Getting user with ID getUserData {}.: "+userId);
      return userService.fetchUserDataonUsers(userId).toString();
    }

when we define cacheManager ="cacheManager2" in @Cacheable , It will use TTL set for cacheManager2 defined in configuration. Same goes for cacheManager1

Goral answered 27/4, 2018 at 6:13 Comment(0)
L
3

You can adopt Quartz for this purpose (implementing ttl for a Redis record). If you use Spring Boot, it autoconfigures Scheduler for you. Thus you can autowire it directly to your service layer.

@Autowired
private Scheduler scheduler;

Then you need to implement a job like this (in this example I am using Spring Redis Data):

@Slf4j
@Component
public class RemoveExpiredRecordJob implements Job {

@Autowired
public RedisRepository redisRepository;

@Override
public void execute(JobExecutionContext jobExecutionContext) {
    String key = jobExecutionContext
            .getJobDetail()
            .getKey()
            .getName();
    redisRepository.deleteById(key);
    log.info("Record removed due timeout :: {}", key);
}

}

Then you can encapsulate some logic for creating JobDetail and Trigger

@Component
public class SchedulerComponentBuilder {

    public JobDetail getJobDetail (String key, Class<? extends org.quartz.Job> clazz) {
        return JobBuilder.newJob().ofType(clazz)
                .storeDurably(false)
                .withIdentity(key)
                .withDescription("This key will be removed from Redis store when time expires.")
                .build();
    }

    public Trigger getTrigger(int ttl, JobDetail jobDetail) {
        java.util.Calendar calendar = java.util.Calendar.getInstance();
        calendar.add(java.util.Calendar.SECOND, ttl);
        return TriggerBuilder.newTrigger().forJob(jobDetail)
                .withDescription("This trigger fires once to remove an expired record from Redis store.")
                .startAt(calendar.getTime())
                .build();
    }
}

And finally, right after you saved you record in Redis repository, you need to schedule a job for removal this record (uniqueKey) from it like this:

@Autowired
private SchedulerComponentBuilder schedulerComponentBuilder;

private void schedule(String uniqueKey, int ttl) {
    try {
        JobDetail jobDetail = schedulerComponentBuilder.getJobDetail(uniqueKey, RemoveExpiredRecordJob.class);
        Trigger jobTrigger = schedulerComponentBuilder.getTrigger(ttl, jobDetail);
        scheduler.scheduleJob(jobDetail,jobTrigger);
        log.info("Job is scheduled :: {}", jobDetail);
    } catch (SchedulerException e) {
        log.error("Filed to schedule a job {}", e);
        throw new RuntimeException(e);
    }
}
Limonite answered 16/4, 2018 at 16:24 Comment(0)
R
2

Though I am late to the party posting this for the Posterity.

Setting TTL value in key level it is not possible, because org.springframework.data.redis.cache.RedisCacheManager does not provide any methods to configure the TTL value in key despite they have provided it for the cache level. The following steps will help you configure the TTL time in a cache and default level.

  1. Adding required maven dependency.
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
  1. adding Redis configuration including TTL values for default level and cache level. Here we have set the TTL value to a sample cache called "photo".
cache:
  host: localhost
  port: 6379
  default-ttl: 6000
  caches-ttl:
    photo: 3600
  1. Adding RedisCacheManager configuration
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

@Configuration
@EnableCaching
public class RedisCacheConfiguration extends CachingConfigurerSupport {

    @Autowired
    private CacheConfigurationProperties cacheConfigurationProperties = null;

    private org.springframework.data.redis.cache.RedisCacheConfiguration createCacheConfiguration(long timeoutInSeconds) {
        return org.springframework.data.redis.cache.RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(timeoutInSeconds));
    }

    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory redisConnectionFactory) {
        Map<String, org.springframework.data.redis.cache.RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        if (Objects.nonNull(cacheConfigurationProperties.getCachesTTL())) {
            for (Entry<String, String> cacheNameAndTimeout : cacheConfigurationProperties.getCachesTTL().entrySet()) {
                cacheConfigurations.put(cacheNameAndTimeout.getKey(), createCacheConfiguration(Long.parseLong(cacheNameAndTimeout.getValue())));
            }
        }
        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(createCacheConfiguration(Long.parseLong(cacheConfigurationProperties.getDefaultTTL())))
                .withInitialCacheConfigurations(cacheConfigurations).build();
    }

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(cacheConfigurationProperties.getHost());
      redisStandaloneConfiguration.setPort(Integer.parseInt(cacheConfigurationProperties.getPort()));
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Configuration
    @ConfigurationProperties(prefix = "cache")
    @Data
    class CacheConfigurationProperties {
        private String port;
        private String host;
        private String defaultTTL;
        private Map<String, String> cachesTTL;
    }
}

The complete documentation is available at Medium

Roxannaroxanne answered 14/4, 2020 at 11:34 Comment(0)
P
0

I had a same problem. Difference was just in using Jedis client. I solved it changing postions of UniqueKey and Key. For your example it will be something like this:

redisService.sadd(key, uniqueKey);
redis.expire(key, expirationTime);
Peninsula answered 29/4, 2020 at 21:41 Comment(0)
C
0

Updated code for @Akanksha Sharma's answer with Lettuce redis client.

@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
    return new LettuceConnectionFactory(new RedisStandaloneConfiguration("localhost", 6379));
}

@Bean
@Primary
public CacheManager cacheManager1(RedisConnectionFactory redisConnectionFactory) {
    
    return RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(60L)))
            .build();
    
}

@Bean
public CacheManager cacheManager2(RedisConnectionFactory redisConnectionFactory) {
    return RedisCacheManager
            .builder(redisConnectionFactory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1L)))
            .build();
}
Clo answered 31/1, 2021 at 10:31 Comment(0)
B
0

Hi not sure if this is still active but the only correct answer is provided by

Santosh Joshi

https://mcmap.net/q/489485/-spring-data-redis-expire-key

as he correctly states you cannot expire an individual key in a redis hash, only the hash itself.

see https://github.com/redis/redis/issues/1042

and https://github.com/redis/redis/issues/1042#issuecomment-45367109 in particular

Brock answered 6/5, 2021 at 14:27 Comment(0)
B
0

Simple and short : via RedisConnection

public void saveInCache(String key, Object obj) {

    RedisConnection connection = connectionFactory.getConnection();
    try {
        connection.set(key.getBytes(), SerializationUtils.serialize(obj));
        //setting custom expiry runtime
        long expiresIn = obj.getExpiresAfter() - Instant.now().getEpochSecond(); 
        connection.expire(key.getBytes(), expiresIn);

    } catch (Exception e) {
        //handle any exception
    }
    finally {
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }

        } catch (Exception e) {
            logger.error("Exception while closing redis connection : " + e.getMessage());
            e.printStackTrace();
        }
    }
}
Beltane answered 15/11, 2021 at 10:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.