Spring Boot Caching with Redis
요즘 Spring을 활용하는 방법에 익숙해 지려고 노력하고 있다. 사실 Spring은 거의 15년여 전 Spring Framework 초기에 잠깐 사용해보고 그 이후는 경험이 전무한 터라 개념 정도는 얘기할 수 있을지라도 요즘 개발자들과 Spring 활용에 대해 말을 썩을 수 있는 처지가 못된다.
이 참에 Spring에 녹아있는 핵심적인 기술들을 하나씩 꺼내와서 이해하고 정리해 보려고 한다. 이 글은 그 시리즈의 첫 번째로 Spring Cache에 대한 내용이다. 인터넷에서 흔히 소개되고 있는 내용들 보다 좀 더 깊숙이 들어가서 어떤 매커니즘에 의해 Caching이 적용되고 동작하는 지 살펴보도록 한다.
시작하기 전에, 이 글에서 설명하는 샘플 어플리케이션은 코드만 봐도 쉽게 이해할 수 있도록 간단하게 작성했다. 아키텍처는 일반적으로 많이 활용하는 Web-WAS-DB 3Tier 기반으로 DB는 PostgreSQL을 그리고 Cache Store로는 Redis를 이용했으니 참고하기 바란다.
Introduction
Spring에서 Caching은 Spring Framework 3.1부터 지원해 오고 있다. 이후 Spring Framework 4.1이 되면서 큰 변화가 발생하는데 JSR-107을 지원함으로써 Caching기능이 확장된다. 이것이 요즘 거의 대부분 필수적으로 활용하는 Caching Annotation 지원의 시초가 된다.
일반적으로 Caching이 어떤 기능인지는 다들 알고 있으리라 생각한다.

