본문 바로가기

Spring Boot/Cache

Spring Cache 기본 사용 : SpEL을 활용한 동적 캐시 키

SpEL ??? 스펠?

 

 - SpEL(Spring Expression Language)의 기본 개념

 - @Cacheable, @CachePut, @CacheEvict에서 SpEL을 활용해 동적 키를 생성하는 법

 - 메서드 파라미터, 리턴값, 객체 프로퍼티 등을 참조하는 표현식 작성

 - 복합 키(Composite Key)를 구성하는 실무 예시

 - 사용자 정의 함수를 SpEL 내에서 호출하는 법

 


SpEL(Spring Expression Language)

SpEL은 Spring에서 제공하는 표현식 언어(Expression Language)로,

런타임 시점에 동적으로 값을 계산하거나 프로퍼티, 메서드, 변수에 접근할 수 있는 기능들을 제공한다고 한다

Spring Cache에서는 SpEL을 활용하여 캐시 키를 동적으로 지정할 수 있다고 함

 

 

1. SpEL 문법의 기본 구조

SpEL은 #{} 또는 "#id" 같은 형태로 작성함

import com.b1uffer.cachetest.entity.User;
import com.b1uffer.cachetest.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.NoSuchElementException;

@Service
public class UserService {
    private UserRepository userRepository;
    
    @Cacheable(value = "users", key = "#id")
    public User findUser(Long id) {
        User user = userRepository.findById(id).orElseThrow(() -> new NoSuchElementException("없음"));
        return user;
    } 
}

 

요소 의미
#id 메서드 파라미터 id값을 참조함
#root.args[0] 첫번째 파라미터를 참조함
#root.methodName 메서드 이름을 참조함
#result 메서드 실행 결과를 참조함(메서드 실행 후에만 사용 가능함)

 

 

 

2. SpEL이 사용 가능한 애너테이션 속성

SpEL은 애너테이션(@Cacheable, @CachePut, @CacheEvict 등) 속성에서 사용할 수 있음

 - @Cacheable : key, condition, unless (key = "#user.id")

 - @CachePut : key, condition (key = "#product.id")

 - @CacheEvict : key, condition (key = "#id")

 

 


메서드 파라미터 참조하기

SpEL의 가장 기본적 사용법은 메서드의 파라미터를 캐시 키로 활용하는 것임

import com.b1uffer.cachetest.entity.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#userId")
    public User getUserById(Long userId) {
        System.out.println("DB 조회 발생");
        User user = new User(userId, "블러퍼", 12);
        return user;
    }
}

 

* key = "#userId" 를 통해 메서드 파라미터 이름인 userId(Long userId)를 캐시 키로 활용함

* 동일한 userId 값으로 다시 호출하면, 캐시된 데이터가 반환된다

 

* SpEL의 다양한 파라미터 접근 방식

 - #userId : 파라미터 이름으로 접근함

 - #a0, #p0 : 첫번째 파라미터로 접근함

 - #root.args[0] : 루트 객체의 첫번째 인자로 접근함 

 

 


복합 키(Composite Key) 구성하기

복합 키는 여러 파라미터를 결합하여 고유한 캐시 키를 만드는 방법임

    @Cacheable(value = "userCache", key = "#name + '_' + #id")
    public User getUser(String name, Long id) {
        User user = new User(id, name, 11);
        return user;
    }

 

* 이름과 ID를 결합하여 b1uffer_11, b1uffer_12와 같은 형태의 캐시 키를 생성해주는 복합 키 생성 방법임

 

 

응용 예시

import com.b1uffer.cachetest.entity.Order;
import com.b1uffer.cachetest.entity.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Cacheable(value = "orderCache", key = "#user.id + ':' + #order.id")
    public Order getOrder(User user, Order order) {
        // 로직
    }
}

 

이 방식은 사용자별 주문 정보 캐시와 같이 다중 속성을 구분해야할 때 유용하다고 한다.

* getter 혹은 get 메서드가 있어야함

 

 


메서드 실행 결과(@Cacheable unless, @CachePut) 활용하기

#result를 활용하면 메서드 실행 후의 결과값을 조건으로 사용할 수 있다고 함

 

import com.b1uffer.cachetest.entity.User;
import com.b1uffer.cachetest.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.NoSuchElementException;

@Service
public class UserService {
    private UserRepository userRepository;

    @Cacheable(value = "userCache", key = "#id", unless = "#result == null")
    public User findUser(Long id) {
        User user = userRepository.findById(id).orElseThrow(() -> new NoSuchElementException("없음"));
        return user;
    }
}

 

* unless는 결과가 null인 경우, 캐시를 저장하지 않도록 하는 속성임

* unless 안에 있는 #result는 반드시 메서드 실행 이후에만 접근 가능한 속성임

 

속성 의미
condition 메서드 실행 전, 캐시 저장 여부를 결정함
unless 메서드 실행 후, 결과값을 기반으로 캐시 저장 여부를 결정함

 

 


사용자 정의 함수 호출

SpEL은 Spring Bean에 등록된 메서드를 호출하여 캐시 키를 만들수도 있음

가령, KeyGeneratorUtil 클래스를 작성하여 사용자 정의 로직을 구현할 수도 있다고 함!

import org.springframework.stereotype.Component;

@Component("keyUtil")
public class KeyGeneratorUtil {
    public String createKey(String prefix, Object id) {
        return prefix + "_" + id;
    }
}

 

이후에 SpEL에서 해당 메서드를 호출할 수 있음 (!!)

    @Cacheable(value = "userCache", key = "@keyUtil.createKey('USER', #id)")
    public User getUser(String name, Long id) {
        User user = new User(id, name, 11);
        return user;
    }

 

key에서 @keyUtil이 보이는가? 우리가 @Component로 등록한 클래스 Bean이다. 오 마이 갓... 이게 되네

 

 - @KeyUtil : Spring Bean의 이름을 참조한 것(@Component("keyUtil"))

 - .createKey() : 메서드 호출

 - 결과적으로 캐시 키는 USER_1, USER_2 와 같은 형태로 생성된다!

 


* SpEL은 유연하지만, 복잡한 키 조합을 자주 사용하면 유지보수가 어려워 질 수 있으니 주의.

* 가독성이 떨어지는 경우 KeyGenerator 인터페이스를 구현하는 별도 클래스를 두는 것을 권장

* 캐시 키에는 절대 개인 정보(이메일, 비밀번호 등)을 포함하지 않기

* 문자열 결합( + + + + )이 많은 경우, String.format() 형태의 키 유틸을 사용하는게 안전하다고 함

 


정리

항목 설명
#paramName 메서드 파라미터를 참조함
#root.args[n] n번째 인자에 접근함
#result 메서드 실행 결과를 참조함
@beanName.method() Bean 메서드를 호출함
condition, unless 캐시 조건 제어 속성