Spring Cache는 캐싱 기능을 통해 애플리케이션 성능을 향상할 수 있는 강력한 도구를 제공한다. 이번 포스팅에서는 Spring Cache 추상화를 통해서 제공되는 주요 어노테이션과 기능에 대해서 정리하고자 한다.
@Cacheable
@Cacheable 어노테이션은 메서드 결과를 캐시에 저장하고, 이 후 같은 파라미터로 메서드가 호출될 경우 캐시 된 결과를 반환한다. 이는 주로 읽기 작업에 사용된다.
속성
- value 또는 cacheNames: 캐시 이름을 지정한다.
- key: 캐시에 저장될 엔트리 키를 지정한다. SpEL(Spring Expression Language)를 사용하여 동적으로 설정할 수 있다.
- condition: 캐시를 적용할 조건을 SpEL로 지정한다.
- unless: 캐시에 저장하지 않을 조건을 SpEL로 지정한다.
- sync: 여러 스레드가 동시에 캐시에 접근할 때 동기화할지 여부를 지정한다.
@Cacheable(value = "users", key = "#userId")
public User getUserById(Long userId) {
// DB에서 사용자 정보를 조회하는 로직
return userRepository.findById(userId).orElse(null);
}
- users 캐시에 userId 키가 존재하지 않는 경우
- users 이름의 캐시에 userId 인자로 전달되는 값을 엔트리 키로 하여 메서드의 리턴 값을 users 캐시에 저장한다.
- users 캐시에 userId 키가 존재하는 경우
- users 캐시로 부터 결과를 반환한다. 메서드 내의 로직은 실행되지 않는다.
즉, @Cacheable은 메서드 실행 전에 캐시를 확인한다.
@CachePut
@CachePut 어노테이션은 메서드를 실행하고 그 결과를 캐시에 저장한다. 메서드 호출 시마다 캐시를 업데이트하며, 주로 쓰기 작업에 사용된다.
속성
- value 또는 cacheNames: 캐시 이름을 지정한다.
- key: 캐시 키를 지정한다. SpEL을 사용하여 동적으로 설정할 수 있다.
- condition: 캐시를 적용할 조건을 SpEL로 지정한다.
- unless: 캐시에 저장하지 않을 조건을 SpEL로 지정한다.
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
// 사용자 정보를 업데이트하는 로직
return userRepository.save(user);
}
위 코드에서 메서드 호출 결과를 users 캐시의 user.id를 키로 갖는 엔트리에 업데이트한다.
만약 users 캐시에 user.id를 키로 갖는 엔트리가 없다면 추가된다.
@CachePut 어노테이션은 항상 메서드를 실행하고 그 결과를 캐시에 갱신한다.
캐시에 데이터가 없는 경우에만 메서드 내의 로직이 실행되는 @Cacheable 어노테이션과 주요 차이점 중 하나다.
데이터베이스 업데이트나 새로운 데이터 생성을 처리할 때 유용하며, 메서드가 호출될 때마다 캐시를 최신 상태로 유지할 수 있다.
@CachePut 어노테이션을 적절히 사용하여 캐시 일관성을 유지하고 성능을 최적화할 수 있다.
@CacheEvict
@CacheEvict 어노테이션은 캐시에서 하나 이상의 엔트리를 제거한다. 메서드가 실행된 후 지정된 키 또는 캐시 이름의 엔트리를 제거한다.
속성
- value 또는 cacheNames: 캐시 이름을 지정한다.
- key: 제거할 캐시 키를 지정한다. SpEL을 사용하여 동적으로 설정할 수 있다.
- allEntries: 캐시의 모든 엔트리를 제거할지 여부를 설정한다. (기본값: false)
- beforeInvocation: 메서드 호출 전에 캐시를 비울지 여부를 지정한다. (기본값: false)
@CacheEvict(value = "users", key = "#userId")
public void deleteUserById(Long userId) {
// 사용자 정보를 삭제하는 로직
userRepository.deleteById(userId);
}
위 코드는 userId 에 해당하는 식별자를 갖는 데이터를 DB에서 삭제 후에 users 캐시에 저장된 userId를 키로 갖는 엔트리도 제거한다.
- beforeInvocation 속성
- true로 지정된 경우 메서드 실행 이전에 캐시로부터 데이터를 삭제하기 때문에 메서드 실행 성공 여부와 관계없이 캐시에서 데이터가 삭제된다.
- false로 지정된 경우 메서드 실행 이후에 캐시로부터 데이터를 삭제하기 때문에 만약 메서드 실행 도중 예외가 발생한 경우 캐시에서 데이터가 삭제되지 않는다. 메서드 실행이 정상적으로 이루어진 경우에만 캐시에서 데이터가 삭제된다.
@Caching
@Caching 어노테이션은 여러 캐시 작업을 조합할 수 있도록 한다. @Cacheable, @CachePut, @CacheEvict을 조합하여 복잡한 캐싱 규칙을 정의할 수 있다.
속성
- cacheable: @Cacheable 어노테이션 배열
- put: @CachePut 어노테이션 배열
- evict: @CacheEvict 어노테이션 배열
@Caching(
put = { @CachePut(value = "users", key = "#user.id") },
evict = { @CacheEvict(value = "userIds", key = "#user.username") }
)
public User saveUser(User user) {
// 사용자 정보를 저장하는 로직
return userRepository.save(user);
}
saveUser() 메서드가 호출될 때 @Caching 어노테이션에 의해서 다음 두 가지 캐시 관련 작업이 수행된다.
- @CachePut: user 캐시에 user.id를 키로 하는 엔트리에 메서드의 결과인 User 인스턴스를 저장한다.
- @CacheEvict: userIds 캐시에 user.username을 키로 하는 엔트리를 제거한다.
@Caching 어노테이션에 지정된 여러 캐시 작업은 지정된 순서를 보장하지 않는다.
만약 캐시 관련 작업에 순서가 중요한 경우에는 @Caching 어노테이션에 여러 캐시 작업을 속성으로 지정하는 대신에 명시적으로 캐시 작업을 분리하여 메서드를 정의하는 것이 좋다.
@Caching 어노테이션에서 동일한 키에 대해 @Cacheable과 @CachePut 속성이 함께 지정된 경우 어떻게 동작할까?
@Caching(
cacheable = { @Cacheable(value = "users", key = "#user.id") },
put = { @CachePut(value = "users", key = "#user.id") }
)
public User saveUser(User user) {
// 사용자 정보를 저장하는 로직
return userRepository.save(user);
}
위와 같이 @Caching 어노테이션이 지정된 경우 @CachePut 어노테이션의 특성 때문에 메서드는 항상 실행되고 반환된 결과가 캐시에 저장된다.
동작과정은 다음과 같다.
1. saveUser() 메서드 호출
2. @Cacheable
- @Cacheable은 메서드 내의 로직이 실행되기 전에 캐시를 확인한다.
- 하지만 @CachePut이 존재하므로 @Cacheable의 캐시 확인은 실제로 영향을 미치지 않는다.
3. 메서드 실행: userRepository.save() 가 호출된다.
4. @CachePut
- saveUser() 메서드 호출 결과 반환된 User 인스턴스가 users 캐시에 user.id 키로 엔트리에 저장된다.
다음 코드를 살펴보자.
@Caching(
cacheable = { @Cacheable(value = "users", key = "#userId") },
put = { @CachePut(value = "users", key = "#userId") }
)
public User getUserById(Long userId) {
return userRepository.findById(userId).orElse(null);
}
getUserById() 메서드를 호출 했을 때 동작 과정은 다음과 같다.
1. getUserById() 메서드 호출
2. @Cacheable
- users 캐시에서 userId를 키로 저장된 값을 조회한다.
- 캐시에 해당 키가 존재하면 캐시된 값이 반환되고, userRepository.findById(userId).orElse(null)는 실행되지 않는다.
- 캐시에 해당 키가 없으면 userRepository.findById(userId).orElse(null)는 실행된다.
3. @CachePut
- @CachePut에 의해서 userRepository.findById(userId).orElse(null)는 항상 실행된다.
- userRepository.findById(userId).orElse(null) 실행 결과를 캐시에 저장한다.
결과적으로 위 두가지 케이스 모두 캐시 조회와 캐시 갱신이 동시에 발생하기 때문에 @Cacheable의 효율성이 떨어진다.
@Caching 어노테이션에서 @Cacheable과 함께 @CachePut 혹은 @CacheEvict을 사용하는 것은 항상 메서드의 실행을 발생시키기 때문이다.
일반적으로 @Caching 어노테이션 내에서 조합하여 캐시 동작을 정의 하는 것보다 각각 캐시 기능을 정의하는 것이 바람직하다.
@CacheConfig
@CacheConfig 어노테이션은 클래스 레벨에서 캐시 설정을 공통으로 지정할 수 있도록 한다. 클래스 내 모든 캐시 어노테이션에 적용된다.
속성
- cacheNames: 캐시 이름을 지정한다.
- keyGenerator: 키 생성기를 지정한다.
- cacheManager: 캐시 매니저를 지정한다.
- cacheResolver: 캐시 리졸버를 지정한다.
@CacheConfig(cacheNames = "users")
public class UserService {
@Cacheable(key = "#userId")
public User getUserById(Long userId) {
// DB에서 사용자 정보를 조회하는 로직
return userRepository.findById(userId).orElse(null);
}
@CachePut(key = "#user.id")
public User updateUser(User user) {
// 사용자 정보를 업데이트하는 로직
return userRepository.save(user);
}
@CacheEvict(key = "#userId")
public void deleteUserById(Long userId) {
// 사용자 정보를 삭제하는 로직
userRepository.deleteById(userId);
}
}
UserService 내에 정의된 각 메서드에서 사용하는 캐시는 users 이름의 캐시를 사용한다.
끝.
'스프링부트' 카테고리의 다른 글
logback을 이용한 spring boot 애플리케이션 syslog 전송하기 (0) | 2024.06.07 |
---|---|
Spring Data Redis - cache 기능 사용하기 (0) | 2024.06.02 |
Spring 이벤트 시스템을 이용한 실시간 데이터 변경 감지 및 처리 (0) | 2024.05.17 |
Spring Data Redis - Redis Repository 사용 (0) | 2024.05.12 |
Spring Data Redis - RedisTemplate의 HashOperations (0) | 2024.04.29 |
댓글