위 그림과 같이 데이터베이스로부터 데이터를 읽어오거나 Network으로 부터 데이터를 읽어오는 것과 같이 I/O bound의 동작이나 CPU intensive한 연산의 경우 Application 성능을 높이기 위해 Cache를 적용하는 것이 일반적이다.
Spring Framework은 Cache Abstraction이라는 매커니즘으로 이런 Expensive한 동작의 횟수를 줄일 수 있도록 Method 차원에서 Caching기능을 지원한다.
Cache Abstraction
Spring Framework에서 제공하는 여러 서비스들 처럼 Caching도 Abstraction(추상화)되어 있다. 따라서 개발자가 Cache 데이터 저장에 필요한 Store만 지정해주면 Cache와 CacheManager 인터페이스 구현체인 Abstraction 레벨에서 대부분의 기능을 처리해 줌으로 개발자가 굳이 Caching로직을 직접 구현하지 않아도 된다.
Spring Boot는 Bean 타입의 CacheManager 가 등록된 상태가 아니거나 cacheResolver 이름으로 된 CacheResolver가 정의되지 않은 상태라면 아래의 순서대로 Provider를 자동 탐지한다.
- Generic
- JCache
- Hazelcast
- Infinispan
- Couchbase
- Redis
- Caffeine
- Cache2k
- Simple (위의 Provider를 찾지 못했다면 Default 적용)
CacheManager가 자동으로 설정되어 있다면 이를 인위적으로 교체할 수 있다. application.properties파일에 spring.cache.type property를 이용하여 직접 Provider를 지정하면 해당 Cache Provider를 사용할 수 있다. 예를 들어 Cash Provider를 Redis로 지정하고자 한다면 아래와 같이 설정하면 된다.
spring.cache.type=redis
또한 이 속성을 활용하면 Caching 기능을 끄고 싶을 때 간단하게 해결할 수 있다. 아래와 같이 설정하면 코드를 일일이 수정하지 않고도 쉽게 Caching기능을 중지시킬 수 있다.
spring.cache.type=none
Cache Abstraction 은 클래스의 Method 레벨에서 Caching을 적용한다고 앞에서 설명했다. 즉, Method가 호출 될 때마다 Caching을 위해 먼저 동일한 변수 값으로 이미 호출 된 적이 있는지 확인한다. 이미 호출된 적이 있다면 Method 를 직접 실행하지 않고 Caching된 결과를 반환한다. 그렇지 않다면 Method 실행 후 그 결과를 먼저 Caching하고 반환하도록 한다. 이런 방법으로 Expensive한 Method는 파라미터를 기반으로 최초 한번만 실행하고 이후부터는 Caching된 결과를 재활용하도록 할 수 있다.
Cache Abstraction 은 그 외에도 보이지 않는 부분에서 Cache의 내용을 업데이트하거나 Cache를 삭제하는 등 Cache와 관련된 동작을 지원한다. 이는 Application 실행 중에 Cache 내용이 변경될 경우 내부적으로 Seamless하게 처리하게 된다.
Auto Configuring
Spring Framework은 서비스는 독립적인 모듈로 분리되어 있으며 해당 모듈을 Dependency에 추가하는 것만으로 그 기능을 바로 활용할 수 있도록 하고 있다. 따라서 Spring Boot에서 Caching기능을 활용하고자 한다면 Gradle의 build.gradle파일에 아래와 같이 추가(bold)해주기만 하면 기본 설정은 끝난다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
...
}
Spring Boot starter중 spring-boot-starter-cache 를 Dependency에 추가하면 Spring Context가 Caching기능을 사용할 수 있도록 내부적으로 환경을 구성한다.
이 상태에서 Default 설정만으로 Caching기능을 활용할 수는 있다. 내부적으로 임의의 Cache Store가 추가된 상태가 아니므로 앞에서 설명한 탐지 순서에서 제일 마지막에 제공되는 Simple Provider(ConcurrentHashMap)가 default로 적용된다. 이 Simple Provider는 기본적인 Store만 제공하고 TTL/TTI/Eviction 정책 등을 제공하지 않아 기능이 매우 제한적이다. 따라서, 상용환경에는 절대 적용하지 말아야 한다.
이 글에서는 Cache Store로 Redis를 활용하기로 했으므로 아래와 같이 build.gradle파일에 Redis Dependency만 추가하면 WAS 시작 단계에서 자동으로 Redis Cache Store를 탐지하므로 Cache Store를 지정하는 단계까지 간단하게 마무리할 수 있다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
...
}
이제 Cache Store로 Redis를 활용할 수 있도록 했으니 아래와 같이 Spring의 application.properties파일에 Redis 접속을 위한 기본적인 환경변수를 지정한다. Redis관련 cache default설정은 spring.cache.data.redis.* 속성으로 지정한다. 여기서는 Redis가 local에 설치되어 있고 6379 포트(default)로 서비스되고 있다고 가정한다.
# redist host address(default: localhost)
spring.data.redis.host=localhost
# redis service port(default: 6379)
spring.data.redis.port=6379
# sets cache expiration(if not (m), time is milliseconds). By default the entries never expire
spring.cache.redis.time-to-live=1m
이와 같이 기본적인 환경변수로 Redis 서버와 포트, 그리고 Cache의 TTL(1분)을 설정했지만 사실 이 값들은 설정하지 않아도 된다. 왜냐하면 각 Property의 default 값이 각각 localhost, 6379, 그리고 no-expire(삭제되지 않음)이기 때문이다.
환경설정만으로는 할 수 없는 좀 더 섬세한 제어를 하고 싶을 경우 자신만의 RedisCacheConfiguration 객체를 만들어 @Bean으로 SpringContext에 추가해 주기만 하면 Redis Provider가 자동으로 인식한다. 이 과정은 추후에 자세하게 설명하기로 한다.
이제 Spring Application 클래스에 @EnableCaching Annotation을 지정하기만 하면 Spring Boot는 내부적으로 Caching기능을 자동설정(auto-configuration)하도록 해준다.
Enabling Caching Annotation
자, 이제 본격적으로 샘플코드를 활용하여 설명을 이어가 보도록 하겠다.
Cache annotation인 @EnableCaching 만 설정했다고 해서 자동적으로 Caching기능이 동작하는 것은 아니다. Caching기능은 Spring의 다른 기능들처럼 선언적인 방법으로 지정해 주어야 한다. 즉, @Configuration 이 지정된 클래스에 추가적으로 @EnableCaching annotation을 지정해 주어야만 시작단계에서 자동으로 인식하고 동작하게 되어있다. 번거롭기는 하지만 이는 프로그램 동작 중 Caching 기능에 문제가 의심될 경우 코드 상에서 Caching관련 annotation을 일일이 제거하는것 보다 @EnableCaching annotation 하나만 제거하더라도 Caching기능을 중지시킬 수 있는 순기능도 제공한다. (앞에서 설명한 spring.cache.type=none 도 동일한 기능을 함)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class CachingApplication {
/**
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}
}
위 코드에서 @SpringBootApplication은 내부적으로 @Configuration을 내포하고 있기 때문에 여기에 @EnableCaching annotation을 추가하면 Caching기능이 정상적으로 인식하고 동작하게 된다.

이제 Spring Boot 개발에서 일반적 템플릿인 Controller, Repository, Model, Service 별로 각각 간단한 샘플 코드를 추가해 보기로 하자. 앞에서도 언급했지만 이 글의 성격상 Spring의 여러 기능들 중 Caching 기능에 촛점을 맞추어 설명을 이어가고자 한다. 샘플 Application은 최대한 간단하게 구성하며, 데이터베이스에 Employee 테이블(id, name, address)을 하나 만들고 JPA를 이용하여 해당 테이블에 있는 특정 데이터를 찾아 출력하는 RESTful API 형태이다. 이 과정에서 Caching을 어떻게 적용하는지 살펴보도록 한다.
Model
데이터베이스 테이블(Employee) 데이터의 DTO 클래스(POJO)로 아래와 같다. 이 부분은 간단하므로 설명은 생략한다.
package com.example.demo.model;
import java.io.Serializable;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(nullable = false)
private int id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String address;
/**
* Default Constructor. Needed for Spring Framework when creating managed bean object
*/
public Employee() {}
/**
* @param id
* @param name
* @param address
*/
public Employee(int id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
}
Repository
JpaRepository 인터페이스를 상속받아 EmployeeRepository를 정의한다. 이 부분도 설명을 생략한다.
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.demo.model.Employee;
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
Service
Service는 Controller가 Client로부터 요청을 받으면(Facade) 그 요청을 실제 처리하는 클래스로 Employees라는 이름으로 정의한다. 이 클래스가 정의하는 메소드단에서 실제 Caching기능을 적용한다.
package com.example.demo.service;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.example.demo.model.Employee;
import com.example.demo.repository.EmployeeRepository;
/**
* Employees class represents database table named employee
*/
@Service
public class Employees {
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* repository will be auto-wired through constructor
*/
private final EmployeeRepository repository;
/**
* Constructor
* @param repository the instance will be injected through Spring DI
*/
public Employees(EmployeeRepository repository) {
this.repository = repository;
}
/**
*
* @param employee
* @return
*/
public Employee saveEmployee(Employee employee) {
log.info("Save the record");
return repository.save(employee);
}
@Cacheable(cacheNames = "employee")
public Employee getEmployeeById(int id) throws InterruptedException {
TimeUnit.SECONDS.sleep(2); // thread sleep for 2 seconds
log.info("Scenario: 2 seconds consumed in finding an Employee by id: {}", id);
return repository.findById(id).orElse(null);
}
@CachePut(cacheNames = "employee", key = "#employee.id")
public Employee updateEmployee(Employee employee) {
log.info("Update the record with id: {}", employee.getId());
return repository.save(employee);
}
@CacheEvict(cacheNames = "employee", key = "#id")
public void deleteEmployee(int id) {
log.info("Delete the record id: {}", id);
repository.deleteById(id);
}
}
이 클래스의 getEmployeeById, updateEmployee, updateEmployee 메소에 각각 @Cacheable, @CachePut, @CacheEvict 와 같이 annotation을 활용하여 Caching기능을 적용하였다. 예를 들어 @Cacheable이 적용된 getEmployeeById() Method의 경우, 파라미터인 id를 이용하여 데이터베이스에서 해당 id의 Employee를 검색한 후 이 값을 반환하며 Cache Proxy는 그 결과를 Controller로 반환하기 전에 Intercept하여 Redis로 값을 Caching한다.
이후에 동일한 id를 가진 파라미터 값이 들어올 경우 Cache Proxy는 getEmployeeById() Method를 다시 실행하지 않고 Cache에 저장된 값을 바로 반환한다. getEmployeeById() Method는 데이터베이스 검색에 2초(TimeUnit.SECONDS.sleep(2);)가 소요도록 시뮬레이션했기 때문에 최초 호출에서는 Caching된 상태가 아니므로 응답 받을 때까지 2초가 걸리지만 이후 호출부터는 이미 Caching된 값을 활용하므로 지체없이 결과를 확인할 수 있다.
Spring의 Caching Abstraction은 Caching을 선언할 수 있도록 아래와 같은 Java Annotation을 제공한다. 자세한 내용은 JCache Annotatioins에서 확인이 가능하다.
- @Cacheable: 캐시를 생성한다.
Method의 결과 값을 캐싱하며 가장 간단한 설정은 다음과 같이 캐시 이름만 지정하는 것이다. (@Cacheable(“employee”)) - @CacheEvict: 캐시를 제거한다.
- @CachePut: Method의 실행일 방해하지 않으면서 캐시를 업데이트 한다.
- @Caching: 하나의 Method에 복수 개의 캐싱 동작이 가능하도록 한다.
- @CacheConfig: 클래스 레벨에서 Cache와 연관된 설정을 서로 공유한다.
클래스 레벨에서 지정하며 Cache 이름, Custom KeyGenerator, Custom CacheManager, Custom CacheResolver를 지정하고 클래스 전체에서 공유할 수 있다.
Default Key Generation
Cache는 기본적으로 Key-Value 형태를 가지므로 Cache에 저장된 값에 접근할 수 있도록 Method와 매핑되는 키가 필요하다. Cache Abstraction은 아래의 알고리즘에 기반하여 KeyGenerator를 적용한다.
- 파라미터가 없을 경우 SimpleKey.EMPTY를 반환
- 파라미터가 1개일 경우 해당 파라미터의 인스턴스를 반환
- 파라미터가 1개 이상일 경우 모든 파라미터를 포함하는 SimpleKey를 반환
이 방법은 파라미터들의 값이 일반적이면서 hashCode()와 equals() Method를 적절하게 구현 되었을 경우는 대부분의 경우 잘 동작하지만 그렇지 않은 경우 org.springframework.cache.interceptor.KeyGenerator 인터페이스를 직접 구현해서 적용해야 한다. KeyGenerator를 직접 만들었다면 아래 코드와 같이 Custom KeyGenerator를 @Bean으로 만들고 그 이름을 등록해 주면 된다.
@Cacheable(cacheNames="employee", keyGenerator="myKeyGenerator")
Method Visibility and Annotations
Cache annotation은 public visibility 를 가진 Method에만 지정할 수 있다. protected, private 또는 package visibility 를 가진 Method에 Caching관련 annotation 을 지정할 경우 오류가 발생하지는 않지만 Caching관련 설정 정보가 외부로 노출되지 않아 Spring Boot의 Cache Proxy가 이를 인식하지 못한다. 따라서 public Method 이외에 Caching annotation을 지정할 경우 Cache기능이 적용되지 않는다는 점을 주의해야 한다.
또한 Spring은 Interface에 @Cache* annotation을 지정하는 것이 아니라 Concrete 클래스 즉, Interface를 구현한 클래스와 Method에 annotation 을 지정하도록 가이드하고 있다. Interface에 @Cache* annotation을 지정할 수는 있지만 이 경우 proxy모드를 설정(mode=”proxy”)해야만 동작한다는 점도 주의해야 한다.
그리고 Proxy특성상 @Cache* annotation을 설정한 클래스의 외부로부터 들어오는 method call에 대해서만 동작한다. 따라서, 클래스 내부에서 자체적으로 호출하는 경우는 호출받는 method가 @Cacheable로 설정되었다 하더라도 Caching 기능이 동작하지 않는다.
Synchronized Caching
Multi-Thread 환경에서는 동일한 파라미터 값으로 operation이 동시에 호출될 수 있다. Cache Abstraction 은 기본적으로 Lock을 제공하지 않는다. 따라서 Caching의 목적과 맞지않게 동일한 값으로 여러 번 연산이 수행될 수도 있다. 이런 경우를 대비해서 아래와 같이 sync 속성을 지정하면 연산중일 때 하나의 Thread만 연산을 수행하도록 Cache Provider가 Cache 진입에 Lock을 걸 수 있도록 지원한다.
@Cacheable(cacheNames="employee", sync=true)
참고로 이 속성은 Optional 기능이라는 점을 참고하기 바란다. 왜냐하면 사용 중인 Cache Store 라이브러리가 이 기능을 지원하지 않을 수도 있기 때문이다.
Conditional Caching
특정 조건에서는 Caching 기능을 적용하는 것이 부적절 하다면 condition 파라미터를 활용하면 된다. 이 파라미터는 SpEL(Spring Expression Language)를 지원한다. EL의 결과가 true이면 Cache가 적용된다. 반대로 Cache에 값을 저장하는 것을 방지하고자 할 경우 unless를 활용하면 된다. 참고로 unless는 condition과 달리 Method가 실행된 이후에 EL평가가 이루어 진다.
@Cacheable(cacheNames="employee", condition=#name.length() < 10, unless="#address.length() < 20")
Spring Framework은 Caching에서 SpEL에 활용할 수 있는 내장 파라미터를 지원하고 있으며 Available Caching SpEL Evaluation Context에서 확인할 수 있다.
Controller
클라이언트로 요청이 오면 제일 먼저 처리하는 Facade 역할을 하며 여기서 RESTful API의 유형에 따라 해당 Service로 요청을 분기하는 역할을 수행한다. 예를 들어 클라이언트의 요청 URL이 http://localhost/employees/1일 경우 getEmployee() 메소드가 실행된다.
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.model.Employee;
import com.example.demo.service.Employees;
/**
* Controller class that handles requests mapped with '/employees' from client
*/
@RestController
@RequestMapping("/employees")
public class EmployeeController {
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* repository will be auto-wired through constructor
*/
private final Employees employees;
/**
* Constructor
* @param employees the instance will be injected through Spring DI
*/
public EmployeeController(Employees employees) {
this.employees = employees;
}
@PostMapping("/save")
public ResponseEntity<Employee> saveEmployee(@RequestBody Employee employee) {
return new ResponseEntity<Employee>(employees.saveEmployee(employee), HttpStatus.CREATED);
}
@PutMapping("/update")
public ResponseEntity<Employee> updateEmployee(@RequestBody Employee employee) {
return new ResponseEntity<Employee>(employees.updateEmployee(employee), HttpStatus.OK);
}
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployee(@PathVariable int id) throws InterruptedException {
log.info("Handle request for GET /{}", id);
return new ResponseEntity<Employee>(employees.getEmployeeById(id), HttpStatus.OK);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable int id) {
employees.deleteEmployee(id);
return new ResponseEntity<Void>(HttpStatus.ACCEPTED);
}
}
작성된 Spring Boot Application을 실행한 후 http://localhost/employees/1를 여러 번 호출해 보면 아래와 유사한 실행 로그를 확인할 수 있다.
2025-03-03T01:08:36.072+09:00 INFO 8312 --- [Caching] [nio-8090-exec-1] c.e.demo.controller.EmployeeController : Handle request for GET /1
2025-03-03T01:08:38.230+09:00 INFO 8312 --- [Caching] [nio-8090-exec-1] com.example.demo.service.Employees : Scenario: 2 seconds consumed in finding an Employee by id: 1
Hibernate:
select
e1_0.id,
e1_0.address,
e1_0.name
from
employee e1_0
where
e1_0.id=?
2025-03-03T01:08:42.439+09:00 INFO 8312 --- [Caching] [nio-8090-exec-2] c.e.demo.controller.EmployeeController : Handle request for GET /1
2025-03-03T01:08:43.226+09:00 INFO 8312 --- [Caching] [nio-8090-exec-3] c.e.demo.controller.EmployeeController : Handle request for GET /1
2025-03-03T01:08:43.965+09:00 INFO 8312 --- [Caching] [nio-8090-exec-4] c.e.demo.controller.EmployeeController : Handle request for GET /1
위 로그에서 보듯이 최초 호출에는 데이터베이스가 호출 되었으며 시뮬레이션으로 2초가 지체된 것을 확인할 수 있지만 이후 연속되는 호출에서는 결과가 즉시 반환됨을 확인할 수 있다.
최초 요청을 실행한 후의 Cache상태를 보면 아래와 같이 “skanto::employee::1” 키로 Cache가 생성되어 있는 것을 확인할 수 있다.
root@0fae453aa5eb:/data# redis-cli
127.0.0.1:6379> keys *
1) "skanto::employee::1"
ttl로 설정된 1분이 지나면 Cache가 삭제되는 것도 확인할 수 있다.
127.0.0.1:6379> keys *
(empty array)
Extending Default Configurations
지금까지는 비교적 간단한 방법으로 Spring Boot에서 제공하는 Caching기능과 활용방법에 대해 알아 보았다.
application.properties파일을 통해 설정할 수 있는 속성들이 제한적일 경우 프로그래밍으로 직접 기능을 확장시킬 수 있다. RedisCacheConfiguration을 직접 생성하여 default로 설정값 이외의 다양한 속성들을 설정할 수 있으며 이렇게 생성한 RedisCacheConfiguration을 @Bean으로 등록하면 간단하게 default 속성을 오버라이드할 수 있다. 다음은 default로 설정된 ttl값(1분)을 10초로 변경하고 key-prefix를 “skanto::”에서 “key::”로 변경하는 코드를 보여준다.
package com.example.demo.config;
import java.time.Duration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
@Configuration
public class RedisConfig {
@Bean
RedisCacheConfiguration configure() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(10))
.disableCachingNullValues()
.prefixCacheNameWith("key::");
}
}
Custom CacheManager로 설정된 값으로 서비스를 실행하면 아래와 같이 Key가 “key::employee::1“로 Cache가 생성되고 10초 후에 자동으로 Cache가 삭제됨을 확인할 수 있다. 좀 더 자세한 내용을 확인하고 싶다며 여기를 참고하기 바란다.
127.0.0.1:6379> keys *
1) "key::employee::1"
마치며…
Spring Framework을 활용한 어플리케이션의 규모가 커질 경우 성능을 개선을 위해 필수적으로 고민해야할 기능 중 하나인 Spring Cach에 대해 알아보았다. 아쉽게도 인터넷에 널려 있는 자료들은 대부분 천편일률적이고 내부 동작원리에 대한 소개가 거의 없을 뿐만 아니라 어떻게 기능을 Customizing 해야 할지에 대한 가이드를 제시하지 못하고 있어 개인적으로 답답한 부분이 많았었다. 이 글을 작성하게 된 계기도 여기에 기인한다고 볼 수 있다.
이 글에서 소개한 내용은 Spring Boot에서 Caching기능을 어떻게 적용하는지, Cache를 위해 적용할 수 있는 Annotation 종류와 활용, 그리고 Default Caching기능을 확장해서 Customizing하는 방법까지 포함하고 있어 큰 그림을 비교적 쉽게 이해하고 개발에 필요한 시작 가이드로는 손색이 없으리라 생각한다. 하지만 모든 내용을 다 담지는 못한 터라 부족한 설명이나 특별히 필요한 부분은 인터넷 또는 서적을 이용하면 길을 잃지 않고 원하는 답을 쉽게 찾을 수 있으리라 생각한다. 내용을 정리하면서 찾아본 자료들 중에서 가장 명료한 설명을 제공하는 곳으로는 역시 Spring Boot 공식 사이트가 최고인것 같다.
개인적으로 Spring Boot의 Caching기능을 익히면서 짧은 경험이지만 양가의 감정을 느끼게 된다. Spring Framework은 Annotation에 의존적인 부분이 많아 개발자마다 접근 경로 및 개발 방법이 달라질 수가 있고 개발과정에서 통일된 표준, 그리고 충분한 가이드가 제공되지 않는다면 개발이 진행되면 될수록 개발부담이 커지고 유지보수에 어려움이 발생할 것이라는 우려가 앞선다. 하지만, 대부분의 개발과정에서 필요로 하는 기능들을 최소한의 노력으로 개발자들이 편리하게 적용할 수 있도록 탄탄한 기반 프레임워크를 제공하고 있어 체계적이고 표준화된 방법으로 접근한다면 좋은 점수를 주고 싶다.
부록: Install Backend Application(PostgreSQL, Redis)
Docker 덕분에 필요로 하는 Backend Application을 매우 편리한 방법으로 설치하고 활용해 볼 수 있다. 간단하지만 컨테이너 설치를 위해 활용했던 부분들을 기록해 둔다.
PostgreSQL 컨테이너 설치 및 실행
PostgreSQL은 아래의 docker 명령으로 최신버전(13.20)의 컨테이너를 쉽게 설치 및 실행할 수 있다.
docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -v postgre_db:/var/lib/postgresql/data -p 5432:5432 --restart always postgres:13.20
참고로, 데이터베이스 연결은 5432포트(default)를 이용하며 사용자 및 패스워드는 postgres/postgres이므로 Spring Framework에서 JPA를 위한 연결 설정은 아래와 같이 application.properties에 추가하면 된다.
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.format_sql=true
기본 설정이 마무리 되면 Console로 데이터베이스 컨테이너에 접속한 후 샘플 어플리케이션 실행을 위해 employee 테이블을 생성하고 샘플 데이터 하나를 넣어 둔다.
skanto@skanto-mbp ~ % docker exec -it postgres bash
root@54ca6f34cbfe:/# psql -U postgres
psql (13.20 (Debian 13.20-1.pgdg120+1))
Type "help" for help.
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+----------+-------+----------
public | employee | table | postgres
public | sample | table | postgres
(2 rows)
postgres=# \d employee
Table "public.employee"
Column | Type | Collation | Nullable | Default
---------+------------------------+-----------+----------+---------
id | integer | | not null |
name | character varying(50) | | |
address | character varying(100) | | |
postgres=# select * from employee;
id | name | address
----+--------+---------
1 | skanto | Gwangju
(1 row)
Redis 컨테이너 설치 및 실행
PostgreSQL과 동일하게 최신버전(7.4.2)의 컨테이너를 설치 및 실행할 수 있다.
docker run --name redis -d -v redis_db:/data -p 6379:6379 --restart always redis:7.4.2 redis-server
Spring Fremework에서 Redis Cache Store가 활용될 수 있도록 아래와 같이 Redis default 속성을 application.properties파일에 설정정보를 추가한다.(앞에서 설명한 부분과 중복 됨)
# Cache type. Bye default, auto-detected according to the environment
spring.cache.type=redis
# if true allows caching null values
spring.cache.redis.cache-null-values=false
# sets cache expiration(if not (m), time is milliseconds). By default the entries never expire
spring.cache.redis.time-to-live=1m
# key prefix
spring.cache.redis.key-prefix=skanto::
# redist host address(default: localhost)
spring.data.redis.host=localhost
# redis service port(default: 6379)
spring.data.redis.port=6379
아래와 같이 Redis 컨테이너에 접속해서 정상적으로 동작하는지 확인한다.
skanto@skanto-mbp ~ % docker exec -it redis bash
root@0fae453aa5eb:/data# redis-cli
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
끝.