Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@
import java.util.List;

@Entity
@Table(name = "feeds")
//@Table(name = "feeds")
@DiscriminatorValue("FEED")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FeedJpaEntity extends PostJpaEntity {

@Column(name = "is_public", nullable = false)
@Column(name = "is_public")
private Boolean isPublic;

@Column(name = "report_count", nullable = false)
@Column(name = "report_count")
private int reportCount = 0;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", nullable = false)
@JoinColumn(name = "book_id")
Comment on lines -25 to +32
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳

private BookJpaEntity bookJpaEntity;

@OneToMany(mappedBy = "postJpaEntity", cascade = CascadeType.ALL, orphanRemoval = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@Entity
@Table(name = "posts")
@Getter
@Inheritance(strategy = InheritanceType.JOINED)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DiscriminatorColumn(name = "dtype")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class PostJpaEntity extends BaseJpaEntity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@
import lombok.NoArgsConstructor;

@Entity
@Table(name = "records")
//@Table(name = "records")
@DiscriminatorValue("RECORD")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class RecordJpaEntity extends PostJpaEntity {

@Column(name = "page")
private Integer page;

@Column(name = "is_overview",nullable = false)
private boolean isOverview;
@Column(name = "is_overview")
private Boolean isOverview;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "room_id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
import lombok.NoArgsConstructor;

@Entity
@Table(name = "votes")
//@Table(name = "votes")
@DiscriminatorValue("VOTE")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class VoteJpaEntity extends PostJpaEntity {

@Column(name = "page")
private Integer page;

@Column(name = "is_overview",nullable = false)
@Column(name = "is_overview")
private boolean isOverview;

@ManyToOne(fetch = FetchType.LAZY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public Record toDomainEntity(RecordJpaEntity recordJpaEntity) {
.content(recordJpaEntity.getContent())
.creatorId(recordJpaEntity.getUserJpaEntity().getUserId())
.page(recordJpaEntity.getPage())
.isOverview(recordJpaEntity.isOverview())
.isOverview(recordJpaEntity.getIsOverview())
.roomId(recordJpaEntity.getRoomJpaEntity().getRoomId())
.likeCount(recordJpaEntity.getLikeCount())
.commentCount(recordJpaEntity.getCommentCount())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.time.LocalDateTime;
import java.util.List;

import static com.querydsl.jpa.JPAExpressions.treat;
import static konkuk.thip.post.domain.PostType.RECORD;
import static konkuk.thip.post.domain.PostType.VOTE;

Expand All @@ -31,8 +32,6 @@ public class RecordQueryRepositoryImpl implements RecordQueryRepository {
private final JPAQueryFactory queryFactory;

private final QPostJpaEntity post = QPostJpaEntity.postJpaEntity;
private final QRecordJpaEntity record = QRecordJpaEntity.recordJpaEntity;
private final QVoteJpaEntity vote = QVoteJpaEntity.voteJpaEntity;
private final QUserJpaEntity user = QUserJpaEntity.userJpaEntity;

@Override
Expand All @@ -47,8 +46,6 @@ public List<RoomPostQueryDto> findMyRecords(Long roomId, Long userId, Cursor cur
return queryFactory
.select(selectPostQueryDto())
.from(post)
.leftJoin(record).on(post.postId.eq(record.postId))
.leftJoin(vote).on(post.postId.eq(vote.postId))
.join(post.userJpaEntity, user)
.where(where)
.orderBy(getOrderSpecifiers(roomPostSortType))
Expand All @@ -59,13 +56,13 @@ public List<RoomPostQueryDto> findMyRecords(Long roomId, Long userId, Cursor cur
private BooleanBuilder buildMyRecordCondition(Long roomId, Long userId) {
BooleanBuilder where = new BooleanBuilder();

BooleanBuilder voteCondition = new BooleanBuilder();
voteCondition.and(post.dtype.eq(VOTE.getType()))
.and(vote.roomJpaEntity.roomId.eq(roomId));
BooleanBuilder voteCondition = new BooleanBuilder()
.and(post.dtype.eq(VOTE.getType()))
.and(treat(post, QVoteJpaEntity.class).roomJpaEntity.roomId.eq(roomId));
Comment on lines -62 to +61
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 이렇게 jpa treat 로 post, record, vote jpa entity 간의 명시적인 left join을 없앨 수 있군요!! 좋습니다!
성능이 한층 개선될 것 같네요!!


BooleanBuilder recordCondition = new BooleanBuilder();
recordCondition.and(post.dtype.eq(RECORD.getType()))
.and(record.roomJpaEntity.roomId.eq(roomId));
BooleanBuilder recordCondition = new BooleanBuilder()
.and(post.dtype.eq(RECORD.getType()))
.and(treat(post, QRecordJpaEntity.class).roomJpaEntity.roomId.eq(roomId));

where.and(voteCondition.or(recordCondition))
.and(post.userJpaEntity.userId.eq(userId))
Expand All @@ -84,8 +81,6 @@ public List<RoomPostQueryDto> findGroupRecordsOrderBySortType(Long roomId, Long
return queryFactory
.select(selectPostQueryDto())
.from(post)
.leftJoin(record).on(post.postId.eq(record.postId))
.leftJoin(vote).on(post.postId.eq(vote.postId))
.join(post.userJpaEntity, user)
.where(where)
.orderBy(getOrderSpecifiers(roomPostSortType))
Expand All @@ -96,26 +91,28 @@ public List<RoomPostQueryDto> findGroupRecordsOrderBySortType(Long roomId, Long
private BooleanBuilder buildRecordVoteCondition(Long roomId, Integer pageStart, Integer pageEnd, Boolean isOverview) {
BooleanBuilder where = new BooleanBuilder();

BooleanBuilder voteCondition = new BooleanBuilder();
voteCondition.and(post.dtype.eq(VOTE.getType()))
.and(vote.roomJpaEntity.roomId.eq(roomId));
// VOTE
BooleanBuilder voteCondition = new BooleanBuilder()
.and(post.dtype.eq(VOTE.getType()))
.and(treat(post, QVoteJpaEntity.class).roomJpaEntity.roomId.eq(roomId));

if (isOverview) {
voteCondition.and(vote.isOverview.isTrue());
voteCondition.and(treat(post, QVoteJpaEntity.class).isOverview.isTrue());
} else {
voteCondition.and(vote.isOverview.isFalse())
.and(vote.page.between(pageStart, pageEnd));
voteCondition.and(treat(post, QVoteJpaEntity.class).isOverview.isFalse())
.and(treat(post, QVoteJpaEntity.class).page.between(pageStart, pageEnd));
}

BooleanBuilder recordCondition = new BooleanBuilder();
recordCondition.and(post.dtype.eq(RECORD.getType()))
.and(record.roomJpaEntity.roomId.eq(roomId));
// RECORD
BooleanBuilder recordCondition = new BooleanBuilder()
.and(post.dtype.eq(RECORD.getType()))
.and(treat(post, QRecordJpaEntity.class).roomJpaEntity.roomId.eq(roomId));

if (isOverview) {
recordCondition.and(record.isOverview.isTrue());
recordCondition.and(treat(post, QRecordJpaEntity.class).isOverview.isTrue());
} else {
recordCondition.and(record.isOverview.isFalse())
.and(record.page.between(pageStart, pageEnd));
recordCondition.and(treat(post, QRecordJpaEntity.class).isOverview.isFalse())
.and(treat(post, QRecordJpaEntity.class).page.between(pageStart, pageEnd));
}

where.and(voteCondition.or(recordCondition))
Expand All @@ -126,16 +123,20 @@ private BooleanBuilder buildRecordVoteCondition(Long roomId, Integer pageStart,
// Case: pageExpr (Record, Vote 분기)
private NumberExpression<Integer> pageExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType())).then(record.page)
.when(post.dtype.eq(VOTE.getType())).then(vote.page)
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class).page)
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class).page)
.otherwise(0);
}
Comment on lines 123 to 131
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

pageExpr가 null을 반환할 수 있어 정렬/커서 비교가 깨집니다 — coalesce(0) 적용 권장

page가 Integer(Nullable)로 변경되었기 때문에 현재 구현은 null을 그대로 반환합니다. ORDER BY와 키셋 커서 비교(lt/eq)에서 null은 3값 논리로 처리되어 페이지네이션이 불안정해질 수 있습니다. 0으로 정규화(coalesce)해 주세요.

                 return new CaseBuilder()
-                .when(post.dtype.eq(RECORD.getType()))
-                .then(treat(post, QRecordJpaEntity.class).page)
-                .when(post.dtype.eq(VOTE.getType()))
-                .then(treat(post, QVoteJpaEntity.class).page)
+                .when(post.dtype.eq(RECORD.getType()))
+                .then(treat(post, QRecordJpaEntity.class).page.coalesce(0))
+                .when(post.dtype.eq(VOTE.getType()))
+                .then(treat(post, QVoteJpaEntity.class).page.coalesce(0))
                 .otherwise(0);

이 개선은 select DTO의 page 값과 Cursor.getInteger(…) 처리에서도 NPE/불일치 위험을 줄여줍니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Case: pageExpr (Record, Vote 분기)
private NumberExpression<Integer> pageExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType())).then(record.page)
.when(post.dtype.eq(VOTE.getType())).then(vote.page)
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class).page)
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class).page)
.otherwise(0);
}
// Case: pageExpr (Record, Vote 분기)
private NumberExpression<Integer> pageExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class).page.coalesce(0))
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class).page.coalesce(0))
.otherwise(0);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/record/RecordQueryRepositoryImpl.java
around lines 123 to 131, the CaseBuilder can yield null for page (since page is
now nullable) which breaks ORDER BY and keyset cursor comparisons; wrap the case
expression with a coalesce returning 0 so nulls are normalized. Concretely:
build the CaseBuilder as you have, assign it to a NumberExpression<Integer>
variable, then return a coalesced expression that returns 0 when the case result
is null (e.g., use QueryDSL/Expressions.coalesce(caseExpr, 0) or equivalent API
in your codebase) so pagination comparisons never see null.


