Users 테이블과 EduContents는 참조하고 있는 컬럼이 하나도 없는 상태이다.
Users 테이블과 EduInfo 테이블은 Users의 id를 EduInfo의 userId가 참조하고 있는 상태이다.
이때 Users를 조회하면서 EduContents의 전체 개수와, EduInfo의 개수를 가져오고 싶을 때 어떻게 해야할까?
(참조하는 컬럼이 전혀 없는 테이블의 count값과 참조하는 컬럼이 있는 테이블의 count값을 join 없이 가져오는 방법)
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.vladmihalcea.hibernate.util.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.querydsl.core.group.GroupBy.groupBy;
import static com.querydsl.core.group.GroupBy.list;
@Repository
@RequiredArgsConstructor
public class UserReaderCustomRepositoryImpl implements UserReaderCustomRepository {
private final JPAQueryFactory queryFactory;
private final QUsers users = QUsers.users;
private final QEduInfo eduInfo = QEduInfo.eduInfo;
private final QEduContents eduContents = QEduContents.eduContents;
private final QSurveyAnswer surveyAnswer = QSurveyAnswer.surveyAnswer;
private final EduContentsRepository eduContentsRepository;
/**
* 관리자 > 교육 진행률 목록 조회
*/
@Override
public Page<EduReaderListDTO> findEduAllList(EduReaderListRequest eduListReqeust) {
Pageable pageable = new PagingRequest(eduListReqeust.getPage(), eduListReqeust.getSize()).of();
long size = queryFactory
.select(users.id)
.from(users)
.where(
users.name.isNotNull(),
containsNameOrTelSearch(eduListReqeust.getNameOrTel()),
eqRegion(eduListReqeust.getUserRegion()),
eqGender(eduListReqeust.getUserGender()),
isEduCompleted(eduListReqeust.getIsCompleted())
) // 위에도 조건 안달아주면 실제로 나오는 객체 개수(아래)랑 다른 개수가 나온다.
.fetch().size();
List<EduReaderListDTO> eduInfoList = queryFactory
.select(Projections.constructor(EduReaderListDTO.class,
users.id,
users.name,
users.region,
users.birth,
users.gender,
users.tel,
users.percentage,
eduInfo.id.countDistinct().as("eduInProgress"), // 대상자가 진행중인 교육 개수
eduContents.countDistinct().as("wholeEduContents")// 전체 교육 개수
))
.from(users, eduContents)
.leftJoin(eduInfo).on(eduInfo.userId.eq(users.id))
.where(
users.name.isNotNull(),
containsNameOrTelSearch(eduListReqeust.getNameOrTel()),
eqRegion(eduListReqeust.getUserRegion()),
eqGender(eduListReqeust.getUserGender()),
isEduCompleted(eduListReqeust.getIsCompleted())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(users.id.desc())
.fetch();
return new PageImpl<>(eduInfoList, pageable, size);
}
이렇게 하니까 원하는 대로 나오지 않았다! 만약 eduInfo(현재 진행중인 교육콘텐츠)가 없다하더라도 객체는 나와줘야 하는데 ,
.leftJoin(eduInfo).on(eduInfo.userId.eq(users.id))
때문에 left outer join을 해도 왜그런지 한 객체만 나왔다... 위 코드를 주석처리하고 포스트맨으로 해보니까 그 객체들의 eduInProgress
의 값과 wholeEduContents
의 값이 고정되어 들어가는 문제가 있었다. (게다가 countDistinct()
를 count()
로 쓰니까 row값 * 객체값이 통째로 들어갔으니 각 객체에 테이블의 row값을 넣어줄거면 countDistinct()
쓰기)
eduInfo.id.countDistinct().as("eduInProgress"), // 예) user1의 값 = 2
eduContents.countDistinct().as("wholeEduContents")// 예) 12
user2를 검색해줘도 2, 12가 들어갔다. 12는 다 공통값이니까 그렇다 쳐도 user2는 0이 나와야하는데 2가 들어가는 상황.
그러다가 Join없이 할 수 있는 방법을 Chat GPT에 물어봤더니 알려줬다 ^^...짜식...
@Override
public Page<EduReaderListDTO> findEduAllList(EduReaderListRequest eduListReqeust) {
Pageable pageable = new PagingRequest(eduListReqeust.getPage(), eduListReqeust.getSize()).of();
long size = queryFactory
.select(users.id)
.from(users)
.where(
users.name.isNotNull(),
containsNameOrTelSearch(eduListReqeust.getNameOrTel()),
eqRegion(eduListReqeust.getUserRegion()),
eqGender(eduListReqeust.getUserGender()),
isEduCompleted(eduListReqeust.getIsCompleted())
)
.fetch().size();
List<EduReaderListDTO> eduInfoList = queryFactory
.select(Projections.constructor(EduReaderListDTO.class,
users.id,
users.name,
users.region,
users.birth,
users.gender,
users.tel,
users.percentage.coalesce(0), // 프론트 요청에 따라 percentage가 null이면 .coalesce()로 기초값 설정
JPAExpressions.select(eduInfo.countDistinct()).from(eduInfo).where(eduInfo.userId.eq(users.id)), // 이부분!!
JPAExpressions.select(eduContents.countDistinct()).from(eduContents) // 이부분!!
))
.from(users)
.where(
users.name.isNotNull(),
containsNameOrTelSearch(eduListReqeust.getNameOrTel()),
eqRegion(eduListReqeust.getUserRegion()),
eqGender(eduListReqeust.getUserGender()),
isEduCompleted(eduListReqeust.getIsCompleted())
)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(users.id.desc())
.fetch();
return new PageImpl<>(eduInfoList, pageable, size);
}
방법은 JPAExpressions을 사용해서 쿼리문을 붙이는 거였다.
이렇게 하면 제일 상위 select문에 join을 걸지 않아도되고, where 조건을 통해 해당 user에게 맞는 eduInfo 정보가 각 각 잘 들어간다.
또한, 연관관계가 아예없는, 참조 컬럼도 없는 테이블도 where 조건을 쓰지 않은채로 사용 하니까 값이 잘 들어갔다.
참고로 프론트에서 users.percentage가 null로 나오는데 0으로 보내주길 바랐기 떄문에 .coalesce(0)
으로 null일 때의 기초값을 0으로 설정해줬다.