From 9f0102625b50f85a8e92dcf1189eae8a6b27f5ca Mon Sep 17 00:00:00 2001 From: s0___0k <61587396+s0ooo0k@users.noreply.github.com> Date: Fri, 2 May 2025 11:15:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat=20:=20=EB=AA=A8=EC=A7=91=EA=B8=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostRequestDTO → Post 변환 로직 toEntity(User)로 위임 - 모집글 등록 시 Skill 존재 여부 확인 후 없으면 생성 - PostResponseDTO, PostDetailDTO 응답 구조 통일 - 모집글 등록/조회 시 Post → Skill 리스트 연결 처리 - 현재 참여자 수(currentCount)는 TODO로 추후 구현 예정 --- .../{ => controller}/ApplyController.java | 0 .../aibe/hosik/apply/{ => entity}/Apply.java | 0 .../{ => repository}/ApplyRepository.java | 0 .../apply/{ => service}/ApplyService.java | 0 .../java/aibe/hosik/post/PostService.java | 12 --- .../post/{ => controller}/PostController.java | 0 .../{ => respository}/PostRepository.java | 0 .../aibe/hosik/post/service/PostService.java | 19 ++++ .../hosik/post/service/PostServiceImpl.java | 86 +++++++++++++++++++ .../{ => cotnroller}/SkillController.java | 0 .../{ => repository}/SkillRepository.java | 3 + .../skill/{ => service}/SkillService.java | 0 12 files changed, 108 insertions(+), 12 deletions(-) rename src/main/java/aibe/hosik/apply/{ => controller}/ApplyController.java (100%) rename src/main/java/aibe/hosik/apply/{ => entity}/Apply.java (100%) rename src/main/java/aibe/hosik/apply/{ => repository}/ApplyRepository.java (100%) rename src/main/java/aibe/hosik/apply/{ => service}/ApplyService.java (100%) delete mode 100644 src/main/java/aibe/hosik/post/PostService.java rename src/main/java/aibe/hosik/post/{ => controller}/PostController.java (100%) rename src/main/java/aibe/hosik/post/{ => respository}/PostRepository.java (100%) create mode 100644 src/main/java/aibe/hosik/post/service/PostService.java create mode 100644 src/main/java/aibe/hosik/post/service/PostServiceImpl.java rename src/main/java/aibe/hosik/skill/{ => cotnroller}/SkillController.java (100%) rename src/main/java/aibe/hosik/skill/{ => repository}/SkillRepository.java (87%) rename src/main/java/aibe/hosik/skill/{ => service}/SkillService.java (100%) diff --git a/src/main/java/aibe/hosik/apply/ApplyController.java b/src/main/java/aibe/hosik/apply/controller/ApplyController.java similarity index 100% rename from src/main/java/aibe/hosik/apply/ApplyController.java rename to src/main/java/aibe/hosik/apply/controller/ApplyController.java diff --git a/src/main/java/aibe/hosik/apply/Apply.java b/src/main/java/aibe/hosik/apply/entity/Apply.java similarity index 100% rename from src/main/java/aibe/hosik/apply/Apply.java rename to src/main/java/aibe/hosik/apply/entity/Apply.java diff --git a/src/main/java/aibe/hosik/apply/ApplyRepository.java b/src/main/java/aibe/hosik/apply/repository/ApplyRepository.java similarity index 100% rename from src/main/java/aibe/hosik/apply/ApplyRepository.java rename to src/main/java/aibe/hosik/apply/repository/ApplyRepository.java diff --git a/src/main/java/aibe/hosik/apply/ApplyService.java b/src/main/java/aibe/hosik/apply/service/ApplyService.java similarity index 100% rename from src/main/java/aibe/hosik/apply/ApplyService.java rename to src/main/java/aibe/hosik/apply/service/ApplyService.java diff --git a/src/main/java/aibe/hosik/post/PostService.java b/src/main/java/aibe/hosik/post/PostService.java deleted file mode 100644 index bef8fae..0000000 --- a/src/main/java/aibe/hosik/post/PostService.java +++ /dev/null @@ -1,12 +0,0 @@ -package aibe.hosik.post; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -@RequiredArgsConstructor -public class PostService { - private final PostRepository postRepository; -} diff --git a/src/main/java/aibe/hosik/post/PostController.java b/src/main/java/aibe/hosik/post/controller/PostController.java similarity index 100% rename from src/main/java/aibe/hosik/post/PostController.java rename to src/main/java/aibe/hosik/post/controller/PostController.java diff --git a/src/main/java/aibe/hosik/post/PostRepository.java b/src/main/java/aibe/hosik/post/respository/PostRepository.java similarity index 100% rename from src/main/java/aibe/hosik/post/PostRepository.java rename to src/main/java/aibe/hosik/post/respository/PostRepository.java diff --git a/src/main/java/aibe/hosik/post/service/PostService.java b/src/main/java/aibe/hosik/post/service/PostService.java new file mode 100644 index 0000000..6d1e5be --- /dev/null +++ b/src/main/java/aibe/hosik/post/service/PostService.java @@ -0,0 +1,19 @@ +package aibe.hosik.post.service; + +import aibe.hosik.post.dto.PostDetailDTO; +import aibe.hosik.post.dto.PostRequestDTO; +import aibe.hosik.post.dto.PostResponseDTO; +import aibe.hosik.post.entity.Post; +import aibe.hosik.post.respository.PostRepository; +import aibe.hosik.user.User; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +public interface PostService { + List getAllPosts(); + Post createPost(PostRequestDTO dto, User user); + PostDetailDTO getPostDetail(Long postId); +} diff --git a/src/main/java/aibe/hosik/post/service/PostServiceImpl.java b/src/main/java/aibe/hosik/post/service/PostServiceImpl.java new file mode 100644 index 0000000..9ffac68 --- /dev/null +++ b/src/main/java/aibe/hosik/post/service/PostServiceImpl.java @@ -0,0 +1,86 @@ +package aibe.hosik.post.service; + +import aibe.hosik.post.dto.MatchedUserDTO; +import aibe.hosik.post.dto.PostDetailDTO; +import aibe.hosik.post.dto.PostRequestDTO; +import aibe.hosik.post.dto.PostResponseDTO; +import aibe.hosik.post.entity.Post; +import aibe.hosik.post.respository.PostRepository; +import aibe.hosik.skill.repository.PostSkillRepository; +import aibe.hosik.skill.repository.SkillRepository; +import aibe.hosik.skill.entity.PostSkill; +import aibe.hosik.skill.entity.Skill; +import aibe.hosik.user.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PostServiceImpl implements PostService { + private final PostRepository postRepository; + private final SkillRepository skillRepository; + private final PostSkillRepository postSkillRepository; + + // PostResponseDTO를 통해 전체 게시글(Post)를 조회합니다 + @Override + public List getAllPosts() { + List posts = postRepository.findAll(); + + return posts.stream() + .map(post -> { + // post와 연관된 스킬 이름 조회 + List skills = postSkillRepository.findSkillByPostId(post.getId()); + + // TODO : 현재 참여자 수 계산 + Integer currentCount = 0; + + // DTO 정적 팩토리 메서드 활용 + return PostResponseDTO.from(post, skills, currentCount); + }).collect(Collectors.toList()); + } + + // 주어진 PostRequestDTO와 User를 기반으로 새로운 게시글(Post)을 생성하고 저장합니다. + // 요청된 스킬 리스트를 기준으로 스킬을 찾거나 새로 생성하여 Post와 연결합니다. + @Override + public Post createPost(PostRequestDTO dto, User user) { + // toEntity 사용해서 Post 객체 생성 + Post post = dto.toEntity(user); + // 생성한 객체 Post 저장 + Post savePost = postRepository.save(post); + + // Skill 찾거나 생성 + for(String skillName : dto.skills()) { + Skill skill = skillRepository.findByName(skillName) + .orElseGet(() -> skillRepository.save(Skill.builder().name(skillName).build())); + + PostSkill postSkill = PostSkill.builder() + .post(savePost) + .skill(skill) + .build(); + + // post-skill 연관관계 추가 + postSkillRepository.save(postSkill); + } + return savePost; + } + + @Override + public PostDetailDTO getPostDetail(Long postId) { + // 게시글 정보 조회 + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("게시글이 존재하지 않습니다")); + + // 스킬 이름 조회 + List skills = postSkillRepository.findSkillByPostId(postId); + + // 매칭 사용자 정보 조회 + // TODO : 실제 매칭된 사용자 조회 + List matchedUsers = new ArrayList<>(); + + return PostDetailDTO.from(post,skills, matchedUsers); + } +} diff --git a/src/main/java/aibe/hosik/skill/SkillController.java b/src/main/java/aibe/hosik/skill/cotnroller/SkillController.java similarity index 100% rename from src/main/java/aibe/hosik/skill/SkillController.java rename to src/main/java/aibe/hosik/skill/cotnroller/SkillController.java diff --git a/src/main/java/aibe/hosik/skill/SkillRepository.java b/src/main/java/aibe/hosik/skill/repository/SkillRepository.java similarity index 87% rename from src/main/java/aibe/hosik/skill/SkillRepository.java rename to src/main/java/aibe/hosik/skill/repository/SkillRepository.java index 4565adf..779db3e 100644 --- a/src/main/java/aibe/hosik/skill/SkillRepository.java +++ b/src/main/java/aibe/hosik/skill/repository/SkillRepository.java @@ -3,5 +3,8 @@ import aibe.hosik.skill.entity.Skill; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface SkillRepository extends JpaRepository { + } diff --git a/src/main/java/aibe/hosik/skill/SkillService.java b/src/main/java/aibe/hosik/skill/service/SkillService.java similarity index 100% rename from src/main/java/aibe/hosik/skill/SkillService.java rename to src/main/java/aibe/hosik/skill/service/SkillService.java From 10dc2205688b56b32d77365bb704572a2d232220 Mon Sep 17 00:00:00 2001 From: s0___0k <61587396+s0ooo0k@users.noreply.github.com> Date: Fri, 2 May 2025 11:19:38 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat=20:=20=EB=AA=A8=EC=A7=91=EA=B8=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=B6=94=EA=B0=80=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모집글 생성 및 조회 기능 관련 구현 파일 추가 업로드 --- build.gradle | 2 +- .../java/aibe/hosik/analysis/Analysis.java | 2 +- .../apply/controller/ApplyController.java | 3 +- .../java/aibe/hosik/apply/entity/Apply.java | 2 +- .../apply/repository/ApplyRepository.java | 5 +- .../hosik/apply/service/ApplyService.java | 3 +- .../hosik/post/controller/PostController.java | 59 ++++++++++++++++++- .../aibe/hosik/post/dto/MatchedUserDTO.java | 15 +++++ .../aibe/hosik/post/dto/PostDetailDTO.java | 41 +++++++++++++ .../aibe/hosik/post/dto/PostRequestDTO.java | 37 ++++++++++++ .../aibe/hosik/post/dto/PostResponseDTO.java | 30 ++++++++++ .../java/aibe/hosik/post/entity/Post.java | 10 ++++ .../post/respository/PostRepository.java | 2 +- .../skill/cotnroller/SkillController.java | 3 +- .../skill/repository/PostSkillRepository.java | 19 ++++++ .../skill/repository/SkillRepository.java | 4 +- .../hosik/skill/service/SkillService.java | 3 +- 17 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 src/main/java/aibe/hosik/post/dto/MatchedUserDTO.java create mode 100644 src/main/java/aibe/hosik/post/dto/PostDetailDTO.java create mode 100644 src/main/java/aibe/hosik/post/dto/PostRequestDTO.java create mode 100644 src/main/java/aibe/hosik/post/dto/PostResponseDTO.java create mode 100644 src/main/java/aibe/hosik/skill/repository/PostSkillRepository.java diff --git a/build.gradle b/build.gradle index 3ada6a0..3c2100b 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' -// implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' diff --git a/src/main/java/aibe/hosik/analysis/Analysis.java b/src/main/java/aibe/hosik/analysis/Analysis.java index 29361b3..03f58d8 100644 --- a/src/main/java/aibe/hosik/analysis/Analysis.java +++ b/src/main/java/aibe/hosik/analysis/Analysis.java @@ -1,6 +1,6 @@ package aibe.hosik.analysis; -import aibe.hosik.apply.Apply; +import aibe.hosik.apply.entity.Apply; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/aibe/hosik/apply/controller/ApplyController.java b/src/main/java/aibe/hosik/apply/controller/ApplyController.java index d54c411..f96b223 100644 --- a/src/main/java/aibe/hosik/apply/controller/ApplyController.java +++ b/src/main/java/aibe/hosik/apply/controller/ApplyController.java @@ -1,5 +1,6 @@ -package aibe.hosik.apply; +package aibe.hosik.apply.controller; +import aibe.hosik.apply.service.ApplyService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/aibe/hosik/apply/entity/Apply.java b/src/main/java/aibe/hosik/apply/entity/Apply.java index df6f9c9..4ffc882 100644 --- a/src/main/java/aibe/hosik/apply/entity/Apply.java +++ b/src/main/java/aibe/hosik/apply/entity/Apply.java @@ -1,4 +1,4 @@ -package aibe.hosik.apply; +package aibe.hosik.apply.entity; import aibe.hosik.common.TimeEntity; import aibe.hosik.post.entity.Post; diff --git a/src/main/java/aibe/hosik/apply/repository/ApplyRepository.java b/src/main/java/aibe/hosik/apply/repository/ApplyRepository.java index 78a9932..ee8552c 100644 --- a/src/main/java/aibe/hosik/apply/repository/ApplyRepository.java +++ b/src/main/java/aibe/hosik/apply/repository/ApplyRepository.java @@ -1,6 +1,9 @@ -package aibe.hosik.apply; +package aibe.hosik.apply.repository; +import aibe.hosik.apply.entity.Apply; import org.springframework.data.jpa.repository.JpaRepository; public interface ApplyRepository extends JpaRepository { + // post id로 지원서 조회 + } diff --git a/src/main/java/aibe/hosik/apply/service/ApplyService.java b/src/main/java/aibe/hosik/apply/service/ApplyService.java index c7ccb5a..9dbdf32 100644 --- a/src/main/java/aibe/hosik/apply/service/ApplyService.java +++ b/src/main/java/aibe/hosik/apply/service/ApplyService.java @@ -1,5 +1,6 @@ -package aibe.hosik.apply; +package aibe.hosik.apply.service; +import aibe.hosik.apply.repository.ApplyRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/src/main/java/aibe/hosik/post/controller/PostController.java b/src/main/java/aibe/hosik/post/controller/PostController.java index 18e9fa9..22a8df9 100644 --- a/src/main/java/aibe/hosik/post/controller/PostController.java +++ b/src/main/java/aibe/hosik/post/controller/PostController.java @@ -1,14 +1,67 @@ -package aibe.hosik.post; +package aibe.hosik.post.controller; +import aibe.hosik.post.dto.PostDetailDTO; +import aibe.hosik.post.dto.PostRequestDTO; +import aibe.hosik.post.dto.PostResponseDTO; +import aibe.hosik.post.entity.Post; +import aibe.hosik.post.service.PostService; +import aibe.hosik.skill.repository.PostSkillRepository; +import aibe.hosik.user.User; +import aibe.hosik.user.UserRepository; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.apache.coyote.Response; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @Slf4j @RestController @RequestMapping("/api/posts") @RequiredArgsConstructor +@Tag(name = "Post", description = "모집글 API") // Swagger Tag public class PostController { private final PostService postService; + private final UserRepository userRepository; + private final PostSkillRepository postSkillRepository; + + @Operation(summary="모집글 등록", description="모집글을 등록합니다.") + @PostMapping + public ResponseEntity createPost(@RequestBody PostRequestDTO dto, @AuthenticationPrincipal User user){ + Post createPost = postService.createPost(dto, user); + // 스킬 조회 + List skills = postSkillRepository.findSkillByPostId(createPost.getId()); + // dto 반환 + // TODO : currentCount 로직 구현 후 변환 + PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0); + return ResponseEntity.ok(responseDTO); + } + // 테스트용 + @Operation(summary="모집글 등록 테스트", description="[TEST] 모집글을 등록합니다.") + @PostMapping("/mock") + public ResponseEntity createPostForSwagger(@RequestBody PostRequestDTO dto){ + + // 테스트용 userId + User mockUser = userRepository.findById(1L).orElseThrow(); + Post createPost = postService.createPost(dto, mockUser); + List skills = postSkillRepository.findSkillByPostId(createPost.getId()); + PostResponseDTO responseDTO = PostResponseDTO.from(createPost, skills, 0); + return ResponseEntity.ok(responseDTO); + } + + @Operation(summary="모집글 조회", description = "모집글 목록을 조회합니다.") + @GetMapping + public ResponseEntity> getAllPosts(){ + return ResponseEntity.ok(postService.getAllPosts()); + } + + @Operation(summary="모집글 상세 조회", description="모집글 게시글을 상세 조회합니다") + @GetMapping("/{postId}") + public ResponseEntity getPostDetail(@PathVariable Long postId){ + return ResponseEntity.ok(postService.getPostDetail(postId)); + } } diff --git a/src/main/java/aibe/hosik/post/dto/MatchedUserDTO.java b/src/main/java/aibe/hosik/post/dto/MatchedUserDTO.java new file mode 100644 index 0000000..22582b2 --- /dev/null +++ b/src/main/java/aibe/hosik/post/dto/MatchedUserDTO.java @@ -0,0 +1,15 @@ +package aibe.hosik.post.dto; + +import aibe.hosik.post.entity.Post; + +import java.util.List; + +public record MatchedUserDTO( + Long userId, + String username, + String nickname, + String image, + String introduction +) { + +} diff --git a/src/main/java/aibe/hosik/post/dto/PostDetailDTO.java b/src/main/java/aibe/hosik/post/dto/PostDetailDTO.java new file mode 100644 index 0000000..510b4c1 --- /dev/null +++ b/src/main/java/aibe/hosik/post/dto/PostDetailDTO.java @@ -0,0 +1,41 @@ +package aibe.hosik.post.dto; + +import aibe.hosik.post.entity.Post; +import aibe.hosik.post.entity.PostCategory; +import aibe.hosik.post.entity.PostType; + +import java.time.LocalDate; +import java.util.List; + +public record PostDetailDTO( + Long id, + String title, + String content, + Integer headCount, + String image, + String requirementPersonality, + LocalDate endedAt, + + String category, + String type, + + List skills, + + // 현재 선택된 목록 보여주기 + List matchedUsers +) { + public static PostDetailDTO from(Post post, List skills, List matchedUsers) { + return new PostDetailDTO( + post.getId(), + post.getTitle(), + post.getContent(), + post.getHeadCount(), + post.getImage(), + post.getRequirementPersonality(), + post.getEndedAt(), + post.getCategory().toString(), + post.getType().toString(), + skills, + matchedUsers + ); +}} diff --git a/src/main/java/aibe/hosik/post/dto/PostRequestDTO.java b/src/main/java/aibe/hosik/post/dto/PostRequestDTO.java new file mode 100644 index 0000000..5115925 --- /dev/null +++ b/src/main/java/aibe/hosik/post/dto/PostRequestDTO.java @@ -0,0 +1,37 @@ +package aibe.hosik.post.dto; + +import aibe.hosik.post.entity.Post; +import aibe.hosik.post.entity.PostCategory; +import aibe.hosik.post.entity.PostType; +import aibe.hosik.user.User; + +import java.time.LocalDate; +import java.util.List; + +public record PostRequestDTO( + String title, + String content, + Integer headCount, + String image, + String requirementPersonality, + LocalDate endedAt, + + PostCategory category, + PostType type, + + List skills +) { + public Post toEntity(User user) { + return Post.builder() + .title(title()) + .content(content()) + .headCount(headCount()) + .image(image()) + .requirementPersonality(requirementPersonality()) + .endedAt(endedAt()) + .category(category()) + .type(type()) + .user(user) + .build(); + } +} diff --git a/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java b/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java new file mode 100644 index 0000000..d34a179 --- /dev/null +++ b/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java @@ -0,0 +1,30 @@ +package aibe.hosik.post.dto; + +import aibe.hosik.post.entity.Post; +import io.jsonwebtoken.security.PublicJwk; + +import java.util.List; + +public record PostResponseDTO( + Long id, + String image, + String title, + String content, + String category, + List skills, + Integer headCount, + Integer currentCount +) { + + public static PostResponseDTO from(Post post,List skills, Integer currentCount) { + return new PostResponseDTO(post.getId(), + post.getImage(), + post.getTitle(), + post.getContent(), + post.getCategory().toString(), + skills, + post.getHeadCount(), + currentCount + ); + } +} diff --git a/src/main/java/aibe/hosik/post/entity/Post.java b/src/main/java/aibe/hosik/post/entity/Post.java index 75fac1c..0a66ab1 100644 --- a/src/main/java/aibe/hosik/post/entity/Post.java +++ b/src/main/java/aibe/hosik/post/entity/Post.java @@ -1,11 +1,14 @@ package aibe.hosik.post.entity; import aibe.hosik.common.TimeEntity; +import aibe.hosik.skill.entity.PostSkill; import aibe.hosik.user.User; import jakarta.persistence.*; import lombok.*; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; @Entity @NoArgsConstructor @@ -34,6 +37,9 @@ public class Post extends TimeEntity { @Column private String image; + @Column + private String requirementPersonality; + @Column(nullable = false) private LocalDate endedAt; @@ -47,4 +53,8 @@ public class Post extends TimeEntity { @ManyToOne(fetch = FetchType.LAZY) private User user; + + // 양방향 매핑 + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List postSkills = new ArrayList<>(); } diff --git a/src/main/java/aibe/hosik/post/respository/PostRepository.java b/src/main/java/aibe/hosik/post/respository/PostRepository.java index b69a679..6515484 100644 --- a/src/main/java/aibe/hosik/post/respository/PostRepository.java +++ b/src/main/java/aibe/hosik/post/respository/PostRepository.java @@ -1,4 +1,4 @@ -package aibe.hosik.post; +package aibe.hosik.post.respository; import aibe.hosik.post.entity.Post; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/aibe/hosik/skill/cotnroller/SkillController.java b/src/main/java/aibe/hosik/skill/cotnroller/SkillController.java index 41d08f9..1dbdf3a 100644 --- a/src/main/java/aibe/hosik/skill/cotnroller/SkillController.java +++ b/src/main/java/aibe/hosik/skill/cotnroller/SkillController.java @@ -1,5 +1,6 @@ -package aibe.hosik.skill; +package aibe.hosik.skill.cotnroller; +import aibe.hosik.skill.service.SkillService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/src/main/java/aibe/hosik/skill/repository/PostSkillRepository.java b/src/main/java/aibe/hosik/skill/repository/PostSkillRepository.java new file mode 100644 index 0000000..0c41a8f --- /dev/null +++ b/src/main/java/aibe/hosik/skill/repository/PostSkillRepository.java @@ -0,0 +1,19 @@ +package aibe.hosik.skill.repository; + + +import aibe.hosik.post.entity.Post; +import aibe.hosik.skill.entity.PostSkill; +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 PostSkillRepository extends JpaRepository { + // Post 엔티티로 Post 게시글과 연관된 모든 PostSkill 목록 조회 + List findByPost(Post post); + + // Post ID로 해당 글과 연관된 모든 스킬 이르 직접 조회 + @Query("SELECT s.skill.name FROM PostSkill s WHERE s.post.id = :postId") + List findSkillByPostId(@Param("postId") Long postId); +} diff --git a/src/main/java/aibe/hosik/skill/repository/SkillRepository.java b/src/main/java/aibe/hosik/skill/repository/SkillRepository.java index 779db3e..2570855 100644 --- a/src/main/java/aibe/hosik/skill/repository/SkillRepository.java +++ b/src/main/java/aibe/hosik/skill/repository/SkillRepository.java @@ -1,4 +1,4 @@ -package aibe.hosik.skill; +package aibe.hosik.skill.repository; import aibe.hosik.skill.entity.Skill; import org.springframework.data.jpa.repository.JpaRepository; @@ -6,5 +6,5 @@ import java.util.Optional; public interface SkillRepository extends JpaRepository { - + Optional findByName(String skillName); } diff --git a/src/main/java/aibe/hosik/skill/service/SkillService.java b/src/main/java/aibe/hosik/skill/service/SkillService.java index 6f3a38d..5e681cf 100644 --- a/src/main/java/aibe/hosik/skill/service/SkillService.java +++ b/src/main/java/aibe/hosik/skill/service/SkillService.java @@ -1,5 +1,6 @@ -package aibe.hosik.skill; +package aibe.hosik.skill.service; +import aibe.hosik.skill.repository.SkillRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; From 15d87fa0852ee17ae25b9a12936bdda72fad1e13 Mon Sep 17 00:00:00 2001 From: s0___0k <61587396+s0ooo0k@users.noreply.github.com> Date: Fri, 2 May 2025 12:05:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat=20:=20Post=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20N+1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PostRepository에 쿼리 기반 fetch join 메서드 추가 - Post 게시글 조회 시 발생하는 N+1 문제 해결 - Repository 패키지명 오타 수정 --- .../aibe/hosik/post/dto/PostResponseDTO.java | 1 - .../java/aibe/hosik/post/entity/Post.java | 1 + .../hosik/post/repository/PostRepository.java | 22 +++++++++++++++++++ .../post/respository/PostRepository.java | 7 ------ .../aibe/hosik/post/service/PostService.java | 4 ---- .../hosik/post/service/PostServiceImpl.java | 20 +++++++++++------ 6 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 src/main/java/aibe/hosik/post/repository/PostRepository.java delete mode 100644 src/main/java/aibe/hosik/post/respository/PostRepository.java diff --git a/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java b/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java index d34a179..df30312 100644 --- a/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java +++ b/src/main/java/aibe/hosik/post/dto/PostResponseDTO.java @@ -1,7 +1,6 @@ package aibe.hosik.post.dto; import aibe.hosik.post.entity.Post; -import io.jsonwebtoken.security.PublicJwk; import java.util.List; diff --git a/src/main/java/aibe/hosik/post/entity/Post.java b/src/main/java/aibe/hosik/post/entity/Post.java index 0a66ab1..cfbc4ea 100644 --- a/src/main/java/aibe/hosik/post/entity/Post.java +++ b/src/main/java/aibe/hosik/post/entity/Post.java @@ -56,5 +56,6 @@ public class Post extends TimeEntity { // 양방향 매핑 @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + @Builder.Default private List postSkills = new ArrayList<>(); } diff --git a/src/main/java/aibe/hosik/post/repository/PostRepository.java b/src/main/java/aibe/hosik/post/repository/PostRepository.java new file mode 100644 index 0000000..aa96837 --- /dev/null +++ b/src/main/java/aibe/hosik/post/repository/PostRepository.java @@ -0,0 +1,22 @@ +package aibe.hosik.post.repository; + +import aibe.hosik.post.entity.Post; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +public interface PostRepository extends JpaRepository { + // Post 조회 시 postSkills, skill 엔티티 즉시 로딩 지정 + //@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"}) + @Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill") + List findAllWithSkills(); + + // PostDetail 조회 시 즉시 로딩 지정 + //@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"}) + @Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill") + Optional findByIdWithSkills(Long id); +} diff --git a/src/main/java/aibe/hosik/post/respository/PostRepository.java b/src/main/java/aibe/hosik/post/respository/PostRepository.java deleted file mode 100644 index 6515484..0000000 --- a/src/main/java/aibe/hosik/post/respository/PostRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package aibe.hosik.post.respository; - -import aibe.hosik.post.entity.Post; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PostRepository extends JpaRepository { -} diff --git a/src/main/java/aibe/hosik/post/service/PostService.java b/src/main/java/aibe/hosik/post/service/PostService.java index 6d1e5be..a693e60 100644 --- a/src/main/java/aibe/hosik/post/service/PostService.java +++ b/src/main/java/aibe/hosik/post/service/PostService.java @@ -4,11 +4,7 @@ import aibe.hosik.post.dto.PostRequestDTO; import aibe.hosik.post.dto.PostResponseDTO; import aibe.hosik.post.entity.Post; -import aibe.hosik.post.respository.PostRepository; import aibe.hosik.user.User; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; import java.util.List; diff --git a/src/main/java/aibe/hosik/post/service/PostServiceImpl.java b/src/main/java/aibe/hosik/post/service/PostServiceImpl.java index 9ffac68..793164e 100644 --- a/src/main/java/aibe/hosik/post/service/PostServiceImpl.java +++ b/src/main/java/aibe/hosik/post/service/PostServiceImpl.java @@ -5,7 +5,7 @@ import aibe.hosik.post.dto.PostRequestDTO; import aibe.hosik.post.dto.PostResponseDTO; import aibe.hosik.post.entity.Post; -import aibe.hosik.post.respository.PostRepository; +import aibe.hosik.post.repository.PostRepository; import aibe.hosik.skill.repository.PostSkillRepository; import aibe.hosik.skill.repository.SkillRepository; import aibe.hosik.skill.entity.PostSkill; @@ -28,12 +28,15 @@ public class PostServiceImpl implements PostService { // PostResponseDTO를 통해 전체 게시글(Post)를 조회합니다 @Override public List getAllPosts() { - List posts = postRepository.findAll(); + // findAllWithSkills로 한 번에 fetch(post, postSkills, skill) + List posts = postRepository.findAllWithSkills(); return posts.stream() .map(post -> { - // post와 연관된 스킬 이름 조회 - List skills = postSkillRepository.findSkillByPostId(post.getId()); + // fetch된 postSkills에서 skill 추출 + List skills = post.getPostSkills().stream() + .map(s -> s.getSkill().getName()) + .collect(Collectors.toList()); // TODO : 현재 참여자 수 계산 Integer currentCount = 0; @@ -53,6 +56,7 @@ public Post createPost(PostRequestDTO dto, User user) { Post savePost = postRepository.save(post); // Skill 찾거나 생성 + // 추후 stream으로 전환 for(String skillName : dto.skills()) { Skill skill = skillRepository.findByName(skillName) .orElseGet(() -> skillRepository.save(Skill.builder().name(skillName).build())); @@ -71,11 +75,13 @@ public Post createPost(PostRequestDTO dto, User user) { @Override public PostDetailDTO getPostDetail(Long postId) { // 게시글 정보 조회 - Post post = postRepository.findById(postId) - .orElseThrow(() -> new IllegalArgumentException("게시글이 존재하지 않습니다")); + Post post = postRepository.findByIdWithSkills(postId) + .orElseThrow(); // 스킬 이름 조회 - List skills = postSkillRepository.findSkillByPostId(postId); + List skills = post.getPostSkills().stream() + .map(s -> s.getSkill().getName()) + .collect(Collectors.toList()); // 매칭 사용자 정보 조회 // TODO : 실제 매칭된 사용자 조회 From bea5700b84e10f46105c08a6d049c15684150109 Mon Sep 17 00:00:00 2001 From: s0___0k <61587396+s0ooo0k@users.noreply.github.com> Date: Fri, 2 May 2025 12:34:01 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix=20:=20Post=20=EC=83=81=EC=84=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B0=99=EC=9D=80=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EB=AC=BC=20=EB=B0=98=ED=99=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findByWithSkills() 쿼리에 WHERE p.id = :id 조건 누락 - 같은 게시물만 반환되던 문제 해결 --- src/main/java/aibe/hosik/post/repository/PostRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/aibe/hosik/post/repository/PostRepository.java b/src/main/java/aibe/hosik/post/repository/PostRepository.java index aa96837..a77f9a6 100644 --- a/src/main/java/aibe/hosik/post/repository/PostRepository.java +++ b/src/main/java/aibe/hosik/post/repository/PostRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -17,6 +18,6 @@ public interface PostRepository extends JpaRepository { // PostDetail 조회 시 즉시 로딩 지정 //@EntityGraph(attributePaths = {"postSkills", "postSkills.skill"}) - @Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill") - Optional findByIdWithSkills(Long id); + @Query("SELECT DISTINCT p FROM Post p LEFT JOIN FETCH p.postSkills ps LEFT JOIN FETCH ps.skill WHERE p.id = :id") + Optional findByIdWithSkills(@Param("id") Long id); } From 0c9091072e32ca54e92d66877a54f0602f6254f1 Mon Sep 17 00:00:00 2001 From: s0___0k <61587396+s0ooo0k@users.noreply.github.com> Date: Fri, 2 May 2025 12:47:27 +0900 Subject: [PATCH 5/5] test --- src/main/java/aibe/hosik/post/controller/PostController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/aibe/hosik/post/controller/PostController.java b/src/main/java/aibe/hosik/post/controller/PostController.java index 22a8df9..00898b4 100644 --- a/src/main/java/aibe/hosik/post/controller/PostController.java +++ b/src/main/java/aibe/hosik/post/controller/PostController.java @@ -19,6 +19,7 @@ import java.util.List; +// 테스트 @Slf4j @RestController @RequestMapping("/api/posts")