프로젝트 - 빙터뷰 [게시글 기능 구현]

Chan Young Jeong·2023년 6월 29일
0

프로젝트 빙터뷰

목록 보기
5/9
post-thumbnail

Board

사용자가 자신의 면접 영상을 올릴 수 있도록 하며 일반 게시글 기능과 동일합니다. 게시글 작성, 읽기, 수정, 삭제 및 좋아요 기능이 구현되어 있습니다. Board와 BoardMemberLike 도메인으로 구성되어 있습니다.

Domain

Board

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board extends EntityDate {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "question_id")
    private Question question;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;
    private String content;
    private String videoUrl;


    @OneToMany(mappedBy = "board",cascade = CascadeType.REMOVE)
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "board",cascade = CascadeType.REMOVE)
    private List<BoardMemberLike> boardMemberLikes = new ArrayList<>();

    @Builder
    public Board(Question question, Member member, String content, String videoUrl) {
        this.question = question;
        this.member = member;
        this.content = content;
        this.videoUrl = videoUrl;
    }

    public void update(Question question , String content, String videoUrl) {
        this.question = question;
        this.content = content;
        this.videoUrl = videoUrl;
    }

}
  • @Entity: JPA 엔티티임을 나타내는 어노테이션으로, 데이터베이스의 테이블과 매핑됩니다.
  • @Getter: Lombok 어노테이션으로, 자동으로 getter 메서드를 생성합니다.
  • @NoArgsConstructor(access = AccessLevel.PROTECTED): Lombok 어노테이션으로, 인자 없는 생성자를 생성합니다. protected 접근 제한자로 설정되어 외부에서 직접 생성되지 않도록 합니다.
  • @Id, @GeneratedValue: 데이터베이스의 기본 키(primary key) 역할을 하는 id 필드에 대한 설정입니다. @GeneratedValue는 기본 키 값을 자동으로 생성하도록 지정합니다.
  • @ManyToOne(fetch = FetchType.LAZY), @JoinColumn: 다른 엔티티와의 관계를 정의하는 어노테이션입니다. Question과 Member 객체와의 관계를 나타냅니다. FetchType.LAZY로 설정하여 필요할 때만 관련 객체를 로딩합니다.
  • @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE): Comment와 BoardMemberLike 객체와의 일대다 관계를 정의하는 어노테이션입니다. mappedBy 속성은 연관 관계의 주인을 지정합니다. CascadeType.REMOVE는 해당 Board 객체가 삭제되면 관련된 Comment와 BoardMemberLike 객체도 함께 삭제됨을 의미합니다.
  • @Builder: Lombok 어노테이션으로, 빌더 패턴을 사용하여 객체를 생성할 수 있도록 합니다. Question, Member, content, videoUrl을 매개변수로 받아 객체를 초기화하는 생성자를 자동으로 생성합니다.
  • public void update(Question question, String content, String videoUrl): 게시글을 업데이트하는 메서드입니다. 매개변수로 받은 값을 사용하여 게시글의 내용과 동영상 URL을 업데이트합니다.

BoardMemberLike

@Entity
@Getter
@NoArgsConstructor
public class BoardMemberLike {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "board_member_id")
    private Long id;

    @Enumerated(EnumType.STRING)
    private LikeType likeStatus;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id")
    private Board board;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @Builder
    public BoardMemberLike(Board board, Member member) {
        this.board = board;
        this.member = member;
        this.likeStatus = LikeType.LIKE;
    }

    public void updateStatus() {
        if (this.likeStatus == LikeType.LIKE) {
            this.likeStatus = LikeType.UNLIKE;
        }else{
            this.likeStatus = LikeType.LIKE;
        }
    }
}

Controller

@Slf4j
@RestController
@RequestMapping(value = "/boards", produces = "application/json;charset=utf8")
@RequiredArgsConstructor
public class BoardController {

