@Override
public Slice<RoomSearchResponseDto> findRoomCustom(RoomSearchRequestDto req, Pageable pageable) {
List<RoomSearchResponseDto> content = jpaQueryFactory.select(Projections.constructor(RoomSearchResponseDto.class,
room.id,
member.memberId,
member.nickname,
room.title,
room.description,
room.link,
room.roomImage,
room.mcount,
room.capacity,
room.isLocked,
room.password,
room.constraints,
room.type)).
from(room)
.innerJoin(member).on(room.manager.memberId.eq(member.memberId))
.leftJoin(roomKeyword).on(roomKeyword.room.eq(room))
.leftJoin(keyword).on(roomKeyword.keyword.eq(keyword))
.where(makeBooleanBuilder(req))
.groupBy(room.id) // group by를 사용하여 중복된 room.id를 제거
.orderBy(makeOrder(req)).
offset(pageable.getOffset()).
limit(pageable.getPageSize() + 1). // 1개를 더 가져온다
fetch();
boolean hasNext = content.size() > pageable.getPageSize(); // 뒤에 더 있는지 확인
content = hasNext ? content.subList(0, pageable.getPageSize()) : content; // 뒤에 더 있으면 1개 더 가져온거 빼고 넘긴다
return new SliceImpl<>(content, pageable, hasNext);
}
private BooleanBuilder makeBooleanBuilder(RoomSearchRequestDto req) {
BooleanBuilder builder = new BooleanBuilder();
if (req.getSearchKeyword() != null) {
builder.andAnyOf(
room.title.contains(req.getSearchKeyword()),
room.description.contains(req.getSearchKeyword())
);
}
if (req.getIsLocked() != null)
builder.and(room.isLocked.eq(req.getIsLocked()));
if (req.getMinMcount() != null || req.getMaxMcount() != null) {
builder.and(room.mcount.between(req.getMinMcount(), req.getMaxMcount()));
}
if (req.getMinCapacity() != null || req.getMaxCapacity() != null) {
builder.and(room.capacity.between(req.getMinCapacity(), req.getMaxCapacity()));
}
if (req.getConstraints() != null && !req.getConstraints().isEmpty())
builder.and(room.constraints.in(req.getConstraints()));
if (req.getType() != null)
builder.and(room.type.eq(req.getType()));
if (req.getKeywordIds() != null && req.getKeywordIds().size() > 0){
builder.and(roomKeyword.keyword.id.in(req.getKeywordIds()));
}
return builder;
}
그런데 원래 Group By는 중복 제거가 아닌 집계 함수를 사용하기 위함이 아닌가?
맞다. 따라서 일반적으로는 **GROUP BY**를 사용할 때는 집계 함수와 함께 사용하며, 집계 함수를 사용하지 않는다면 SELECT 절에 명시된 모든 열은 GROUP BY 절에 포함되어야 한다.
https://kimsyoung.tistory.com/entry/GROUP-BY下-오류문-해결하기
그러나 Querydsl에서 groupby는 어떻게 정확히 동작하는지 모르겠으나 집계함수를 사용하지 않았는데도 제대로 작동이 되었다. 아마 group by 기준을 room 엔티티의 pk로 잡았고, dto의 값들이 모두 이 pk를 기준으로 유일하기 때문에 제대로 작동된 것이 아닐까..! 정확한 것은 더 찾아봐야겠다 ㅠㅠ
지피티짱의 설명
일반적으로는 **
GROUP BY**를 사용할 때는 집계 함수와 함께 사용하며, 집계 함수를 사용하지 않는다면SELECT절에 명시된 모든 열은GROUP BY절에 포함되어야 합니다.만약 **
GROUP BY**를 주 키(primary key)로 사용한다면, 이는 각 행이 고유하게 식별되어 중복이 없다는 것을 의미합니다. 그렇기 때문에 일반적으로 주 키를 **GROUP BY**에 사용하는 것은 중복된 결과를 방지하고 해당 그룹 내에서의 집계 연산을 수행하는데 사용됩니다.따라서 주 키를 **
GROUP BY**에 사용하는 것은 올바른 사용 방법 중 하나입니다. 하지만 각 데이터베이스 시스템의 특정 동작을 이해하고 문서를 참고하여 작성하는 것이 좋습니다.
Spring Data JPA에서 **
distinct**를 사용하려면 특정 필드에 대해 **distinct**를 적용하는 것이 아니라, 특정 엔터티에 대해 **distinct**를 적용합니다. 따라서 **room.id**가 아닌room엔터티에 대한 **distinct**를 적용할 수 있습니다.그러나 “엔티티”에 대해 **
distinct**를 사용하면 JPA는 엔터티의 식별자 (ID)에 대한 **distinct**를 적용하게 됩니다. 이 경우에는 결과 집합에 대해 **distinct**를 사용하는 것이 큰 의미가 없습니다. 특히 **Projections.constructor**를 사용하는 경우 유의 ⇒ (모든 필드를 기준으로 중복제거를 함)
public Slice<RoomSearchResponseDto> findRoomCustom(RoomSearchRequestDto req, Pageable pageable) {
List<RoomSearchResponseDto> content = jpaQueryFactory.select(Projections.constructor(RoomSearchResponseDto.class,
room.id,
member.memberId,
member.nickname,
room.title,
room.description,
room.link,
room.roomImage,
room.mcount,
room.capacity,
room.isLocked,
room.password,
room.constraints,
room.type)).distinct(). // groupBy 대신 distinct로 중복 제거
from(room) // from 이후에 들어가면
.innerJoin(member).on(room.manager.memberId.eq(member.memberId))
.leftJoin(roomKeyword).on(roomKeyword.room.eq(room))
.leftJoin(keyword).on(roomKeyword.keyword.eq(keyword))
.where(makeBooleanBuilder(req))
// .groupBy(room.id)
.orderBy(makeOrder(req)).
offset(pageable.getOffset()).
limit(pageable.getPageSize() + 1). // 1개를 더 가져온다
fetch();
boolean hasNext = content.size() > pageable.getPageSize(); // 뒤에 더 있는지 확인
content = hasNext ? content.subList(0, pageable.getPageSize()) : content; // 뒤에 더 있으면 1개 더 가져온거 빼고 넘긴다
return new SliceImpl<>(content, pageable, hasNext);
}