[Spring] Spring에서 Redis로 Cache 사용하기 (CrudRepository, RedisTemplate)
캐싱 전략 (Look Aside = Lazy Loading)
장점
단점
캐시에 없는 데이터의 경우 오랜 시간 소요
DB 업데이트와 캐시 업데이트 로직은 분리되어 있음
(Write-Throgh 구조는 함께 관리할 수 있지만 리소스 낭비의 단점이 있음)
캐시 사용 방법
@EnableCaching
디스크 I/O가 아니므로 읽기, 쓰기 속도 개선
RAM 저장
단점 : 단일 서버 메모리에 저장되므로 메모리 한계 존재
분산 서버 효율성
단일 쓰레드 실행이므로 동시성을 회피하여 데이터 일관성 확보
Key-Value 형식의 자료구조LOW LEVEL
Transaction 지원
객체 저장 시 KeyGen 메소드 권장

import org.springframework.data.redis.core.*;
public class RedisService {
/* 트랜잭션 처리를 위해 RedisTemplate 사용 */
**protected final StringRedisTemplate redisTemplate;**
/* Redis 데이터 관리 객체 */
/* Redis 서비스 KEY| 서비스 저장 데이터의 KEY| 저장 데이터(VALUE) */
**protected HashOperations<String, Object, Object> hashOperations = null;**
/* Redis TTL */
**private final long TTL = 120;**
/* Redis 서비스별 TTL */
**private final Map<String, Integer> SERVICE_TTL;**
/* 초기화 */
**public RedisService(StringRedisTemplate redisTemplate) {**
this.redisTemplate = redisTemplate;
// Redis 데이터와 연결
this.hashOperations = this.redisTemplate.opsForHash();
// Redis 서비스별 TTL 분리 위한 예시
SERVICE_TTL = new HashMap<>() {{
put("엔티티1", 10800);
put("엔티티2", 10800);
}};
**}**
/* Redis 서비스 엔티티1 KEY 설정 */
**protected String get엔티티1Key(String 그룹ID, String 엔티티1ID) {**
return 그룹ID + "." + 엔티티1ID;
**}**
**/* Redis 서비스 엔티티1 데이터 Setting */
/* Redis 서비스 엔티티1 객체 그대로 저장 */**
**protected void setData(String 그룹ID, 엔티티 엔티티객체) {**
redisTemplate.execute(new SessionCallback<>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations)
throws DataAccessException {
operations.multi(); // 트랜잭션 시작
hashOperations.put(get엔티티Key(그룹ID, 엔티티객체.getID()),
"속성명1", 엔티티객체.get속성1());
hashOperations.put(get엔티티Key(그룹ID, 엔티티객체.getID()),
"속성명2", Boolean.toString(player.get속성2()));
/* 모든 데이터는 String이 되어야 함. 따라서 toString() 메소드 필수! */
List<Object> result = operations.exec(); // 트랜잭션 실행
if(result == null) {
System.out.println("엔티티1 :: REDIS TRANSACTION ERROR");
}
return null;
}
});
/* Redis 서비스 엔티티1 TTL 설정 */
redisTemplate.expire(get엔티티1Key(그룹ID, 엔티티객체.getID()),
SERVICE_TTL.get("엔티티1"), TimeUnit.SECONDS);
**}**
**/* Redis 서비스 엔티티1 데이터 Setting */
/* Redis 서비스 엔티티1 객체 데이터 Map 형식 저장 */**
**protected void set엔티티1Info(String 그룹ID, String 엔티티ID,
Map<String, String> hash) {**
redisTemplate.execute(new SessionCallback<>() {
@Override
public Object execute(RedisOperations operations)
throws DataAccessException {
operations.multi(); // 트랜잭션 시작
for (String hashKey : hash.keySet()) {
hashOperations.put(get엔티티1Key(그룹ID, 엔티티ID),
hashKey, hash.get(hashKey));
}
List<Object> result = operations.exec(); // 트랜잭션 실행
if(result == null) {
System.out.println("엔티티1 :: REDIS TRANSACTION ERROR");
}
return null;
}
});
**}**
**/* Redis 서비스 엔티티1 데이터 삭제 */**
**protected void delete엔티티1(String 그룹ID, String 엔티티ID) {**
redisTemplate.execute(new SessionCallback<>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations)
throws DataAccessException {
operations.multi(); // 트랜잭션 시작
hashOperations.delete(get엔티티1Key(그룹ID, 엔티티ID), "속성명1");
hashOperations.delete(get엔티티1Key(그룹ID, 엔티티ID), "속성명2");
List<Object> result = operations.exec(); // 트랜잭션 실행
if(result == null) {
System.out.println("엔티티1 :: REDIS TRANSACTION ERROR");
}
return null;
}
});
**}**
**** **/* Redis 서비스 엔티티1 데이터 Getting */
/* Redis 서비스 엔티티1 객체 그대로 리턴 */**
**protected Map<Object, Object> get엔티티1(String 그룹ID, String 엔티티ID) {**
if(hashOperations.entries(get엔티티1Key(그룹ID, 엔티티ID)).size() == 0){
return null;
}
return hashOperations.entries(get엔티티1Key(그룹ID, 엔티티ID));
**}**
**/* Redis 서비스 엔티티1 데이터 Getting */
/* Redis 서비스 엔티티1 객체 속성별 리턴 */**
**protected String get엔티티1Info(String 그룹ID, String 엔티티ID, String hashKey) {**
return (String) hashOperations.get(get엔티티1Key(그룹ID, 엔티티ID), hashKey);
**}**