    private final BoardService boardService;
    private final S3Upload fileStore;

    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(AccessDeniedException.class)
    public ErrorResult accessDeniedExHandle(AccessDeniedException e) {
        log.error("[accessDeniedExHandle] ex", e);
        return new ErrorResult("403", e.getMessage());
    }

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(EntityNotFoundException.class)
    public ErrorResult entityNotFoundExHandle(EntityNotFoundException e) {
        log.error("[entityNotFoundExHandle] ex", e);
        return new ErrorResult("404", e.getMessage());
    }


    @GetMapping(value = "")
    @Trace
    public ResponseEntity<BoardListDTO> boards(@RequestParam(name = "page", defaultValue = "0") int page,
                                               @RequestParam(name = "size", defaultValue = "20") int size) {
        log.info("BoardController boards");
        BoardListDTO boardListDTO = boardService.findAll(page, size, true,null);

        return ResponseEntity.ok(boardListDTO);
    }

    @GetMapping(value = "", params = "member_id")
    @Trace
    public ResponseEntity<BoardListDTO> filterByMember(@RequestParam(name = "member_id") Long member_id,
                                                       @RequestParam(name = "page", defaultValue = "0") int page,
                                                       @RequestParam(name = "size", defaultValue = "20") int size) {

        Long start = System.currentTimeMillis();
        BoardListDTO boardListDTO = boardService.findByMember(member_id, page, size);
        Long end = System.currentTimeMillis();
        log.info("findByMember {}", end - start);

        return ResponseEntity.ok(boardListDTO);
    }

    @GetMapping(value = "", params = {"question_id","order_by"})
    @Trace
    public ResponseEntity<BoardListDTO> filterByQuestion(@RequestParam(name = "question_id") Long question_id,
                                                         @RequestParam(name = "order_by",defaultValue = "") String order_by,
                                                         @RequestParam(name = "page", defaultValue = "0") int page,
                                                         @RequestParam(name = "size", defaultValue = "20") int size) {

        if (order_by.equals("like")) {
            BoardListDTO boardListDTO = boardService.orderByLike(page, size,question_id);
            return ResponseEntity.ok(boardListDTO);

        } else if (order_by.equals("comment")) {
            BoardListDTO boardListDTO = boardService.orderByComment(page, size,question_id);
            return ResponseEntity.ok(boardListDTO);

        } else if (order_by.equals("old")) {
            BoardListDTO boardListDTO = boardService.findAll(page, size, false,question_id);
            return ResponseEntity.ok(boardListDTO);
        }

        BoardListDTO boardListDTO = boardService.findByQuestion(question_id, page, size);

        return ResponseEntity.ok(boardListDTO);

    }



    @GetMapping(value = "", params = "order_by")
    @Trace
    public ResponseEntity<BoardListDTO> filterByOrder(@RequestParam(name = "order_by") String order_by,
                                                      @RequestParam(name = "page", defaultValue = "0") int page,
                                                      @RequestParam(name = "size", defaultValue = "20") int size) {

        if (order_by.equals("like")) {
            BoardListDTO boardListDTO = boardService.orderByLike(page, size,null);
            return ResponseEntity.ok(boardListDTO);

        } else if (order_by.equals("comment")) {
            BoardListDTO boardListDTO = boardService.orderByComment(page, size, null);
            return ResponseEntity.ok(boardListDTO);

        } else if (order_by.equals("old")) {
            BoardListDTO boardListDTO = boardService.findAll(page, size, false, null);
            return ResponseEntity.ok(boardListDTO);
        }

        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(new ArrayList<>());
        boardListDTO.setHasNext(false);
        boardListDTO.setNextPage(0);

        return ResponseEntity.badRequest().body(boardListDTO);


    }

    @PostMapping("")
    @Trace
    public ResponseEntity<BoardResponseDTO> create(@RequestBody BoardCreateDTO boardCreateDTO,
                                                   @LoginMemberId Long memberId) {

        Long boardId = boardService.save(memberId, boardCreateDTO);
        BoardResponseDTO boardResponseDTO = new BoardResponseDTO();
        boardResponseDTO.setBoardId(boardId);

        return new ResponseEntity<>(boardResponseDTO, HttpStatus.CREATED);
    }

