사용자가 자신의 면접 영상을 올릴 수 있도록 하며 일반 게시글 기능과 동일합니다. 게시글 작성, 읽기, 수정, 삭제 및 좋아요 기능이 구현되어 있습니다. Board와 BoardMemberLike 도메인으로 구성되어 있습니다.
@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
@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;
}
}
}
@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를 받아 좋아요를 추가합니다.
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();
}
}
해당 코드는 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();
}
}
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) 엔티티를 함께 로딩합니다. 이렇게 함으로써 게시글을 조회할 때 연관된 멤버와 질문도 함께 가져올 수 있습니다.