
처음 A1BnB 홈페이지에 접근하면 다음과 같이 호스트가 등록한 게시물들을 확인할 수 있다. 현재 홈페이지에서 페이지당 8개씩의 게시물들을 최신순으로 노출시키고 있다.

게시물 조회 관련 클래스들은 다음과 같다.
// 게시물 응답
public record PostResponse(Long postId, String authorName, List<String> photoUrls, String location,
Double pricePerNight, Integer likeCount) {
public PostResponse {
}
}
@Component
public class PostResponseMapper {
public List<PostResponse> toPostResponses(List<Post> posts) {
return posts.stream()
.map(post -> toPostResponse(post))
.collect(Collectors.toList());
}
public PostResponse toPostResponse(Post post) {
return new PostResponse(
post.getPostId(),
post.getAuthor().getName(),
getPhotoUrls(post),
post.getLocation(),
post.getPricePerNight(),
post.getLikeCount()
);
}
private List<String> getPhotoUrls(Post post) {
return post.getPhotos().stream()
.map(Photo::getOriginalUrl)
.collect(Collectors.toList());
}
}
// 게시물 응답 DTO Page 반환
@Override
@Transactional(readOnly = true)
public Page<PostResponse> getAllPosts(Pageable pageable) {
Page<Post> postPage = postRepository.findAll(pageable);
// 게시물 응답 DTO 반환
return postPage.map(postResponseMapper::toPostResponse);
}
게시물 조회시 각 Post 엔티티의 필드들에 접근해야하기 때문에, 이용자들이 홈페이지에 접근할 때마다 MySQL 데이터베이스에 조회 쿼리를 날려야하므로 비효율적이다.
지난 포스팅에서 연결한 ElastiCashe Redis 캐시를 이용해 게시판 조회 성능을 올려보자!
우선 Application 클래스에 @EnableCaching 어노테이션을 적용해야 한다.
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
메서드에 캐싱을 적용하려면 우선 @Cacheable에 대해 알아보자.
@Cacheable 어노테이션은 메서드의 실행 결과를 캐싱할 때 사용되며, 다양한 파라미터를 통해 캐싱 동작 방식을 제어할 수 있다.
- cacheNames: 캐시 이름 지정, 여러 캐시 지정 가능
- key: 캐시에 저장될 데이터의 키 지정
- condition: 캐싱을 적용할 조건
- unless: 메서드의 반환값을 검사하여 조건이 true일 경우 결과를 캐시 추가 X,
condition이 메서드 호출 전에 평가되는 반면, unless는 메서드 호출 후에 평가- keyGenerator: key 파라미터 대신 사용, 캐시 키를 생성하기 위한 커스텀 KeyGenerator를 지정
- sync: 여러 스레드가 동시에 같은 키로 캐시되지 않은 메서드를 호출하면, sync가 true로 설정되어 있을 때 하나의 스레드만 메서드를 실행하고 나머지 스레드는 결과를 공유받음. 동시성 문제를 방지
앞서 설명한 KeyGenerator를 이용해 Page의 number로 키를 지정해보자.
@Component("cachingKeyGenerator")
public class CachingKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
Pageable pageable = (Pageable) params[0];
return "Page:" + pageable.getPageNumber();
}
}
// 게시물 응답 DTO Page 반환
@Override
@Transactional(readOnly = true)
@Cacheable(cacheNames = "findPosts", keyGenerator = "cachingKeyGenerator")
public Page<PostResponse> getAllPosts(Pageable pageable) {
Page<Post> postPage = postRepository.findAll(pageable);
// 게시물 응답 DTO 반환
return postPage.map(postResponseMapper::toPostResponse);
}
데이터를 캐싱하려면 저장하는 객체를 직렬화해야 한다.
// 게시물 응답
public record PostResponse(Long postId, String authorName, List<String> photoUrls, String location,
Double pricePerNight, Integer likeCount) implements Serializable {
public PostResponse {
}
}
테스트를 위해 우선 redis-cli를 준비하자.
현재 redis에는 어떤한 데이터도 저장되어 있지 않은 상태이다.


홈페이지 접근이후 getAllPosts 메서드를 통해 데이터가 저장된것을 확인할 수 있다.

만약 기존의 게시물 리스트에서 게시물의 추가나 삭제가 행해진다면.. 페이지의 정보가 변하기 때문에 redis에서 관리하는 데이터들이 더이상 쓸모가 없어진다.
캐시 무효화는 캐싱된 데이터를 최신 상태로 유지하기 위해, 더 이상 유효하지 않거나 오래된 데이터를 캐시에서 제거하는 과정을 뜻한다.
이번에는 @CacheEvict를 통해 캐시 데이터를 삭제해보자.
allEntries는 true시에 해당 캐시의 모든 항목을 제거해준다.
// 게시물 등록
@Override
@Transactional
@CacheEvict(cacheNames = "findPosts", allEntries = true)
public void registerPost(String username, PostUploadRequest requestParam) {
Member currentMember = memberService.findMember(username);
List<Photo> photos = photoService.findPhotos(requestParam.photoIdList());
List<Date> availableDates = dateService.getDates(requestParam.startDate(), requestParam.endDate());
savePost(requestParam, currentMember, photos, availableDates);
}
게시물 업로드로 확인해보자.

저장된 데이터가 삭제 된것을 확인할 수 있다.