    @GetMapping("/{id}")
    @Trace
    public ResponseEntity<BoardDTOwithLike> board(@PathVariable(name = "id") Long id,
                                          @LoginMemberId Long memberId) {
        Boolean like = boardService.isLike(id, memberId);
        BoardDTO boardDTO = boardService.findById(id);

        BoardDTOwithLike boardDTOwithLike = BoardDTOwithLike.builder()
                .boardDTO(boardDTO)
                .like(like)
                .build();
        return ResponseEntity.ok(boardDTOwithLike);
    }


    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @Trace
    public void delete(@PathVariable(name = "id") Long id,@LoginMemberId Long memberId)
    {
        boardService.delete(id,memberId);
    }


    @PutMapping("/{id}")
    @Trace
    public ResponseEntity<BoardResponseDTO> update(@PathVariable(name = "id") Long id,
                                                   @RequestBody BoardUpdateDTO boardUpdateDTO,
                                                   @LoginMemberId Long memberId) {

        Long boardId = boardService.update(id, boardUpdateDTO,memberId);
        BoardResponseDTO boardResponseDTO = new BoardResponseDTO();
        boardResponseDTO.setBoardId(boardId);
        return new ResponseEntity<>(boardResponseDTO, HttpStatus.CREATED);
    }


    @GetMapping("/{id}/like")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @Trace
    public void like(@PathVariable(name = "id") Long id, @LoginMemberId Long memberId) {
        boardService.like(memberId, id);
    }

    @PostMapping("/video/{imgNumber}")
    @Trace
    public ResponseEntity<VideoResponseDTO> videoUpload(@ModelAttribute VideoDTO videoDTO,
                                                        @PathVariable(name = "imgNumber") Long imgNumber) {

        if (videoDTO.getVideo() != null && !videoDTO.getVideo().isEmpty()) {
            String storeFileName = fileStore.createStoreFileName(videoDTO.getVideo().getOriginalFilename());

            log.info("----------uploadFile----------start {} {}", LocalDateTime.now(), Thread.currentThread().getName());

            fileStore.tempFileUpload(videoDTO.getVideo(), storeFileName);
//          videoDTO.getVideo().transferTo(new File(tempDir + storeFileName));

            fileStore.uploadFile(storeFileName,imgNumber);
            log.info("----------UploadFile----------returned {} {}", LocalDateTime.now(), Thread.currentThread().getName());

            VideoResponseDTO videoResponseDTO = new VideoResponseDTO();
//            videoResponseDTO.setVideoUrl("https://vingterview.s3.ap-northeast-2.amazonaws.com/video/31181518-cb36-4aa2-8b9f-49fd03899f34.mp4");
            videoResponseDTO.setVideoUrl(fileStore.getFullPath(storeFileName, false));

            return new ResponseEntity<>(videoResponseDTO, HttpStatus.CREATED);

        }

        VideoResponseDTO videoResponseDTO = new VideoResponseDTO();
        videoResponseDTO.setVideoUrl("잘못된 접근입니다.");
        return new ResponseEntity<>(videoResponseDTO, HttpStatus.BAD_REQUEST);

    }
}

  • @ExceptionHandler: 예외 처리를 위한 어노테이션입니다. AccessDeniedException과 EntityNotFoundException 예외를 처리하는 메서드가 정의되어 있습니다. 해당 예외가 발생하면 예외가 발생했을 때 DTO인 ErrorResult에 에러코드와 에러 메세지를 담아 정해놓은 응답코드로 Response를 보내게 됩니다.

  • /boards 경로의 GET 메서드 핸들러(boards 메서드): 페이지와 사이즈를 파라미터로 받아 모든 게시글을 조회합니다. BoardListDTO 객체를 반환합니다.

  • /boards 경로의 GET 메서드 핸들러(filterByMember 메서드): 멤버 ID를 파라미터로 받아 해당 멤버의 게시글을 조회합니다. BoardListDTO 객체를 반환합니다.

  • /boards 경로의 GET 메서드 핸들러(filterByQuestion 메서드): 질문 ID와 정렬 기준을 파라미터로 받아 해당 질문에 대한 게시글을 조회합니다. BoardListDTO 객체를 반환합니다.

  • /boards 경로의 GET 메서드 핸들러(filterByOrder 메서드): 정렬 기준을 파라미터로 받아 게시글을 조회합니다. BoardListDTO 객체를 반환합니다.

  • /boards 경로의 POST 메서드 핸들러(create 메서드): 새로운 게시글을 생성합니다. BoardCreateDTO 객체와 로그인 멤버의 ID를 받습니다. BoardResponseDTO 객체를 반환합니다.

  • /boards/{id} 경로의 GET 메서드 핸들러(board 메서드): 게시글 ID와 로그인 멤버의 ID를 받아 게시글과 좋아요 여부를 조회합니다. BoardDTOwithLike 객체를 반환합니다.

  • /boards/{id} 경로의 DELETE 메서드 핸들러(delete 메서드): 게시글 ID와 로그인 멤버의 ID를 받아 게시글을 삭제합니다.

  • /boards/{id} 경로의 PUT 메서드 핸들러(update 메서드): 게시글 ID, BoardUpdateDTO 객체, 로그인 멤버의 ID를 받아 게시글을 업데이트합니다. BoardResponseDTO 객체를 반환합니다.

  • /boards/{id}/like 경로의 GET 메서드 핸들러(like 메서드): 게시글 ID와 로그인 멤버의 ID를 받아 좋아요를 추가합니다.

