JPA+QueryDSL階層レビュー,レビューの実現(2)
49907 ワード
今回は前編に続き、階層的なコメントや大きなコメントを再整理します.
以前の投稿では,階層的なコメント,大きなコメントが実現されたが,N+1の問題があった.今回はそのN+1問題を解決します.
上のロジックは、前の記事で作成した
親のコメント数が
QueryDSLコード
上のコードは,記事
この映画に比べて、コードがずいぶん変わりました.😁
上記のコードを簡単に説明すると、文章クエリーロジックが実行された瞬間
親のコメントを取得した後、子供のコメントを取得します.
このようにインポートすると、親コメントは
これにより、クエリーが4回実行されます.(1回更新、3回選択)
PostOneResponse postOneCommentResponse CommentsChildrenResponse 結果画面(Postman)
N+1の問題が完全に無くなってしまい、前よりもきれいにラッピングされたようです!!🏡
私の知っている限りでは、私が作成した方法のほかに、階層クエリーを作成する方法もいろいろありますが、初めて作成した階層クエリーなので、ちょっと難しい感じがします.
次編では投稿+ファイルアップロード編(
@Transactional
public PostOneResponse getOnePost(Long postId) {
PostOneResponse postOneResponse = postRepository.findOnePostById(postId)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_POST));
commentsExtractor(postId, postOneResponse);
return postOneResponse;
}
private void commentsExtractor(Long postId, PostOneResponse postOneResponse) {
postOneResponse.getComments()
.forEach(comment -> {
List<CommentsChildrenResponse> comments = commentRepository.findPostComments(postId, comment.getCommentId());
comment.setChildren(comments);
});
}
上のロジックは、前の記事で作成した
N+1
に問題が発生したロジックです.親のコメント数が
100개
の場合、100번
以上のクエリが表示されます.これは非常に悪いコードです.ううう🥲@RequiredArgsConstructor
public class PostRepositoryImpl implements PostCustomRepository {
private final JPAQueryFactory queryFactory;
@Override
public Optional<PostOneResponse> findOnePostById(Long postId, Long userId) {
queryFactory.update(post)
.set(post.viewCount, post.viewCount.add(1))
.where(post.id.eq(postId))
.execute();
Optional<PostOneResponse> response = Optional.ofNullable(queryFactory
.select(new QPostOneResponse(
post.id,
post.title,
post.content,
post.scraps.size(),
post.comments.size(),
post.postLikes.size(),
post.timeEntity.createdDate,
post.timeEntity.updatedDate,
post.viewCount,
user.nickname,
JPAExpressions
.selectFrom(post)
.where(user.id.eq(userId))
.exists(),
JPAExpressions
.selectFrom(postLike)
.where(postLike.post.eq(post).and(user.id.eq(userId)))
.exists(),
JPAExpressions
.selectFrom(scrap)
.where(scrap.post.eq(post).and(user.id.eq(userId)))
.exists()))
.from(post)
.innerJoin(post.user, user)
.where(post.id.eq(postId))
.fetchOne());
if (response.isEmpty()) {
return Optional.empty();
}
List<PostOneCommentResponse> comments = queryFactory
.select(new QPostOneCommentResponse(
comment.parent.id,
comment.id,
comment.content,
user.nickname,
JPAExpressions
.selectFrom(comment)
.where(user.id.eq(userId))
.exists(),
comment.timeEntity.createdDate,
comment.timeEntity.updatedDate))
.from(comment)
.innerJoin(comment.post, post)
.innerJoin(comment.user, user)
.where(post.id.eq(postId).and(comment.parent.id.isNull()))
.orderBy(comment.id.asc())
.fetch();
List<CommentsChildrenResponse> childComments = queryFactory
.select(new QCommentsChildrenResponse(
comment.parent.id,
comment.id,
comment.content,
user.nickname,
JPAExpressions
.selectFrom(comment)
.where(user.id.eq(userId))
.exists(),
comment.timeEntity.createdDate,
comment.timeEntity.updatedDate
))
.from(comment)
.innerJoin(comment.post, post)
.innerJoin(comment.user, user)
.where(post.id.eq(postId).and(comment.parent.id.isNotNull()))
.fetch();
comments.stream()
.forEach(parent -> {
parent.setChildren(childComments.stream()
.filter(child -> child.getParentId().equals(parent.getCommentId()))
.collect(Collectors.toList()));
});
response.get().setComments(comments);
return response;
}
}
上のコードは,記事
조회
を発行する際に,댓글
+대댓글
を一度にインポートし,JSON
を階層化したクエリとする.この映画に比べて、コードがずいぶん変わりました.😁
上記のコードを簡単に説明すると、文章クエリーロジックが実行された瞬間
viewCount
1に増加したクエリーが表示され、post
に増加したクエリーが表示されます.JPAExpressions
はサブクエリとして使用され、戻りタイプはboolean
であり、QueryDSL
によってサポートされる機能であり、ユーザーの投稿、好きかどうか、クリップするかどうかを決定するために使用されます.post
なければOptional
を返し、存在する場合は以下の論理を実行する.いろいろな方法でコメントを得ることができますが、私は先に両親のコメントを得ました.(parentId = null)
親のコメントを取得した後、子供のコメントを取得します.
(parentId = notNull)
このようにインポートすると、親コメントは
List
に戻り、stream
を使用してcommentId
を子コメントのparentId
と比較し、同じ値があればリストタイプに入れる.これにより、クエリーが4回実行されます.(1回更新、3回選択)
@ApiModel(description = "결과 응답 데이터 모델")
@Getter
@Setter
@NoArgsConstructor
public class PostOneResponse {
@ApiModelProperty(value = "게시글 Id")
private Long postId;
@ApiModelProperty(value = "게시글 제목")
private String title;
@ApiModelProperty(value = "게시글 내용")
private String content;
@ApiModelProperty(value = "해당 게시글의 전체 스크랩 수")
private int scrapCount;
@ApiModelProperty(value = "해당 게시글의 전체 댓글의 수")
private int commentCount;
@ApiModelProperty(value = "해당 게시글의 전체 좋아요 수")
private int likeCount;
@ApiModelProperty(value = "해당 게시글의 생성 시간")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createDate;
@ApiModelProperty(value = "해당 게시글의 수정 시간")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime updateDate;
@ApiModelProperty(value = "해당 게시글의 조회수")
private Integer viewCount;
@ApiModelProperty(value = "해당 게시글의 생성 회원 이름")
private String username;
@ApiModelProperty(value = "해당 게시글의 유저 본인 확인")
private boolean myPost;
@ApiModelProperty(value = "해당 게시글의 좋아요 본인 확인")
private boolean myLike;
@ApiModelProperty(value = "해당 게시글의 스크랩 본인 확인")
private boolean myScrap;
@ApiModelProperty(value = "해당 게시글의 댓글")
private List<PostOneCommentResponse> comments = new ArrayList<>();
@QueryProjection
public PostOneResponse(Long postId, String title, String content, int scrapCount, int commentCount, int likeCount, LocalDateTime createDate, LocalDateTime updateDate, Integer viewCount, String username, boolean myPost, boolean myLike, boolean myScrap) {
this.postId = postId;
this.title = title;
this.content = content;
this.scrapCount = scrapCount;
this.likeCount = likeCount;
this.commentCount = commentCount;
this.createDate = createDate;
this.updateDate = updateDate;
this.viewCount = viewCount;
this.username = username;
this.myPost = myPost;
this.myLike = myLike;
this.myScrap = myScrap;
}
}
@Getter
@Setter
@NoArgsConstructor
public class PostOneCommentResponse {
private Long parentId;
private Long commentId;
private String content;
private String username;
private boolean myComment;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createDate;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime updateDate;
private List<CommentsChildrenResponse> children = new ArrayList<>();
@QueryProjection
public PostOneCommentResponse(Long parentId, Long commentId, String content, String username, boolean myComment, LocalDateTime createDate, LocalDateTime updateDate) {
this.parentId = parentId;
this.commentId = commentId;
this.content = content;
this.username = username;
this.myComment = myComment;
this.createDate = createDate;
this.updateDate = updateDate;
}
}
@Getter
@Setter
@NoArgsConstructor
public class CommentsChildrenResponse {
private Long parentId;
private Long commentId;
private String content;
private String username;
private boolean myComment;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createDate;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime updateDate;
@QueryProjection
public CommentsChildrenResponse(Long parentId, Long commentId, String content, String username, boolean myComment, LocalDateTime createDate, LocalDateTime updateDate) {
this.parentId = parentId;
this.commentId = commentId;
this.content = content;
this.username = username;
this.myComment = myComment;
this.createDate = createDate;
this.updateDate = updateDate;
}
}
N+1の問題が完全に無くなってしまい、前よりもきれいにラッピングされたようです!!🏡
私の知っている限りでは、私が作成した方法のほかに、階層クエリーを作成する方法もいろいろありますが、初めて作成した階層クエリーなので、ちょっと難しい感じがします.
次編では投稿+ファイルアップロード編(
AWS S3
)を作成します.Reference
この問題について(JPA+QueryDSL階層レビュー,レビューの実現(2)), 我々は、より多くの情報をここで見つけました https://velog.io/@do-hoon/JPA-QueryDSL-계층형-댓글-대댓글-구현2テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol