코드 보는 법

이소연's avatar
Aug 05, 2024
코드 보는 법

setting >> plugins

notion image
 

비지니스 파악

 
CommService
package com.example.aboutme.comm; import com.example.aboutme._core.error.exception.Exception404; import com.example.aboutme.comm.ResourceNotFoundException.ResourceNotFoundException; import com.example.aboutme.reply.Reply; import com.example.aboutme.reply.ReplyRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @RequiredArgsConstructor @Service public class CommService { private final CommRepository commRepository; private final ReplyRepository replyRepository; public List<CommResponse.CommAndReplyDTO> findAllCommsWithReply() { return commRepository.findAllCommsWithReply(); } @Transactional public CommResponse.CommDetailDTO getCommDetail(int id) { // 주어진 ID로 게시글을 가져옵니다. Comm comm = commRepository.findById(id) .orElseThrow(() -> new Exception404("게시물을 찾을 수 없습니다")); // 주어진 게시글의 댓글을 가져옵니다. List<Reply> replies = replyRepository.findByCommId(comm.getId()); // 같은 카테고리의 다른 글들과 해당 글들의 댓글을 가져옵니다. List<Comm> relatedComms = commRepository.findByCategoryWithRepliesAndExcludeId(comm.getCategory(), comm.getId()); return new CommResponse.CommDetailDTO(comm, replies, relatedComms); } public Comm findById(Integer id) { Optional<Comm> commOptional = commRepository.findById(id); return commOptional.orElse(null); // orElse(null)을 사용하여 엔티티가 없을 경우 null 반환 } }
 

쿼리 관련, 전부 다 test해보기

💡
ctrl + shift + t
package com.example.aboutme.comm; import com.example.aboutme.comm.enums.CommCategory; import com.example.aboutme.reply.Reply; import com.example.aboutme.reply.ReplyRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import java.util.List; @DataJpaTest class CommServiceTest { @Autowired private CommRepository commRepository; @Autowired private ReplyRepository replyRepository; @Test void findByCatagory() { CommCategory category = CommCategory.GENERAL_CONCERNS; List<Comm> comms = commRepository.findByCategory(category); comms.forEach(comm -> System.out.println("comm = " + comm)); } @Test void getCommDetail() { int id = 1; CommCategory category = CommCategory.GENERAL_CONCERNS; List<Comm> comms = commRepository.findByCategoryWithRepliesAndExcludeId(category, id); comms.forEach(comm -> { System.out.println("comm = " + comm); if (comm.getReplies() != null) { comm.getReplies().forEach(reply -> System.out.println("reply = " + reply)); } else { System.out.println("No replies found for comm id " + comm.getId()); } }); } @BeforeEach void setUp() { } @Test void findById() { int id = 1; Comm comm = commRepository.findById(id).get(); System.out.println("comm = " + comm); } @Test void 댓글조회하기() { int id = 1; List<Reply> replys = replyRepository.findByCommId(id); for (Reply reply : replys) { System.out.println("reply = " + reply); } } }
 
레파지토리 쿼리
package com.example.aboutme.comm; import com.example.aboutme.comm.enums.CommCategory; import com.example.aboutme.user.UserResponse; import jdk.jfr.Category; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface CommRepository extends JpaRepository<Comm, Integer> { // 현재 게시글 ID를 제외하고 같은 카테고리의 다른 게시글을 가져오는 쿼리 List<Comm> findByCategoryAndIdNot(CommCategory category, Long id); List<Comm> findByCategory(CommCategory category); @Query("SELECT c FROM Comm c LEFT JOIN FETCH c.replies r WHERE c.category = :category AND c.id <> :id") List<Comm> findByCategoryWithRepliesAndExcludeId(@Param("category") CommCategory category, @Param("id") Integer id); // 메인 커뮤니티 리스트 @Query(""" SELECT new com.example.aboutme.user.UserResponse$ClientMainDTO$CommDTO( c.id, c.title, c.content, c.category, c.user.profileImage, c.user.name, r.user.profileImage, r.user.name ) FROM Comm c JOIN c.replies r WHERE r.user.userRole = com.example.aboutme.user.enums.UserRole.EXPERT """) List<UserResponse.ClientMainDTO.CommDTO> findCommsWithReply(); // /comm 출력하려고 뽑은 쿼리 @Query(""" SELECT new com.example.aboutme.comm.CommResponse$CommAndReplyDTO( c.id, c.title, c.content, c.category, c.user.profileImage, c.user.name, r.user.userRole, r.user.profileImage, r.user.name, r.solution ) FROM Comm c JOIN c.replies r """) List<CommResponse.CommAndReplyDTO> findAllCommsWithReply(); }
 
CommResponse
package com.example.aboutme.comm; import com.example.aboutme.comm.enums.CommCategory; import com.example.aboutme.reply.Reply; import com.example.aboutme.user.enums.UserRole; import lombok.Data; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import static java.util.stream.Collectors.toList; public class CommResponse { @Data public static class ClientMainCommListDTO { private Integer communityId; private String title; private String content; private String category; private String writerImage; private String writerName; private String expertImage; private String expertName; public ClientMainCommListDTO(Integer communityId, String title, String content, CommCategory category, String writerImage, String writerName, String expertImage, String expertName) { this.communityId = communityId; this.title = title; this.content = content; this.category = category.getKorean(); this.writerImage = writerImage; this.writerName = writerName; this.expertImage = expertImage; this.expertName = expertName; } } @Data public static class CommAndReplyDTO { private Integer id; private String title; private String content; private String category; private String userProfileImage; private String writerName; private boolean userRole; private String replyProfileImage; private String expertName; private String solution; // 생성자 잘 확인해야함. EXPERT면 true 나와서 출력할 수 있도록. public CommAndReplyDTO(Integer id, String title, String content, CommCategory category, String userProfileImage, String writerName, UserRole userRole, String replyProfileImage, String expertName, String solution) { this.id = id; this.title = title; this.content = content; this.category = category.getKorean(); this.userProfileImage = userProfileImage; this.writerName = writerName; this.userRole = userRole == UserRole.EXPERT ? true : false; this.replyProfileImage = replyProfileImage; this.expertName = expertName; this.solution = solution; } } // @Data // public static class CommDetailDTO { // // private List<String> replyContents; // private List<String> replyContents; // private Map<String, List<CommDTO>> commsByCategory; // // public CommDetailDTO(Integer id, String clientImage, String name, String content, String title, String category, Timestamp createdAt, List<String> replyContents, Map<String, List<CommDTO>> commsByCategory) { // this.id = id; // this.clientImage = clientImage; // this.name = name; // this.content = content; // this.title = title; // this.category = category; // this.createdAt = createdAt; // this.replyContents = replyContents; // this.commsByCategory = commsByCategory; // } // } // // @Data // public static class CommDTO { // private Integer id; // private String content; // private String title; // private String category; // private Timestamp createdAt; // // public CommDTO(Integer id, String content, String title, String category, Timestamp createdAt) { // this.id = id; // this.content = content; // this.title = title; // this.category = category; // this.createdAt = createdAt; // } // } @Data public static class CommDetailDTO { private CommDTO commDTO; private List<ReplyDTO> replies = new ArrayList<>(); private List<RelatedCommWithRepliesDTO> relatedComms = new ArrayList<>(); public CommDetailDTO(Comm comm, List<Reply> replies, List<Comm> relatedComms) { this.commDTO = new CommDTO(comm); this.replies = replies.stream() .map(ReplyDTO::new) .toList(); this.relatedComms = relatedComms.stream() .map(RelatedCommWithRepliesDTO::new) .toList(); } @Data public static class CommDTO { private Integer id; private String title; private String content; private String category; private String writerName; private String writerProfileImage; private Timestamp createdAt; // private int likeCount; private int replyCount; public CommDTO(Comm comm) { this.id = comm.getId(); this.title = comm.getTitle(); this.content = comm.getContent(); this.category = comm.getCategory().getKorean(); this.writerName = comm.getUser().getName(); this.writerProfileImage = comm.getUser().getProfileImage(); this.createdAt = comm.getCreatedAt(); // this.likeCount = comm.getLikes().size(); this.replyCount = comm.getReplies().size(); } } @Data public static class ReplyDTO { private Integer id; private String content; private String solution; private Timestamp createdAt; public ReplyDTO(Reply reply) { this.id = reply.getId(); this.content = reply.getContent(); this.content = reply.getSolution(); this.createdAt = reply.getCreatedAt(); } } @Data public static class RelatedCommWithRepliesDTO { private Integer id; private String content; private String title; private String category; private Timestamp createdAt; private int replies; private List<ExpertReplyDTO> expertReplies = new ArrayList<>(); public RelatedCommWithRepliesDTO(Comm comm) { this.id = comm.getId(); this.content = comm.getContent(); this.title = comm.getTitle(); this.category = comm.getCategory().getKorean(); this.createdAt = comm.getCreatedAt(); this.replies = (int) comm.getReplies().stream() .filter(reply -> !reply.getUser().getUserRole().equals(UserRole.CLIENT)) .count(); this.expertReplies = comm.getReplies().stream() .filter(reply -> reply.getUser().getUserRole().equals(UserRole.EXPERT)) .map(ExpertReplyDTO::new) .collect(toList()); } @Data public static class ExpertReplyDTO { private Integer id; private String solution; public ExpertReplyDTO(Reply reply) { this.id = reply.getId(); this.solution = reply.getSolution(); } } } } }
 
data.sql
-- reply_tb INSERT INTO reply_tb (user_id, comm_id, summary, cause_analysis, solution, created_at) VALUES -- 글 1의 댓글 (1, 1, '집에 가고 싶은 이유는...', '스트레스가 원인입니다', '휴식을 취하세요', NOW()), (21, 1, '상담사 의견', '생활 속 스트레스가 주요 원인입니다.', '정기적인 휴식과 상담을 추천합니다.', NOW()), -- 글 2의 댓글 (2, 2, '회사에서 스트레스 받는 이유는...', '업무 부담이 원인입니다', '업무 분담을 요청해보세요', NOW()), -- 글 3의 댓글 (3, 3, '저녁 메뉴 고민', '다양한 옵션이 많아 결정이 어렵습니다', '가볍게 샐러드나 요거트를 추천합니다', NOW()), (22, 3, '상담사 의견', '건강한 다이어트를 위해 균형 잡힌 식사를 추천합니다.', '영양소가 균형 잡힌 식단을 고려해보세요.', NOW()), -- 글 4의 댓글 (4, 4, '친구와의 갈등 원인', '의사소통 부족이 원인입니다', '서로 솔직하게 대화해보세요', NOW()), (23, 4, '상담사 의견', '갈등 해결을 위해 열린 마음으로 대화하세요.', '서로의 입장을 이해해보세요.', NOW()), -- 글 5의 댓글 (5, 5, '수업시간 졸음 원인', '수면 부족이 원인입니다', '충분한 수면을 취하세요', NOW()), -- 글 6의 댓글 (6, 6, '시험 공부 힘든 이유', '과도한 학습량이 원인입니다', '적절한 학습 계획을 세우세요', NOW()), (24, 6, '상담사 의견', '효과적인 학습 방법을 찾는 것이 중요합니다.', '짧은 휴식과 함께 계획적인 학습을 추천합니다.', NOW()), -- 글 7의 댓글 (7, 7, '프로젝트 어려운 이유', '프로젝트 범위가 큽니다', '작게 나눠서 진행하세요', NOW()), (25, 7, '상담사 의견', '프로젝트 관리가 중요합니다.', '체계적인 계획을 세워보세요.', NOW()), -- 글 8의 댓글 (8, 8, '취업 준비가 막막해요', '준비할 것이 많아서', '계획적으로 준비하세요', NOW()), (26, 8, '상담사 의견', '취업 준비는 체계적으로.', '리스트를 만들어보세요.', NOW()), -- 글 9의 댓글 (9, 9, '연애가 어려운 이유는...', '서로의 이해가 부족합니다', '서로에게 솔직해지세요', NOW()), (27, 9, '상담사 의견', '서로의 감정을 잘 이해하세요.', '솔직한 대화를 나누세요.', NOW()), -- 글 10의 댓글 (10, 10, '취미를 찾고 싶어요', '다양한 시도를 해보세요', '관심사를 탐색하세요', NOW()), (28, 10, '상담사 의견', '다양한 취미를 시도해보세요.', '여러 가지를 경험해보세요.', NOW()), -- 글 11의 댓글 (11, 11, '스트레스 해소 방법', '운동과 휴식이 중요합니다', '규칙적인 운동을 해보세요', NOW()), (29, 11, '상담사 의견', '운동과 휴식의 균형을 맞추세요.', '일정한 운동 시간을 가져보세요.', NOW()), -- 글 12의 댓글 (12, 12, '혼자 밥 먹기 싫은 이유', '외로움이 원인입니다', '가벼운 외출을 해보세요', NOW()), (30, 12, '상담사 의견', '혼자 있는 시간도 소중하게 생각하세요.', '작은 취미를 가져보세요.', NOW()), -- 일반 유저들의 추가 댓글 -- 글 2의 추가 댓글 (3, 2, '회사에서 받은 스트레스는...', '동료와의 관계도 원인일 수 있습니다', '개인 시간을 가져보세요', NOW()), (4, 2, '스트레스를 줄이는 방법은...', '업무 외 활동이 중요합니다', '취미 활동을 시작해보세요', NOW()), -- 글 5의 추가 댓글 (6, 5, '수업시간에 집중하려면...', '충분한 수면과 휴식이 필요합니다', '낮잠을 잠깐 자보세요', NOW()), (7, 5, '졸음을 이기기 위해...', '커피나 차를 마시는 것도 도움이 됩니다', '잠시 산책을 해보세요', NOW()), -- 글 6의 추가 댓글 (8, 6, '시험 공부가 힘든 이유는...', '과도한 스트레스와 불안감이 원인입니다', '리뷰나 정리를 해보세요', NOW()), (9, 6, '공부에 집중하기 위해...', '짧은 휴식을 자주 가지세요', '체계적인 공부 계획을 세우세요', NOW()), -- 글 9의 추가 댓글 (10, 9, '연애가 어려운 이유는...', '서로의 감정을 잘 이해하는 것이 중요합니다', '대화를 많이 나누세요', NOW()), (11, 9, '연애에서 중요한 것은...', '서로의 차이를 인정하는 것입니다', '타협점을 찾아보세요', NOW()), -- 글 12의 추가 댓글 (12, 12, '혼자 밥 먹기 싫다면...', '외로움을 달래기 위한 활동이 필요합니다', '다양한 활동을 시도해보세요', NOW()), (13, 12, '혼밥의 장점은...', '자유롭게 시간을 쓸 수 있습니다', '혼밥의 즐거움을 찾아보세요', NOW());
근데 여기서 solution이 2개여서 잘 못되었음. 1개로 되어야 함.
 
더미 수정 후
 
erd 볼 때도 비지니스 흐름으로 봐야 한다. 같은 엔티티라도 다른 조에는 다르게 표현되어 있을 수 있다. 그러니 여기서 중요한 것은 1:다 관계가 아니라 비지니스 흐름이 중요하다.
notion image
 

실습1

CommService로 가서 커뮤니티 상세보기를 구현한다.
//커뮤니티 상세보기 @Transactional public CommResponse.CommDetailDTO getCommDetail(int id) { //주어진 commID로 게시글(Comm_tb)을 가져옵니다. Comm comm = commRepository.findById(id) .orElseThrow(() -> new Exception404("게시물을 찾을 수 없습니다."));
Spring Data JPA 기본 메서드
Spring Data JPA에서 제공하는 몇 가지 기본 메서드는 다음과 같습니다:
  • findById(ID id): 주어진 ID로 엔티티를 조회합니다. 반환 타입은 Optional<T>입니다.
  • save(S entity): 엔티티를 저장하거나 업데이트합니다.
  • deleteById(ID id): 주어진 ID로 엔티티를 삭제합니다.
  • findAll(): 모든 엔티티를 조회합니다.
 
 
test로 가서 시험해본다.
notion image
 
여기서 왜 Comm의
@OneToMany(mappedBy = "comm", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Reply> replies;
이 부분은 안 나올까?
@OneToMany 관계에서 fetch = FetchType.LAZY를 사용하면 기본적으로 해당 컬렉션은 명시적으로 접근할 때까지 로드되지 않습니다. 따라서 테스트 코드에서 findById를 호출한 후 replies를 접근하려고 하면, replies 컬렉션이 비어있거나 null로 보일 수 있습니다.
 
Share article

Coding's note