비동기 처리

  • /boards/video/{imgNumber} 경로의 POST 메서드 핸들러(videoUpload 메서드): 비디오 파일을 업로드합니다. VideoDTO 객체와 이미지 번호를 받습니다. VideoResponseDTO 객체를 반환합니다.
public class VideoDTO {

    private MultipartFile video;
}

public class VideoResponseDTO {

    private String videoUrl;
}

해당 메서드 핸들러는 면접 영상을 딥페이크 기술을 활용하여 가상의 사용자 얼굴로 변환한 후, 게시하는 역할을 합니다. 하지만 딥페이크 처리는 CPU를 사용할 때 약 700프레임을 처리하는 데 8~10분 정도 소요됩니다. (GPU를 사용하면 1분 이내로 처리 가능합니다.) 이렇게 오랜 시간이 걸리는 작업을 동기적으로 처리하면 응답이 오랫동안 전달되지 않아 TIMEOUT 오류가 발생할 수 있습니다. 따라서 이 문제를 해결하기 위해 비동기적인 처리 방식을 채택하였습니다. 즉, 먼저 영상의 S3에 저장될 경로(URL)를 클라이언트에게 반환하고, 딥페이크 처리는 나중에 비동기적으로 수행합니다.

비동기적으로 코드가 수행되는 부부은 fileStore.uploadFile(storeFileName,imgNumber)입니다. 해당 메서드를 보면 @Async 어노테이션을 이용하여 비동기적으로 실행할 수 있도록하였습니다. 딥페이크는 스프링 서버가 아니라 플라스크 서버에서 동작합니다.

    @Async("threadPoolTaskExecutor")

    public void uploadFile(String storeName, Long imgNumber) {
        log.info("Started uploading file at {} {}", LocalDateTime.now(),Thread.currentThread().getName());

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        String videoPath = amazonS3.getUrl(bucket, "temp/" + storeName).toString();
        body.add("imgNumber", imgNumber.toString());
        body.add("name", storeName);
        body.add("bucket", bucket);
        HttpEntity<?> request = new HttpEntity<>(body, headers);
        ResponseEntity<String> response = null;
        try {
            response = restTemplate.postForEntity("http://18.116.5.117:5000/boards/video", request, String.class);
            log.info("Response from flask server {}", response);
            log.info("Ended uploading file at {} {}", LocalDateTime.now(),Thread.currentThread().getName());
        } catch (ResourceAccessException e) {
            amazonS3.deleteObject(bucket,"temp/" + storeName);
            log.error("비디오 변환 과정에서 오류 발생하였습니다.");
            e.printStackTrace();

        }


    }

