우주먼지

💡 게시물 기능

 

요구사항

PostMan API Docs 작성
https://documenter.getpostman.com/view/23682055/2s8ZDU6jkR

Open-Session-in-view 옵션: false (트랜잭션 내부의 프록시객체 초기화 필요)
연관관계 : Member 1   < - >    Post N

@ManyToOne 단방향 매핑으로 진행
FetchType : Lazy

1. 게시물 생성
2. 게시물 수정
3. 게시물 단건조회
4. 게시물 전체조회(Pagenation)
5. 게시물 삭제
6. 조회수 카운트(Update Query)

게시물 생성

  • 게시물 생성 시, HttpServletRequest를 파라미터로 받아
    유저의 Role (Admin, User, Counselor)을 검증 후 게시글에 유저 or 상담사 할당
  • 그럼 Post 엔티티 내부에 존재하는 Member, Counselor 중 하나는 Null이 들어가므로
    @ManyToOne의 optional 옵션을, 기존의 false에서 기본값인 true로 변경해주었음

 

게시물 수정

  • 쿼리파라미터로 Post의 식별자를 입력받아 Entity에서 생성한 update 메서드를 이용해 필드값 수정
  • Member 프록시 객체 초기화 로직 필요

 

게시물 단건조회

  • 쿼리파라미터로 Post의 식별자를 입력받아 조회
  • Member 프록시 객체 초기화 로직 필요
  • @Transactional(readOnly = true)

 

게시물 전체 조회

  • Pagenation을 이용하여 쿼리파라미터로 page, size를 입력받아 페이지 출력
  • Member 프록시 객체 초기화 로직 필요
  • @Transactional(readOnly = true)

 

게시물 삭제

  • 쿼리파라미터로 Post의 식별자를 입력받아 삭제

 

조회수 증가 로직

  • Repository 인터페이스에 JQPL을 이용한 Update Query 작성

💡 소스코드

 

Entity

@Getter @Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Post extends BaseEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long postId;

    @Column(nullable = false, length = 50)
    private String title;

    @Column(nullable = false)
    private String content;

    @Column(columnDefinition = "integer default 0" ,nullable = false)
    private int views;

    @Enumerated(value = EnumType.STRING)
    @Column(nullable = false)
    private Kind kinds;

    // ------------------ 연관관계 매핑 ------------------
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    @JsonIgnore
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "COUNSELOR_ID")
    @JsonIgnore
    private Counselor counselor;

    public void setMember(Member member) {
        this.member = member;
    }

    public void setCounselor(Counselor counselor) {
        this.counselor = counselor;
    }


    public void update(String title, String content, Kind kinds) {
        this.title = title;
        this.content = content;
        this.kinds = kinds;
    }
    
    // ------------------ Test 를 위한 팩토리 메서드 ------------------
    private Post(String title, String content, Kind kinds, Member member) {
        this.title = title;
        this.content = content;
        this.kinds = kinds;
        this.member = member;
    }

    public static Post of(String title, String content, Kind kinds, Member member) {
        return new Post(title,content,kinds,member);
    }

    // ------------------ Enum ------------------

    public enum Kind {
        GENERAL("일반"),
        REVIEW("후기");

        @Getter
        private String status;

        Kind(String status) {
            this.status = status;
        }
    }
}

 

DTO

public class PostDto {

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Post {
        private String title;
        private String content;
        private Role role;
        private com.server.seb41_main_11.domain.post.entity.Post.Kind kinds;
    }

    @Getter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Patch {
        private long postId;
        private String title;
        private String content;
        private com.server.seb41_main_11.domain.post.entity.Post.Kind kinds;

        public void setPostId(long postId) {
            this.postId = postId;
        }
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class SingleResponse {
        private long postId;
        private String title;
        private String content;
        private com.server.seb41_main_11.domain.post.entity.Post.Kind kinds;
        private int views;
        private String writer;
        private LocalDateTime createdTime;

        public static SingleResponse ofMember(com.server.seb41_main_11.domain.post.entity.Post post) {
            return SingleResponse.builder()
                    .postId(post.getPostId())
                    .title(post.getTitle())
                    .content(post.getContent())
                    .kinds(post.getKinds())
                    .views(post.getViews())
                    .writer(post.getMember().getMemberName())
                    .createdTime(post.getCreateTime())
                    .build();
        }
      
        public static SingleResponse ofCounselor(com.server.seb41_main_11.domain.post.entity.Post post) {
            return SingleResponse.builder()
                    .postId(post.getPostId())
                    .title(post.getTitle())
                    .content(post.getContent())
                    .kinds(post.getKinds())
                    .views(post.getViews())
                    .writer(post.getCounselor().getCounselorName())
                    .createdTime(post.getCreateTime())
                    .build();
        }
    }

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class MultiResponse {
        private long postId;
        private String title;
        private com.server.seb41_main_11.domain.post.entity.Post.Kind kinds;
        private int views;
        private String writer;
        private LocalDateTime createdTime;

        public static MultiResponse of(com.server.seb41_main_11.domain.post.entity.Post post) {
            return MultiResponse.builder()
                    .postId(post.getPostId())
                    .title(post.getTitle())
                    .kinds(post.getKinds())
                    .views(post.getViews())
                    .writer(post.getMember().getMemberName())
                    .createdTime(post.getCreateTime())
                    .build();
        }
    }
}

 

Mapper

@Mapper(componentModel = "spring")
public interface PostMapper {

