Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.be.sportizebe.domain.comment.controller;

import com.be.sportizebe.domain.comment.dto.request.CreateCommentRequest;
import com.be.sportizebe.domain.comment.dto.response.CommentListResponse;
import com.be.sportizebe.domain.comment.dto.response.CommentResponse;
import com.be.sportizebe.domain.comment.service.CommentService;
import com.be.sportizebe.domain.user.entity.User;
Expand Down Expand Up @@ -38,10 +39,10 @@ public ResponseEntity<BaseResponse<CommentResponse>> createComment(

@GetMapping
@Operation(summary = "댓글 목록 조회", description = "게시글의 댓글 목록 조회 (대댓글 포함)")
public ResponseEntity<BaseResponse<List<CommentResponse>>> getComments(
public ResponseEntity<BaseResponse<CommentListResponse>> getComments(
@Parameter(description = "게시글 ID") @PathVariable Long postId) {
List<CommentResponse> responses = commentService.getCommentsByPostId(postId);
return ResponseEntity.ok(BaseResponse.success(responses));
CommentListResponse response = commentService.getCommentsByPostId(postId);
return ResponseEntity.ok(BaseResponse.success(response));
}

@DeleteMapping("/{commentId}")
Expand All @@ -50,7 +51,7 @@ public ResponseEntity<BaseResponse<Void>> deleteComment(
@Parameter(description = "게시글 ID") @PathVariable Long postId,
@Parameter(description = "댓글 ID") @PathVariable Long commentId,
@AuthenticationPrincipal User user) {
commentService.deleteComment(commentId, user);
commentService.deleteComment(postId, commentId, user);
return ResponseEntity.ok(BaseResponse.success("댓글 삭제 성공", null));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.be.sportizebe.domain.comment.dto.response;

import java.util.List;

public record CommentListResponse(
List<CommentResponse> comments
) {
public static CommentListResponse of(List<CommentResponse> comments) {
return new CommentListResponse(comments);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.be.sportizebe.domain.comment.service;

import com.be.sportizebe.domain.comment.dto.request.CreateCommentRequest;
import com.be.sportizebe.domain.comment.dto.response.CommentListResponse;
import com.be.sportizebe.domain.comment.dto.response.CommentResponse;
import com.be.sportizebe.domain.user.entity.User;

Expand All @@ -12,10 +13,10 @@ public interface CommentService {
CommentResponse createComment(Long postId, CreateCommentRequest request, User user);

// 게시글의 댓글 목록 조회
List<CommentResponse> getCommentsByPostId(Long postId);
CommentListResponse getCommentsByPostId(Long postId);

// 댓글 삭제
void deleteComment(Long commentId, User user);
void deleteComment(Long postId, Long commentId, User user);

// 게시글의 댓글 수 조회
long getCommentCount(Long postId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.be.sportizebe.domain.comment.service;

import com.be.sportizebe.domain.comment.dto.request.CreateCommentRequest;
import com.be.sportizebe.domain.comment.dto.response.CommentListResponse;
import com.be.sportizebe.domain.comment.dto.response.CommentResponse;
import com.be.sportizebe.domain.comment.entity.Comment;
import com.be.sportizebe.domain.comment.exception.CommentErrorCode;
Expand All @@ -13,6 +14,8 @@
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;
Expand All @@ -26,6 +29,7 @@ public class CommentServiceImpl implements CommentService {
private final PostRepository postRepository;

@Override
@CacheEvict(cacheNames = {"commentList", "commentCount"}, key = "#postId")
@Transactional
public CommentResponse createComment(Long postId, CreateCommentRequest request, User user) {
// 게시글 조회
Expand Down Expand Up @@ -57,22 +61,25 @@ public CommentResponse createComment(Long postId, CreateCommentRequest request,
}

@Override
@Cacheable(cacheNames = "commentList", key = "#postId")
@Transactional
public List<CommentResponse> getCommentsByPostId(Long postId) {
public CommentListResponse getCommentsByPostId(Long postId) {
// 게시글 조회
Post post = postRepository.findById(postId)
.orElseThrow(() -> new CustomException(PostErrorCode.POST_NOT_FOUND));

List<Comment> comments = commentRepository.findByPostAndParentIsNullOrderByCreatedAtAsc(post);

return comments.stream()
List<CommentResponse> list = comments.stream()
.map(CommentResponse::from) // 여기서 LazyInitializationException가 터질 수 있기때문에 @Transaction필요
.toList();

return CommentListResponse.of(list);
}

@Override
@CacheEvict(cacheNames = {"commentList", "commentCount"}, key = "#postId")
@Transactional
public void deleteComment(Long commentId, User user) {
public void deleteComment(Long postId, Long commentId, User user) {
Comment comment = commentRepository.findById(commentId)
.orElseThrow(() -> new CustomException(CommentErrorCode.COMMENT_NOT_FOUND));

Expand All @@ -85,6 +92,7 @@ public void deleteComment(Long commentId, User user) {
}

@Override
@Cacheable(cacheNames = "commentCount", key = "#postId")
public long getCommentCount(Long postId) {
Post post = postRepository.findById(postId)
.orElseThrow(() -> new CustomException(PostErrorCode.POST_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.be.sportizebe.global.config;

import com.be.sportizebe.domain.comment.dto.response.CommentListResponse;
import com.be.sportizebe.domain.post.dto.response.PostPageResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
Expand Down Expand Up @@ -27,12 +28,11 @@ public class RedisCacheConfig {
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 캐시 역직렬화를 위해 타입 정보(@class)를 포함하도록 설정
ObjectMapper objectMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.addModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
// ❗ 타입 정보 절대 쓰지 않음
.build();
// 캐시에 들어가는 값의 직렬화 방식 결정

// 기본 캐시(대부분)는 Object로 직렬화/역직렬화
Jackson2JsonRedisSerializer<Object> defaultValueSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
Expand All @@ -42,8 +42,19 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
Jackson2JsonRedisSerializer<PostPageResponse> postListValueSerializer =
new Jackson2JsonRedisSerializer<>(PostPageResponse.class);
postListValueSerializer.setObjectMapper(objectMapper);
// TTL(캐시 수명) 정책 정의
// 아무 설정 없는 캐시(Default) = 5분

// CommentList 캐시는 CommentListResponse 타입으로 역직렬화
Jackson2JsonRedisSerializer<CommentListResponse> commentListSerializer =
new Jackson2JsonRedisSerializer<>(CommentListResponse.class);
commentListSerializer.setObjectMapper(objectMapper);

// commentCount 캐시는 Long 타입으로 역직렬화
Jackson2JsonRedisSerializer<Long> commentCountSerializer =
new Jackson2JsonRedisSerializer<>(Long.class);
commentCountSerializer.setObjectMapper(objectMapper);

// 기본 캐시 설정
// TTL: 5분, Serializer: Object 기준
RedisCacheConfiguration defaultConfig =
RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
Expand All @@ -54,7 +65,8 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();


// 캐시별로 TTL override 가능 ( post, commet 등 우리가 원하는 값으로 TTL 설정 가능 )
// 개별 캐시 설정
// Post, Comment 등 역직렬화 문제 생기는 것들은 우리가 임의로 설정해주기
cacheConfigs.put(
"facilityNear",
defaultConfig.entryTtl(Duration.ofSeconds(60))
Expand All @@ -71,6 +83,14 @@ public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory)
)
.entryTtl(Duration.ofSeconds(30))
);
cacheConfigs.put(
"commentList",
RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(commentListSerializer)
)
.entryTtl(Duration.ofSeconds(30))
);
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(cacheConfigs)
Expand Down