Service

해당 코드는 BoardService라는 클래스로, 게시판(Board)과 관련된 비즈니스 로직을 처리하는 서비스 클래스입니다. 주요 기능은 다음과 같습니다.


@Service
@RequiredArgsConstructor
@Slf4j
public class BoardService {
    private final BoardRepository boardRepository;
    private final MemberRepository memberRepository;
    private final QuestionRepository questionRepository;
    private final BoardMemberLikeRepository boardMemberLikeRepository;


    @Transactional
    public Long save(Long memberId, BoardCreateDTO boardCreateDTO) {

        Member member = memberRepository.findById(memberId).orElseThrow(() -> new EntityNotFoundException("해당 사용자를 찾을 수 없습니다"));

        Long questionId = boardCreateDTO.getQuestionId();
        Question question = questionRepository.findById(questionId).orElseThrow(() -> new EntityNotFoundException("해당 질문을 찾을 수 없습니다."));

        Board board = Board.builder()
                .member(member)
                .content(boardCreateDTO.getContent())
                .question(question)
                .videoUrl(boardCreateDTO.getVideoUrl())
                .build();

        boardRepository.save(board);

        return board.getId();

    }



    public BoardDTO findById(Long id) {
        Board board = boardRepository.findByIdWithMemberQuestion(id).orElseThrow(() -> new EntityNotFoundException("해당 게시글을 찾을 수 없습니다."));
        return transferBoardDTO(board);
    }

    public Boolean isLike(Long id, Long memberId) {

        Optional<BoardMemberLike> boardMemberLike = boardMemberLikeRepository.findByMemberIdAndBoardId(memberId, id);
        if (boardMemberLike.isEmpty()) {
            return false;
        }else{
            if (boardMemberLike.get().getLikeStatus() == LikeType.LIKE) {
                return true;
            }else{
                return false;
            }
        }
    }



    public void delete(Long id, Long memberId) {

        Board board = boardRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("해당 게시글을 찾을 수 없습니다"));
        if (board.getMember().getId() != memberId) {
            throw new AccessDeniedException("해당 게시글에 생성자만 글을 삭제할 수 있습니다.");
        }