// Case: isOverviewExpr (총평 여부를 정렬 기준으로 사용)
private NumberExpression<Integer> isOverviewExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType())).then(record.isOverview.castToNum(Integer.class))
.when(post.dtype.eq(VOTE.getType())).then(vote.isOverview.castToNum(Integer.class))
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class).isOverview.castToNum(Integer.class))
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class).isOverview.castToNum(Integer.class))
.otherwise(0);
}
Comment on lines 133 to 141
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

isOverviewExpr도 null 가능성이 있어 비교/정렬이 불안정합니다 — coalesce(0) 적용

Boolean(Nullable) → Number로 cast한 뒤 null이 그대로 유지됩니다. eq/lt 비교와 ORDER BY에서 3값 논리로 변질되므로 0으로 coalesce 하는 편이 안전합니다.

         return new CaseBuilder()
-                .when(post.dtype.eq(RECORD.getType()))
-                .then(treat(post, QRecordJpaEntity.class).isOverview.castToNum(Integer.class))
-                .when(post.dtype.eq(VOTE.getType()))
-                .then(treat(post, QVoteJpaEntity.class).isOverview.castToNum(Integer.class))
+                .when(post.dtype.eq(RECORD.getType()))
+                .then(treat(post, QRecordJpaEntity.class).isOverview.castToNum(Integer.class).coalesce(0))
+                .when(post.dtype.eq(VOTE.getType()))
+                .then(treat(post, QVoteJpaEntity.class).isOverview.castToNum(Integer.class).coalesce(0))
                 .otherwise(0);