    Post postToEntity(PostDto.Post post);

    Post patchToEntity(PostDto.Patch patch);

    // 생성, 단건조회

    default PostDto.SingleResponse entityToMemberSingleResponse(Post post) {
        return PostDto.SingleResponse.ofMember(post);
    }

    default PostDto.SingleResponse entityToCounselorSingleResponse(Post post) {
        return PostDto.SingleResponse.ofMember(post);
    }

    // 전체 조회
    default List<PostDto.MultiResponse> entityToMultiResponse(List<Post> posts) {
        List<PostDto.MultiResponse> list = new ArrayList<PostDto.MultiResponse>(posts.size());

        for (Post a : posts) {
            list.add(PostDto.MultiResponse.of(a));
        }

        return list;
    }
}

 

Repository

public interface PostRepository extends JpaRepository<Post, Long> {

//    @Modifying
//    @Query("update Post p set p.views = p.views + 1 where p.postId = :postId")
//    void updateViews(@Param("postId") Long postId);

    @Query(value = "select p FROM Post p WHERE p.postId = :postId")
    Optional<Post> findById(@Param("postId") Long postId);
}

 

Service

@Service
@Transactional
@RequiredArgsConstructor
public class PostService {
    // ----------------- DI ---------------------
    private final PostRepository postRepository;

    private final MemberService memberService;

    private final CounselorService counselorService;
    // ----------------- DI ---------------------

    // 멤버 글 등록
    public Post createByMember(Post post, Member member) {

        post.setMember(member);
        return postRepository.save(post);
    }

    // 상담사 글 등록
    public Post createByCounselor(Post post, Counselor counselor) {

        post.setCounselor(counselor);
        return postRepository.save(post);
    }

    // 멤버 글 수정
    public Post updateByMember(Post post, HttpServletRequest httpServletRequest) {
        Post findPost = findVerifiedPost(post.getPostId());

        if(findPost.getMember().getMemberId() != memberService.getLoginMember(httpServletRequest).getMemberId()){
            throw new BusinessException(ErrorCode.NO_RIGHT_EDIT);
        }

        findPost.update(post.getTitle(),post.getContent(),post.getKinds());

        return postRepository.save(findPost);
    }

    // 상담사 글 수정
    public Post updateByCounselor(Post post, HttpServletRequest httpServletRequest) {
        Post findPost = findVerifiedPost(post.getPostId());

        if (findPost.getCounselor().getCounselorId() != counselorService.getLoginCounselor(httpServletRequest).getCounselorId()) {
            throw new BusinessException(ErrorCode.NO_RIGHT_EDIT);
        }

        findPost.update(post.getTitle(),post.getContent(),post.getKinds());

        return postRepository.save(findPost);
    }

    //관리자 글 수정
    public Post updateByAdmin(Post post) {
        Post findPost = findVerifiedPost(post.getPostId());

        findPost.update(post.getTitle(),post.getContent(),post.getKinds());

        return postRepository.save(findPost);
    }

    // 글 1건 조회
    // @Transactional(readOnly = true) (조회수 로직에서 막힘 set)
    public Post find(long postId) {

        Post post = findVerifiedPost(postId);
        String memberName = post.getMember().getMemberName();

        updateViews(postId);

        return post;
    }

    // 글 전체 조회
    @Transactional(readOnly = true)
    public Page<Post> findAll(int page, int size) {

        Pageable pageable = PageRequest.of(page, size, Sort.by("postId").descending());

        Page<Post> pagePost = postRepository.findAll(pageable);

        List<Post> listPost = pagePost.getContent();

        for (Post a : listPost) {
            if (Objects.equals(a.getMember().getStatus(), DELETE)) {
//                a.setTitle("탈퇴한 회원의 글입니다." + a.getTitle());
                a.getMember().setMemberName("탈퇴한 회원");
            }
        }

        return pagePost;
    }

    //회원 글 삭제
    public void deleteByMember(long postId, HttpServletRequest httpServletRequest) {
        Post findPost = findVerifiedPost(postId);
        Member member = memberService.getLoginMember(httpServletRequest);

        if(findPost.getMember().getMemberId() != member.getMemberId()){
            throw new BusinessException(ErrorCode.NO_RIGHT_DELETE);
        }
        postRepository.deleteById(postId);
    }