        boardRepository.deleteById(id);
    }

    @Transactional
    public Long update(Long id, BoardUpdateDTO boardUpdateDTO,Long memberId) {

        Board board = boardRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("해당 게시글을 찾을 수 없습니다"));
        if (board.getMember().getId() != memberId) {
            throw new AccessDeniedException("해당 게시글에 생성자만 글을 수정할 수 있습니다.");
        }



        Question question = questionRepository.findById(boardUpdateDTO.getQuestionId()).orElseThrow(() -> new EntityNotFoundException("해당 질문을 찾을 수 없습니다."));
        board.update(question, boardUpdateDTO.getContent(), boardUpdateDTO.getVideoUrl());
        return board.getId();

    }

    @Transactional
    public void like(Long memberId, Long boardId) {

        Optional<BoardMemberLike> boardMemberLike = boardMemberLikeRepository.findByMemberIdAndBoardId(memberId, boardId);

        if (boardMemberLike.isEmpty()) {
            log.info("create like , board_id:  {} , member_id: {}", boardId, memberId);
            Board board = boardRepository.findById(boardId).orElseThrow(() -> new EntityNotFoundException("해당 게시물을 찾을 수 없습니다."));
            Member member = memberRepository.findById(memberId).orElseThrow(() -> new EntityNotFoundException("해당 멤버를 찾을 수 없습니다."));
            boardMemberLikeRepository.save(new BoardMemberLike(board, member));

        }else{
            boardMemberLike.get().updateStatus();
        }


    }

    /**
     * @param page        현재 페이지
     * @param size        페이지 크기
     * @param question_id
     * @return BoardListDTO
     * 최근 생성된 게시물 순
     */
    public BoardListDTO findAll(int page, int size, boolean desc, Long questionId) {
        PageRequest pageRequest;
        if (desc) {
            pageRequest = PageRequest.of(page, size, Sort.by("createTime").descending());
        }else{
            pageRequest = PageRequest.of(page, size, Sort.by("createTime").ascending());
        }

        Slice<Board> boardSlice = null;
        if (questionId == null) {
            boardSlice = boardRepository.findSliceBy(pageRequest);
        }else{
            boardSlice = boardRepository.findByQuestionIdWithMemberQuestion(questionId, pageRequest);
        }


        List<BoardDTO> boardDTOList = boardSlice.stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());

        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(boardDTOList);
        boardListDTO.setHasNext(boardSlice.hasNext());
        boardListDTO.setNextPage(page + 1); // 최대 페이지 넘게 요청하면 애초에 아무것도 반환이 안 됨.

        return boardListDTO;
    }

    public BoardListDTO findByMember(Long memberId, int page,int size) {

        /**
         * 92ms
         */
/*        List<Long> boardIds = boardRepository.findByMemberId(memberId)
                .stream().map(board -> board.getId()).collect(Collectors.toList());

        List<BoardDTO> boardDTOList = boardRepository.findByIdsWithMemberQuestion(boardIds)
                .stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());*/

        /**
         * 81ms
         */
        PageRequest pageRequest = PageRequest.of(page, size, Sort.by("createTime").descending());

        Slice<Board> boardSlice = boardRepository.findByMemberIdWithMemberQuestion(memberId,pageRequest);

        List<BoardDTO> boardDTOList = boardSlice.stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());


        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(boardDTOList);
        boardListDTO.setHasNext(boardSlice.hasNext());
        boardListDTO.setNextPage(page+1);


        return boardListDTO;

    }

    public BoardListDTO findByQuestion(Long questionId,int page,int size) {

        PageRequest pageRequest = PageRequest.of(page, size, Sort.by("createTime").descending());

        Slice<Board> boardSlice = boardRepository.findByQuestionIdWithMemberQuestion(questionId,pageRequest);
        List<BoardDTO> boardDTOList = boardSlice.stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());


        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(boardDTOList);
        boardListDTO.setHasNext(boardSlice.hasNext());
        boardListDTO.setNextPage(page+1);

        return boardListDTO;
    }


    public BoardListDTO orderByLike(int page,int size,Long questionId) {


        PageRequest pageRequest = PageRequest.of(page, size);
        Slice<Board> boardSlice = null;
        if (questionId == null) {
            boardSlice = boardRepository.orderSliceByLike(pageRequest);
        }else{
            boardSlice = boardRepository.orderSliceByLikeQuestion(pageRequest, questionId);
        }
        List<BoardDTO> boardDTOList = boardSlice.stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());

        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(boardDTOList);
        boardListDTO.setHasNext(boardSlice.hasNext());
        boardListDTO.setNextPage(page+1);

        return boardListDTO;

    }

    public BoardListDTO orderByComment(int page, int size, Long questionId) {
        PageRequest pageRequest = PageRequest.of(page, size);

        Slice<Board> boardSlice = null;
        if (questionId == null) {
            boardSlice = boardRepository.orderSliceByComment(pageRequest);
        }else{
            boardSlice = boardRepository.orderSliceByCommentQuestion(pageRequest, questionId);
        }

        List<BoardDTO> boardDTOList = boardSlice.stream().map(board -> transferBoardDTO(board)).collect(Collectors.toList());

        BoardListDTO boardListDTO = new BoardListDTO();
        boardListDTO.setBoards(boardDTOList);
        boardListDTO.setHasNext(boardSlice.hasNext());
        boardListDTO.setNextPage(page+1);

        return boardListDTO;

    }

    private BoardDTO transferBoardDTO(Board board) {

        Question question = board.getQuestion();
        Member member = board.getMember();

        List<Comment> comments = board.getComments();

        int likeCount = boardMemberLikeRepository.countByBoardAndLikeStatus(board, LikeType.LIKE);


        return BoardDTO.builder()
                .boardId(board.getId())
                .questionId(question.getId())
                .questionContent(question.getContent())
                .memberId(member.getId())
                .memberNickname(member.getNickname())
                .profileImageUrl(member.getProfileImageUrl())
                .content(board.getContent())
                .videoUrl(board.getVideoUrl())
                .likeCount(likeCount)
                .commentCount(comments.size())
                .createTime(LocalDateTime.now())
                .updateTime(board.getUpdateTime())
                .createTime(board.getCreateTime())
                .build();
    }



}

  • save: 멤버 ID와 게시글 생성에 필요한 정보를 전달받아, 새로운 게시글을 생성하고 저장합니다.
  • findById: 게시글의 ID를 받아 해당 게시글을 조회하고, BoardDTO로 변환하여 반환합니다.
  • isLike: 게시글 ID와 멤버 ID를 받아 해당 멤버가 해당 게시글을 좋아하는지 여부를 확인합니다.
  • delete: 게시글의 ID와 멤버 ID를 받아, 해당 멤버가 해당 게시글을 삭제할 수 있는 권한이 있는지 확인한 후 게시글을 삭제합니다.
  • update: 게시글의 ID와 업데이트할 내용을 담은 BoardUpdateDTO, 그리고 멤버 ID를 받아 해당 게시글을 업데이트합니다.
  • like: 멤버 ID와 게시글 ID를 받아 해당 멤버가 해당 게시글에 좋아요를 누르거나 해제합니다.
  • findAll: 페이지네이션을 적용하여 전체 게시글 목록을 조회합니다. 최근 생성된 게시글부터 정렬되며, 필요에 따라 질문 ID를 기준으로 필터링할 수 있습니다.
  • findByMember: 멤버 ID를 받아 해당 멤버가 작성한 게시글 목록을 페이지네이션을 적용하여 조회합니다.
  • findByQuestion: 질문 ID를 받아 해당 질문에 대한 게시글 목록을 페이지네이션을 적용하여 조회합니다.
  • orderByLike: 좋아요 수를 기준으로 게시글을 정렬하여 페이지네이션을 적용하여 조회합니다. 필요에 따라 질문 ID를 기준으로 필터링할 수 있습니다.
  • orderByComment: 댓글 수를 기준으로 게시글을 정렬하여 페이지네이션을 적용하여 조회합니다. 필요에 따라 질문 ID를 기준으로 필터링할 수 있습니다.
  • transferBoardDTO: Board 객체를 BoardDTO로 변환하는 내부 메서드입니다. 이 메서드를 통해 게시글의 정보를 DTO 객체로 전환하여 반환합니다.

Repository


public interface BoardRepository extends JpaRepository<Board,Long> {

    @Query("select b from Board  b " +
            " join fetch b.member m " +
            " join fetch b.question  q" +
            " where b.id = :id ")
    Optional<Board> findByIdWithMemberQuestion(@Param(value = "id") Long id);

    @Query("select b from Board  b " +
            " join fetch b.member m " +
            " join fetch b.question  q " +
            " where " +
            " m.id = :memberId")
    Slice<Board> findByMemberIdWithMemberQuestion(@Param(value = "memberId") Long memberId,Pageable pageable);

    @Query("select b from Board  b " +
            " join fetch b.member m " +
            " join fetch b.question  q " +
            " where " +
            " q.id = :questionId")
    Slice<Board> findByQuestionIdWithMemberQuestion(@Param(value = "questionId") Long questionId,Pageable pageable);


    Slice<Board> findSliceBy(Pageable pageable);

    @Query("select b from Board b " +
            " left outer join b.boardMemberLikes bml " +
            " join fetch b.member m " +
            " join fetch b.question q " +
            " group by b.id " +
            " order by count(bml.id) desc, b.createTime desc ")
    Slice<Board> orderSliceByLike(Pageable pageable);

    @Query("select b from Board b " +
            " left outer join b.boardMemberLikes bml " +
            " join fetch b.member m " +
            " join fetch b.question q " +
            " where b.question.id = :questionId" +
            " group by b.id " +
            " order by count(bml.id) desc, b.createTime desc ")
    Slice<Board> orderSliceByLikeQuestion(Pageable pageable,@Param(value = "questionId") Long questionId);

    @Query("select b from Board b " +
            " left outer join b.comments c " +
            " join fetch b.member m " +
            " join fetch b.question q " +
            " group by b.id " +
            " order by count(c.id) desc, b.createTime desc ")
    Slice<Board> orderSliceByComment(Pageable pageable);

    @Query("select b from Board b " +
            " left outer join b.comments c " +
            " join fetch b.member m " +
            " join fetch b.question q " +
            " where b.question.id = :questionId" +
            " group by b.id " +
            " order by count(c.id) desc, b.createTime desc ")
    Slice<Board> orderSliceByCommentQuestion(Pageable pageable,@Param(value = "questionId") Long questionId);

    int countByQuestion(Question question);

}
  • findByIdWithMemberQuestion: 게시글 ID를 사용하여 해당 게시글을 조회하고, 연관된 멤버와 질문 정보를 함께 가져오는 쿼리입니다.

  • findByIdsWithMemberQuestion: 게시글 ID 리스트를 받아 해당 게시글들을 조회하고, 각 게시글에 연관된 멤버와 질문 정보를 함께 가져오는 쿼리입니다.

  • findByMemberIdWithMemberQuestion: 멤버 ID를 사용하여 해당 멤버가 작성한 게시글들을 조회하고, 각 게시글에 연관된 멤버와 질문 정보를 함께 가져오는 쿼리입니다. 페이지네이션을 적용할 수 있습니다.

  • findByQuestionIdWithMemberQuestion: 질문 ID를 사용하여 해당 질문에 대한 게시글들을 조회하고, 각 게시글에 연관된 멤버와 질문 정보를 함께 가져오는 쿼리입니다. 페이지네이션을 적용할 수 있습니다.

  • findSliceBy: 페이지네이션을 적용하여 게시글을 조회하는 쿼리입니다. Slice 객체를 반환하며, 페이지 정보 외에도 추가적인 슬라이스 정보를 가져올 수 있습니다.

  • orderSliceByLike: 좋아요 수를 기준으로 게시글을 정렬하여 페이지네이션을 적용하여 조회하는 쿼리입니다. 좋아요 수와 생성 시간을 기준으로 정렬됩니다.

  • orderSliceByLikeQuestion: 특정 질문에 대한 게시글을 좋아요 수를 기준으로 정렬하여 페이지네이션을 적용하여 조회하는 쿼리입니다. 좋아요 수와 생성 시간을 기준으로 정렬됩니다.

  • orderSliceByComment: 댓글 수를 기준으로 게시글을 정렬하여 페이지네이션을 적용하여 조회하는 쿼리입니다. 댓글 수와 생성 시간을 기준으로 정렬됩니다.

  • orderSliceByCommentQuestion: 특정 질문에 대한 게시글을 댓글 수를 기준으로 정렬하여 페이지네이션을 적용하여 조회하는 쿼리입니다. 댓글 수와 생성 시간을 기준으로 정렬됩니다.

  • countByQuestion: 특정 질문과 연관된 게시글의 수를 계산하는 쿼리입니다.

join fetch를 사용하여 연관된 엔티티를 즉시 로딩하며, SQL 쿼리에서 조인을 사용하여 한 번의 쿼리로 연관된 엔티티를 함께 가져옵니다. 이를 통해 지연로딩으로 인한 추가적인 데이터베이스 쿼리 호출을 방지하고 성능을 향상시킬 수 있습니다. 예를 들어, 주어진 코드에서 join fetch는 게시글(Board) 엔티티와 연관된 멤버(Member)와 질문(Question) 엔티티를 함께 로딩합니다. 이렇게 함으로써 게시글을 조회할 때 연관된 멤버와 질문도 함께 가져올 수 있습니다.

0개의 댓글

관련 채용 정보