이 변경은 selectPostQueryDto의 isOverviewExpr().eq(1)에서도 null 비교를 피하게 되어 투영 시 Boolean/boolean 매핑 문제를 예방합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Case: isOverviewExpr (총평 여부를 정렬 기준으로 사용)
private NumberExpression<Integer> isOverviewExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType())).then(record.isOverview.castToNum(Integer.class))
.when(post.dtype.eq(VOTE.getType())).then(vote.isOverview.castToNum(Integer.class))
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class).isOverview.castToNum(Integer.class))
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class).isOverview.castToNum(Integer.class))
.otherwise(0);
}
// Case: isOverviewExpr (총평 여부를 정렬 기준으로 사용)
private NumberExpression<Integer> isOverviewExpr() {
return new CaseBuilder()
.when(post.dtype.eq(RECORD.getType()))
.then(treat(post, QRecordJpaEntity.class)
.isOverview
.castToNum(Integer.class)
.coalesce(0))
.when(post.dtype.eq(VOTE.getType()))
.then(treat(post, QVoteJpaEntity.class)
.isOverview
.castToNum(Integer.class)
.coalesce(0))
.otherwise(0);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/roompost/adapter/out/persistence/repository/record/RecordQueryRepositoryImpl.java
around lines 133 to 141, the CaseBuilder expression returns nullable Integer
(from casting Boolean) which leaves nulls in comparisons and ORDER BY; update
the expression to wrap the cast result with a coalesce(0) so any null becomes 0
(apply coalesce to each then-branch result or to the whole case result) to
ensure stable eq/lt checks and ordering and to avoid null projection when
calling isOverviewExpr().eq(1).


Expand Down