* first commit for Spring Data Redis TTL

* BAEL-4779: Removed incorrect module

* removed mvn wrapper

* Reduced wait time to check expiration

* J17 -> J11

* spring boot redis added to JDK 9 and above profile

* pulled latest from master

* restored from master

* resolved from master

* Update pom.xml

* Update pom.xml

* using Collections api available in J15

* updated Integration test class name

---------

Co-authored-by: s9m33r <no-reply>
Co-authored-by: Loredana Crusoveanu <lore.crusoveanu@gmail.com>
This commit is contained in:
Sameer
2023-03-15 05:14:31 +05:30
committed by GitHub
parent f93f1d2c1e
commit 350a3c3575
16 changed files with 501 additions and 3 deletions
@@ -0,0 +1,13 @@
package com.baelding.springbootredis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRedisApplication.class, args);
}
}
@@ -0,0 +1,58 @@
package com.baelding.springbootredis.config;
import com.baelding.springbootredis.model.Session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisKeyExpiredEvent;
import org.springframework.data.redis.core.RedisKeyValueAdapter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.convert.MappingConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.stereotype.Component;
import java.util.Collections;
@Configuration
@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP)
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Session> getRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Session> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
@Bean
public RedisMappingContext keyValueMappingContext() {
return new RedisMappingContext(new MappingConfiguration(new IndexConfiguration(), new MyKeyspaceConfiguration()));
}
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
@Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
KeyspaceSettings keyspaceSettings = new KeyspaceSettings(Session.class, "session");
keyspaceSettings.setTimeToLive(60L);
return Collections.singleton(keyspaceSettings);
}
}
@Component
public static class SessionExpiredEventListener {
@EventListener
public void handleRedisKeyExpiredEvent(RedisKeyExpiredEvent<Session> event) {
Session expiredSession = (Session) event.getValue();
assert expiredSession != null;
log.info("Session with key={} has expired", expiredSession.getId());
}
}
}
@@ -0,0 +1,49 @@
package com.baelding.springbootredis.controller;
import com.baelding.springbootredis.dto.SessionCreateRequest;
import com.baelding.springbootredis.model.Session;
import com.baelding.springbootredis.service.cache.session.SessionCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/v1/sessions")
public class SessionController {
private static final String LOCATION_HEADER_VALUE_FORMAT = "/v1/sessions/%s";
@Autowired
private SessionCache sessionCache;
@GetMapping
public List<Session> getAllSessions() {
return sessionCache.getAllSessions();
}
@GetMapping("/{id}")
public Session getSession(@PathVariable("id") String id) {
return sessionCache.getSession(id);
}
@PostMapping
public ResponseEntity<Session> createASession(@RequestBody SessionCreateRequest sessionCreateRequest) {
Session createdSession = sessionCache.createASession(Session.builder().expirationInSeconds(sessionCreateRequest.getExpirationInSeconds()).build());
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.set(HttpHeaders.LOCATION, String.format(LOCATION_HEADER_VALUE_FORMAT, createdSession.getId()));
return new ResponseEntity<>(createdSession, new HttpHeaders(headers), HttpStatus.CREATED);
}
}
@@ -0,0 +1,14 @@
package com.baelding.springbootredis.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SessionCreateRequest {
private Long expirationInSeconds;
}
@@ -0,0 +1,12 @@
package com.baelding.springbootredis.exception.session;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class SessionNotFoundException extends RuntimeException {
private static final String MESSAGE_FORMAT = "Session with id=%s doesn't exists!";
public SessionNotFoundException(String id) {
super(String.format(MESSAGE_FORMAT, id));
}
}
@@ -0,0 +1,19 @@
package com.baelding.springbootredis.model;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
@Data
@Builder
@EqualsAndHashCode
@RedisHash(timeToLive = 60L)
public class Session {
@Id
private String id;
@TimeToLive
private Long expirationInSeconds;
}
@@ -0,0 +1,7 @@
package com.baelding.springbootredis.repository;
import com.baelding.springbootredis.model.Session;
import org.springframework.data.repository.CrudRepository;
public interface SessionRepository extends CrudRepository<Session, String> {
}
@@ -0,0 +1,34 @@
package com.baelding.springbootredis.service.cache.session;
import com.baelding.springbootredis.exception.session.SessionNotFoundException;
import com.baelding.springbootredis.model.Session;
import com.baelding.springbootredis.repository.SessionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Iterator;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
public class RedisSessionCache implements SessionCache {
@Autowired
private SessionRepository sessionRepository;
@Override
public Session createASession(Session session) {
return sessionRepository.save(session);
}
@Override
public Session getSession(String id) {
return sessionRepository.findById(id).orElseThrow(() -> new SessionNotFoundException(id));
}
@Override
public List<Session> getAllSessions() {
return Stream.iterate(sessionRepository.findAll().iterator(), Iterator::hasNext, UnaryOperator.identity()).map(Iterator::next).collect(Collectors.toList());
}
}
@@ -0,0 +1,12 @@
package com.baelding.springbootredis.service.cache.session;
import com.baelding.springbootredis.model.Session;
import java.util.List;
public interface SessionCache {
Session createASession(Session session);
Session getSession(String id);
List<Session> getAllSessions();
}