-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] hibernate filter 적용 #287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f7ed1f0
5daea4c
68c759c
9633f59
ee75622
50bd3e7
89af422
64420fd
f77a03b
e6e72c4
8604465
4f352bc
3b4fcec
7926b2a
9f4e761
487a4ed
60be983
f35b1e1
94158ca
47947a8
08bcd8b
622cf9a
ea0f36b
7a872b4
5fc2dcf
65c387e
42ca451
11425df
dc34e9a
04bc4c5
b9dd16f
2e22e6d
16756f2
6cd777f
f787539
8c20e29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,18 +39,21 @@ public class CommentJpaEntity extends BaseJpaEntity { | |
| private int likeCount = 0; | ||
|
|
||
| //TODO 상속구조 해지하면서 postType만 가질지, postId + postType가질지 논의 필요 | ||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
| @JoinColumn(name = "post_id", nullable = false) | ||
| private PostJpaEntity postJpaEntity; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| @Column(name = "post_type", nullable = false, length = 10) | ||
| private PostType postType; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
| @JoinColumn(name = "user_id", nullable = false) | ||
| private UserJpaEntity userJpaEntity; | ||
|
|
||
| /** | ||
| * nullable = true : 최상위 댓글인 경우 null | ||
| */ | ||
|
Comment on lines
+54
to
+56
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LGTM |
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "parent_id") | ||
| private CommentJpaEntity parent; | ||
|
Comment on lines
+54
to
59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 상위 댓글 필터링 시 ObjectNotFound 위험 — 매핑에 NotFound/nullable/optional 명시 필요 Hibernate Filter로 부모가 INACTIVE인 경우 지연로딩 시 부모 조회가 필터에 걸려 예외가 날 수 있습니다. - /**
- * nullable = true : 최상위 댓글인 경우 null
- */
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "parent_id")
+ /**
+ * nullable = true : 최상위 댓글인 경우 null
+ * 부모가 필터로 제외될 수 있으므로 NotFound IGNORE로 방어
+ */
+ @NotFound(action = NotFoundAction.IGNORE)
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ @JoinColumn(name = "parent_id", nullable = true)
private CommentJpaEntity parent;아래 import 추가가 필요합니다. import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 프로젝트는 헥사고날 아키텍처를 따르고 있어서 jpa entity와 도메인 entity를 분리해 사용하고 있으므로, Jpa entity 필드의 지연로딩이 발생하는 유스케이스는 없어. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @seongjunnoh, 완전히 맞는 말씀입니다! 헥사고날 아키텍처에서 JPA entity와 domain entity를 명확히 분리하고 계시는군요. JPA entity가 항상 domain entity로 매핑된 후 어플리케이션 레이어에서는 도메인 엔티티만 사용하시니, 제가 우려했던 지연로딩 시 ObjectNotFound 예외 상황은 발생할 수 없습니다. 특히 comment domain entity에서 parent를 Long ID로만 참조하고 계시니, parent가 inactive이거나 없는 경우에도 단순히 null로 매핑되어 안전하게 처리되겠네요. 아키텍처 패턴을 고려하지 못한 제안이었습니다. 명확한 설명 감사합니다! ✏️ Learnings added
🧠 Learnings used |
||
|
|
@@ -71,5 +74,4 @@ public CommentJpaEntity updateFrom(Comment comment) { | |
| public void updateLikeCount(int likeCount) { | ||
| this.likeCount = likeCount; | ||
| } | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,17 +15,24 @@ | |||||||||||||||||||||||||||||
| import java.util.*; | ||||||||||||||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import static konkuk.thip.common.entity.StatusType.ACTIVE; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| @Repository | ||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||
| public class CommentQueryRepositoryImpl implements CommentQueryRepository { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||
| * 댓글 관련 queryDsl 코드에서는 status 값 명시해야함 (서비스 메서드에서 status filter off가 전제) | ||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final JPAQueryFactory queryFactory; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| private final QCommentJpaEntity comment = QCommentJpaEntity.commentJpaEntity; | ||||||||||||||||||||||||||||||
| private final QUserJpaEntity commentCreator = QUserJpaEntity.userJpaEntity; | ||||||||||||||||||||||||||||||
| private final QCommentJpaEntity parentComment = new QCommentJpaEntity("parentComment"); | ||||||||||||||||||||||||||||||
| private final QUserJpaEntity parentCommentCreator = new QUserJpaEntity("parentCommentCreator"); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // 최상위 댓글 조회 (삭제된 댓글 포함, 최신순, 페이징) | ||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||
| public List<CommentQueryDto> findRootCommentsWithDeletedByCreatedAtDesc(Long postId, String postTypeStr, LocalDateTime lastCreatedAt, int size) { | ||||||||||||||||||||||||||||||
| // 최상위 댓글(size+1) 프로젝션 생성 | ||||||||||||||||||||||||||||||
|
|
@@ -44,6 +51,7 @@ public List<CommentQueryDto> findRootCommentsWithDeletedByCreatedAtDesc(Long pos | |||||||||||||||||||||||||||||
| BooleanExpression whereClause = comment.postJpaEntity.postId.eq(postId) | ||||||||||||||||||||||||||||||
| .and(comment.postJpaEntity.dtype.eq(postTypeStr)) // dType 필터링 추가 | ||||||||||||||||||||||||||||||
| .and(comment.parent.isNull()) // 게시글의 최상위 댓글 조회 | ||||||||||||||||||||||||||||||
| .and(commentCreator.status.eq(ACTIVE)) // 댓글 작성자 ACTIVE | ||||||||||||||||||||||||||||||
| .and(lastCreatedAt != null // 최신순 정렬 | ||||||||||||||||||||||||||||||
| ? comment.createdAt.lt(lastCreatedAt) | ||||||||||||||||||||||||||||||
| : Expressions.TRUE | ||||||||||||||||||||||||||||||
|
|
@@ -53,7 +61,7 @@ public List<CommentQueryDto> findRootCommentsWithDeletedByCreatedAtDesc(Long pos | |||||||||||||||||||||||||||||
| return queryFactory | ||||||||||||||||||||||||||||||
| .select(proj) | ||||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .join(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .where(whereClause) | ||||||||||||||||||||||||||||||
| .orderBy(comment.createdAt.desc()) | ||||||||||||||||||||||||||||||
| .limit(size + 1) // size + 1 개 조회 | ||||||||||||||||||||||||||||||
|
|
@@ -89,10 +97,11 @@ public List<CommentQueryDto> findAllActiveChildCommentsByCreatedAtAsc(Long rootC | |||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||
| .leftJoin(comment.parent, parentComment) | ||||||||||||||||||||||||||||||
| .leftJoin(parentComment.userJpaEntity, parentCommentCreator) | ||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .join(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||
| comment.parent.commentId.in(parentIds), // parentIds 하위의 모든 자식 댓글 조회 | ||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE) // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||
| comment.status.eq(ACTIVE), // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||
| commentCreator.status.eq(ACTIVE) // 자식 댓글 작성자 ACTIVE | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
Comment on lines
+100
to
105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain자식 댓글 조회 시 부모 상태 필터 미적용 — findChildCommentId와 불일치 여기서는 자식 댓글과 작성자만 ACTIVE를 강제하지만, 아래 findChildCommentId에서는 부모도 ACTIVE를 요구합니다. “삭제된 부모 아래 자식 노출 정책”을 일관화해 주세요. 가능한 두 옵션:
- .where(
- comment.parent.commentId.in(parentIds),
- comment.status.eq(ACTIVE),
- commentCreator.status.eq(ACTIVE)
- )
+ .where(
+ comment.parent.commentId.in(parentIds),
+ parentComment.status.eq(ACTIVE),
+ comment.status.eq(ACTIVE),
+ commentCreator.status.eq(ACTIVE)
+ )
- parentComment.status.eq(ACTIVE),
comment.status.eq(ACTIVE),의도에 맞는 방향으로 선택 부탁드립니다. 🏁 Script executed: #!/bin/bash
# findChildCommentId 메서드의 쿼리에서 부모 상태 필터 적용 여부 확인
rg -n "findChildCommentId" -C5 srcLength of output: 3525 🏁 Script executed: #!/bin/bash
# findChildCommentId 메서드의 부모 상태 필터 적용 여부 확인 (join 및 where 절)
sed -n '200,260p' src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentQueryRepositoryImpl.javaLength of output: 1686 자식 댓글 조회 시 부모 상태(ACTIVE) 필터 누락 — 일관성 유지 필요 .where(
- comment.parent.commentId.in(parentIds),
+ comment.parent.commentId.in(parentIds),
+ parentComment.status.eq(ACTIVE), // 부모도 ACTIVE인 경우만 조회
comment.status.eq(ACTIVE),
commentCreator.status.eq(ACTIVE)
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위 메서드는 부모 댓글의 status와 관계없이 모든 active 상태인 자식 댓글들을 조회하는 메서드야. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @seongjunnoh 비즈니스 요구사항에 대한 명확한 설명 감사드립니다! 삭제된 부모 댓글 하위의 활성 자식 댓글도 표시해야 한다는 정책을 이해했습니다. 이 경우 현재 구현이 요구사항에 맞게 올바르게 작동하고 있네요. 향후 리뷰에서 이런 도메인별 정책을 고려하도록 하겠습니다. ✏️ Learnings added
🧠 Learnings used |
||||||||||||||||||||||||||||||
| .fetch(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -148,10 +157,11 @@ public Map<Long, List<CommentQueryDto>> findAllActiveChildCommentsByCreatedAtAsc | |||||||||||||||||||||||||||||
| .from(comment) | ||||||||||||||||||||||||||||||
| .leftJoin(comment.parent, parentComment) | ||||||||||||||||||||||||||||||
| .leftJoin(parentComment.userJpaEntity, parentCommentCreator) | ||||||||||||||||||||||||||||||
| .leftJoin(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .join(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||
| comment.parent.commentId.in(parentIds), // parentIds 하위의 모든 자식 댓글 조회 | ||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE) // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||
| comment.status.eq(ACTIVE), // 자식 댓글은 ACTIVE인 것만 조회 | ||||||||||||||||||||||||||||||
| commentCreator.status.eq(ACTIVE) // 자식 댓글 작성자 ACTIVE | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| .fetch(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -196,7 +206,7 @@ public CommentQueryDto findRootCommentId(Long rootCommentId) { | |||||||||||||||||||||||||||||
| .join(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||
| comment.commentId.eq(rootCommentId), | ||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE) | ||||||||||||||||||||||||||||||
| comment.status.eq(ACTIVE) | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| .fetchOne(); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
@@ -225,8 +235,8 @@ public CommentQueryDto findChildCommentId(Long rootCommentId, Long replyCommentI | |||||||||||||||||||||||||||||
| .join(comment.userJpaEntity, commentCreator) | ||||||||||||||||||||||||||||||
| .where( | ||||||||||||||||||||||||||||||
| comment.parent.commentId.eq(rootCommentId), | ||||||||||||||||||||||||||||||
| parentComment.status.eq(StatusType.ACTIVE), | ||||||||||||||||||||||||||||||
| comment.status.eq(StatusType.ACTIVE), | ||||||||||||||||||||||||||||||
| parentComment.status.eq(ACTIVE), | ||||||||||||||||||||||||||||||
| comment.status.eq(ACTIVE), | ||||||||||||||||||||||||||||||
| comment.commentId.eq(replyCommentId) | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| .fetchOne(); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,10 +7,12 @@ | |||||||||||||||||
| import konkuk.thip.comment.application.port.out.CommentLikeQueryPort; | ||||||||||||||||||
| import konkuk.thip.comment.application.port.out.CommentQueryPort; | ||||||||||||||||||
| import konkuk.thip.comment.application.port.out.dto.CommentQueryDto; | ||||||||||||||||||
| import konkuk.thip.common.annotation.persistence.Unfiltered; | ||||||||||||||||||
| import konkuk.thip.common.util.Cursor; | ||||||||||||||||||
| import konkuk.thip.common.util.CursorBasedList; | ||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.util.*; | ||||||||||||||||||
| import java.util.stream.Collectors; | ||||||||||||||||||
|
|
@@ -25,6 +27,8 @@ public class CommentShowAllService implements CommentShowAllUseCase { | |||||||||||||||||
| private final CommentQueryMapper commentQueryMapper; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| @Transactional(readOnly = true) | ||||||||||||||||||
| @Unfiltered | ||||||||||||||||||
| public CommentForSinglePostResponse showAllCommentsOfPost(CommentShowAllQuery query) { | ||||||||||||||||||
|
Comment on lines
+30
to
32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion @unfiltered로 자식 댓글까지 비활성 포함되는 부작용 발생 메서드 단위 @unfiltered는 전체 트랜잭션에서 필터를 꺼서, 이름상 ‘findAllActiveChildCommentsOldestFirst’까지 INACTIVE를 포함시킬 위험이 큽니다. 요구사항은 “루트는 삭제 포함 조회, 자식은 ACTIVE만”으로 보입니다. 서비스에서 @unfiltered를 제거하고, “루트 댓글 조회”를 수행하는 어댑터/포트 메서드에만 @unfiltered를 부여해 범위를 축소하세요. @Override
- @Transactional(readOnly = true)
- @Unfiltered
+ @Transactional(readOnly = true)
public CommentForSinglePostResponse showAllCommentsOfPost(CommentShowAllQuery query) {추가로, 루트 조회용 포트/리포지토리 메서드(예: findLatestRootCommentsWithDeleted)에 @unfiltered를 선언하거나, 어댑터 내부에서 일시적으로 필터 on/off를 제어하는 구현을 분리하는 것을 권장합니다(같은 빈 내 self-invocation 주의). 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 댓글 조회 서비스 메서드의 하위 동작에서는 status filter를 끄고, 대신 하위 영속성 메서드에 명시적으로 요구사항에 맞는 status 조건을 붙이는 식으로 구현했으므로 문제될 건 없다고 생각해. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||
| Cursor cursor = Cursor.from(query.cursorStr(), PAGE_SIZE); | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package konkuk.thip.common.annotation; | ||
| package konkuk.thip.common.annotation.application; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굿 패키지 분리 좋습니다 |
||
|
|
||
| import org.springframework.core.annotation.AliasFor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package konkuk.thip.common.annotation.persistence; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target({ElementType.METHOD, ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface IncludeInactive { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package konkuk.thip.common.annotation.persistence; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Target({ElementType.METHOD, ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface Unfiltered { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굿굿 반영 감삼다