Board 엔티티만들기
@Entity
@Table(name = "boards")
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@CreatedDate
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdAt;
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
}
BoardController
@RestController
@RequestMapping("boards")
public class BoardController {
private BoardService boardService;
public BoardController(BoardService boardService) {
this.boardService = boardService;
}
@GetMapping()
public List<Board> getBoards(
@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size
) {
return boardService.getBoards(page, size);
}
}
BoardService
@Service
public class BoardService {
private BoardRepository boardRepository;
public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
public List<Board> getBoards(int page, int size) {
Pageable pageable = PageRequest.of(page - 1, size);
Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
return pageOfBoards.getContent();
}
}
BoardRepository
public interface BoardRepository extends JpaRepository<Board, Long> {
Page<Board> findAllByOrderByCreatedAtDesc(Pageable pageable);
}
더미데이터 넣기
-- 높은 재귀(반복) 횟수를 허용하도록 설정
-- (아래에서 생성할 더미 데이터의 개수와 맞춰서 작성하면 된다.)
SET SESSION cte_max_recursion_depth = 1000000;
-- boards 테이블에 더미 데이터 삽입
INSERT INTO boards (title, content, created_at)
WITH RECURSIVE cte (n) AS
(
SELECT 1
UNION ALL
SELECT n + 1 FROM cte WHERE n < 1000000 -- 생성하고 싶은 더미 데이터의 개수
)
SELECT
CONCAT('Title', LPAD(n, 7, '0')) AS title, -- 'Title' 다음에 7자리 숫자로 구성된 제목 생성
CONCAT('Content', LPAD(n, 7, '0')) AS content, -- 'Content' 다음에 7자리 숫자로 구성된 내용 생성
TIMESTAMP(DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 3650 + 1) DAY) + INTERVAL FLOOR(RAND() * 86400) SECOND) AS created_at -- 최근 10년 내의 임의의 날짜와 시간 생성
FROM cte;
✅ Spring Boot 프로젝트에 Redis 셋팅 추가하기
1. Redis 의존성 추가하기
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
2. application.yml 수정하기
# local 환경
spring:
profiles:
default: local
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
data:
redis:
host: localhost
port: 6379
logging:
level:
org.springframework.cache: trace # Redis 사용에 대한 로그가 조회되도록 설정
3. Redis 설정 추가하기
config/RedisConfig
@Configuration
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
// Lettuce라는 라이브러리를 활용해 Redis 연결을 관리하는 객체를 생성하고
// Redis 서버에 대한 정보(host, port)를 설정한다.
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
}
}
config/RedisCacheConfig
@Configuration
@EnableCaching // Spring Boot의 캐싱 설정을 활성화
public class RedisCacheConfig {
@Bean
public CacheManager boardCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
.defaultCacheConfig()
// Redis에 Key를 저장할 때 String으로 직렬화(변환)해서 저장
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
// Redis에 Value를 저장할 때 Json으로 직렬화(변환)해서 저장
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer<Object>(Object.class)
)
)
// 데이터의 만료기간(TTL) 설정
.entryTtl(Duration.ofMinutes(1L));
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
}
}
4. BoardService에 캐싱 로직 추가하기
BoardService
@Service
public class BoardService {
private BoardRepository boardRepository;
private Pageable pageable;
public BoardService(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
@Cacheable(cacheNames = "getBoards", key = "'boards:page:' + #page + ':size:' + #size", cacheManager = "boardCacheManager")
public List<Board> getBoards(int page, int size) {
Pageable pageable = PageRequest.of(page - 1, size);
Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
return pageOfBoards.getContent();
}
}
@Cacheable 어노테이션을 붙이면 Cache Aside 전략으로 캐싱이 적용된다. 즉, 해당 메서드로 요청이 들어오면 레디스를 확인한 후에 데이터가 있다면 레디스의 데이터를 조회해서 바로 응답한다. 만약 데이터가 없다면 메서드 내부의 로직을 실행시킨 뒤에 return 값으로 응답한다. 그리고 그 return 값을 레디스에 저장한다.
[속성 값 설명]
- cacheNames : 캐시 이름을 설정
- key : Redis에 저장할 Key의 이름을 설정
- cacheManager : 사용할 cacheManager의 Bean 이름을 지정
✅ 테스트 해보기
1. Spring Boot 서버를 실행시켜서 API 실행시켜보기
Cache가 존재하지 않아서 DB로부터 데이터를 조회한 뒤 Cache를 생성했다고 로그가 찍혀있다.
한 번 더 새로고침을 해보면 Cache가 생성(Creating)되지 않고 기존 Cache를 조회해왔음을 알 수 있다.
2. Redis-cli를 활용해 정상적으로 캐싱이 됐는 지 확인하기
$ redis-cli
$ keys * # Redis에 저장되어 있는 모든 key 조회
$ get getBoards::boards:page:1:size:10 # 특정 key의 Value 조회
$ ttl getBoards::boards:page:1:size:10 # 특정 key의 TTL 조회
✅Postman으로 Redis를 적용하기 전후 성능 비교
✅ Redis 적용 전
@Service
public class BoardService {
...
// @Cacheable(cacheNames = "getBoards", key = "'boards:page:' + #page + ':size:' + #size", cacheManager = "boardCacheManager")
public List<Board> getBoards(int page, int size) {
Pageable pageable = PageRequest.of(page - 1, size);
Page<Board> pageOfBoards = boardRepository.findAllByOrderByCreatedAtDesc(pageable);
return pageOfBoards.getContent();
}
}
캐싱을 적용시키는 어노테이션을 주석 처리한 뒤 포스트맨으로 테스트를 해보자.
윈도우가 느려서 그런지 1259ms 정도가 나온다..
✅ Redis 적용 후
다시 주석해제하고 실행시켜보자.
말도 안되게 속도가 줄은 것을 확인할 수 있다.
'🏛️Infra > Redis' 카테고리의 다른 글
부하 테스트를 통해 Redis 적용 전후 성능 비교 (0) | 2025.03.16 |
---|---|
AWS EC2에서 Redis 활용 (0) | 2025.03.15 |
Redis 캐싱 전략 (0) | 2025.03.15 |
Redis (0) | 2025.03.12 |