Reputation: 255
I have use case where single entries need to removed from the cache at a specific time. The TTL needs to be set on a key and not on a cache level
Following this spring redis documentation I tried to implement key specific TTL but it does not work. There is no event happing, I used a listener to check that and there is only an event happing when the cache ttl runs out.
The cached object has a field annotated with @TimeToLive
from
org.springframework.data.redis.core.TimeToLive
looking at the documentation this should trigger an expire event after the time has run out
Cached object
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BrandResponse {
@TimeToLive
private Long ttl;
@NotBlank
private String id;
}
Used dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.3</version>
</dependency>
Enable Key Space Events
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
public class KikaRestApiApplication {
public static void main(String[] args) {
SpringApplication.run(KikaRestApiApplication.class, args);
}
}
The default TTL for the cache is 5 minutes .entryTtl(Duration.ofMinutes(5))
Cache setup
@Configuration
@EnableCaching
public class RedisCachingConfiguration {
private final KikaApiProperties kikaApiProperties;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private Integer port;
public RedisCachingConfiguration(KikaApiProperties kikaApiProperties) {
this.kikaApiProperties = kikaApiProperties;
}
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
.serializeValuesWith(
SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
return new JedisConnectionFactory(configuration);
}
@Bean
public RedisTemplate<String, Idmap> redisTemplate() {
RedisTemplate<String, Idmap> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
}
Is there something I am missing of does @TimeToLive not work together with spring-redis caching.
Upvotes: 3
Views: 9971
Reputation: 429
as per documentation
TimeToLive marks a single numeric property on aggregate root to be used for setting expirations in Redis. The annotated property supersedes any other timeout configuration.
RedisHash marks Objects as aggregate roots to be stored in a Redis hash.
Default ttl unit is second. @TimeToLive(unit = TimeUnit.SECONDS) you can use other values like MINUTES.
Use @RedisHash on BrandResponse. All the best
Below is my working code
@RedisHash
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BrandResponse {
@TimeToLive(unit = TimeUnit.SECONDS )
private Long ttl;
@NotNull
@Id
private String id;
}
@Repository
public interface BrandRepository extends JpaRepository<BrandResponse, String> {
}
public interface CacheService {
void add(BrandResponse response);
boolean exists(String id);
}
@Service
public class RedisCacheServiceImpl implements CacheService {
@Autowired
private BrandRepository brandRepository;
@Override
public void add(BrandResponse response){
this.brandRepository.save(response);
}
@Override
public boolean exists(String id){
return !this.brandRepository.findById(id).isEmpty();
}
}
@Configuration
@EnableCaching
public class RedisCachingConfiguration {
private String host ="192.168.1.59";
private Integer port = 6379;
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory
= new JedisConnectionFactory();
jedisConFactory.setHostName(host);
jedisConFactory.setPort(port);
return jedisConFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
I used two data sources one for Redis another for db
@SpringBootApplication
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP,
basePackages = {"com.c4c.authn.core.repository.redis"})
@EnableJpaRepositories(basePackages = {"com.c4c.authn.core.repository.db"})
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}
Unit test
public class RedisCacheServiceImplTest extends BaseServiceTest {
@Autowired
private CacheService cacheService;
@Test
public void test_add_ok() throws InterruptedException {
this.cacheService.add(new BrandResponse(5l, "ID1"));
assertTrue(this.cacheService.exists("ID1"));
Thread.sleep(6000);
assertFalse(this.cacheService.exists("ID1"));
}
}
Upvotes: 2