    //상담사 글 삭제
    public void deleteByCounselor(long postId, HttpServletRequest httpServletRequest) {
        Post findPost = findVerifiedPost(postId);
        Counselor counselor = counselorService.getLoginCounselor(httpServletRequest);
        if(findPost.getCounselor().getCounselorId() != counselor.getCounselorId()){
            throw new BusinessException(ErrorCode.NO_RIGHT_DELETE);
        }
        postRepository.deleteById(postId);
    }

    //관리자 글 삭제
    public void deleteByAdmin(long postId) {
        postRepository.deleteById(postId);
    }

    // 조회수 증가 로직
    public void updateViews(Long id) {
        Post post = findVerifiedPost(id);

        int findViews = post.getViews() + 1;

        post.setViews(findViews);

        postRepository.save(post);
    }

    // 글 검증
    private Post findVerifiedPost(long postId) {
        Optional<Post> optPost = Optional.of(postRepository.getReferenceById(postId));
        Post findPost = optPost.orElseThrow(() -> new EntityNotFoundException("글이 없습니다"));

        return findPost;
    }
}

 

Controller

@RestController
@RequestMapping("/api/posts")
@Validated
@RequiredArgsConstructor
public class PostController {

    private final PostService postService;
    private final MemberService memberService;
    private final CounselorService counselorService;
    private final PostMapper mapper;

    // 일반 유저가 글 등록
    @PostMapping("/post")
    public ResponseEntity createPost(@Valid @RequestBody PostDto.Post post,
                                     HttpServletRequest request) {

        Role role = memberService.getLoginRole(request);

        if (role.equals(Role.USER)) {
            Post findPostByMember = postService.createByMember(mapper.postToEntity(post), memberService.getLoginMember(request));
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(findPostByMember)), HttpStatus.CREATED);
        } else if (role.equals(Role.COUNSELOR)) {
            Post findPostByCounselor = postService.createByCounselor(mapper.postToEntity(post), counselorService.getLoginCounselor(request));
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToCounselorSingleResponse(findPostByCounselor)), HttpStatus.CREATED);
        } else {
            Post findPostByMember = postService.createByMember(mapper.postToEntity(post), memberService.getLoginMember(request));
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(findPostByMember)), HttpStatus.CREATED);
        }
    }

    @PatchMapping("/patch/{post-id}")
    public ResponseEntity updatePost(@PathVariable("post-id") @Positive long postId,
                                     @Valid @RequestBody PostDto.Patch patch,
                                     HttpServletRequest httpServletRequest) {
        patch.setPostId(postId);

        Role role = memberService.getLoginRole(httpServletRequest);

        if(role.equals(Role.USER)) {
            Post update = postService.updateByMember(mapper.patchToEntity(patch), httpServletRequest);
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(update)), HttpStatus.OK);
        }else if(role.equals(Role.COUNSELOR)){
            Post update = postService.updateByCounselor(mapper.patchToEntity(patch), httpServletRequest);
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(update)), HttpStatus.OK);
        }else{
            Post update = postService.updateByAdmin(mapper.patchToEntity(patch));
            return new ResponseEntity<>(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(update)), HttpStatus.OK);
        }

    }

    @GetMapping("/lookup/{post-id}")
    public ResponseEntity findPost(@PathVariable("post-id") @Positive long postId) {
        Post findPost = postService.find(postId);

        return new ResponseEntity(new SingleResponseDto<>(mapper.entityToMemberSingleResponse(findPost)), HttpStatus.OK);
    }

    @GetMapping("/lookup/list")
    public ResponseEntity findAllByPostId(@Positive @RequestParam(defaultValue = "1") int page,
                                          @Positive @RequestParam(defaultValue = "10") int size) {
        Page<Post> pagePost = postService.findAll(page-1, size);
        List<Post> posts = pagePost.getContent();

        return new ResponseEntity<>(new MultiResponseDto<>(mapper.entityToMultiResponse(posts), pagePost), HttpStatus.OK);
    }

    @DeleteMapping("/delete/{post-id}")
    public ResponseEntity deletePost(@PathVariable("post-id") @Positive long postId,
                                     HttpServletRequest httpServletRequest) {

        Role role = memberService.getLoginRole(httpServletRequest);

        if(role.equals(Role.USER)) {
            postService.deleteByMember(postId, httpServletRequest);
        }else if(role.equals(Role.COUNSELOR)){
            postService.deleteByCounselor(postId, httpServletRequest);
        }else{
            postService.deleteByAdmin(postId);
        }

        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}
profile

우주먼지

@o귤o

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그