문제: 1. 조건에 따른 다중 JOIN시, 오류 발생

1) 필터가 적용되지 않았을 때, 기존 NativeQuery의 기능 수행

@Override
    public Page<Meeting> getMeetingsQueryDsl(Double locationLat, Double locationLng, Pageable pageable) {
        QMeeting meeting = QMeeting.meeting;

        List<Meeting> meetingList = queryFactory
                .selectFrom(meeting)
                .orderBy(distanceExpression(locationLat, locationLng).asc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
        Long total = queryFactory
                .select(meeting.count())
                .from(meeting)
                .fetchOne();

        long totalCount = total != null ? total : 0L;

        return new PageImpl<>(meetingList, pageable, totalCount);
    }

    private ComparableExpressionBase<Double> distanceExpression(Double locationLat, Double locationLng) {
        return Expressions.numberTemplate(Double.class,
                "ST_DISTANCE_SPHERE(point({0}, {1}), point(meeting.locationLng, meeting.locationLat))",
                locationLng, locationLat);
    }

2) 1개의 쿼리로 처리하려고 하는 경우,

→ Where 안에 null 값이 들어가는 경우, Query에서 무시

→ join의 경우, null이 들어가면 404

→ .leftjoin을 skill ID 혹은 career ID 가 null / isEmpty()인 경우에도 수행한다면, 이 경우의 수를 분기처리해야 하는 것인지, QueryDsl 기능으로 처리가 가능한지 확인 중

@Override
public Page<Meeting> getMeetingsWithSkillAndCareer(Double locationLat, Double locationLng, List<Long> skillId, List<Long> careerId, Pageable pageable) {
    QMeeting meeting = QMeeting.meeting;
    QMeetingSkill meetingSkill = QMeetingSkill.meetingSkill;
    QMeetingCareer meetingCareer = QMeetingCareer.meetingCareer;

    List<Meeting> meetingList = queryFactory.selectFrom(meeting)
            .leftJoin(meetingSkill).on(meeting.Id.eq(meetingSkill.meeting.Id))
            .leftJoin(meetingCareer).on(meeting.Id.eq(meetingCareer.meeting.Id))
            .where(
                    meetingSkill.meeting.Id.in(skillId),
                    meetingCareer.career.Id.in(careerId)
            )
            .orderBy(distanceExpression(locationLat, locationLng).asc())
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    Long total = queryFactory.select(meeting.count())
            .from(meeting)
            .leftJoin(meetingSkill).on(meeting.Id.eq(meetingSkill.meeting.Id))
            .leftJoin(meetingCareer).on(meeting.Id.eq(meetingCareer.meeting.Id))
            .where(
                    meetingSkill.meeting.Id.in(skillId),
                    meetingCareer.career.Id.in(careerId)
            )
            .fetchOne();

    long totalCount = total != null ? total : 0L;

    return new PageImpl<>(meetingList, pageable, totalCount);
}

→ List<Long> skillId, List<Long> careerId 둘 다 값을 입력하지 않은 경우 ⇒ 404 "errorMessage": "Cannot invoke \\"java.util.Collection.size()\\" because \\"right\\" is null"

❓ ‘동적 쿼리를 제어하기 위한 QueryDsl’에서 Join을 어떻게 제어해야 하는가?

→ 결론: 동적 쿼리에 대한 제어는 Where절 안에서 이루어지는 것이며, Where를 걸기 위한 조건이 Join이라면, 아래의 예제처럼 분기 처리가 필요하다.

(❗Join의 형태가 다양한 경우이므로, 우리 상황과 맞지 않다고 생각됩니다.)

@Override
public List<Tuple> findTestListByCondition(AdminTestListConditionRequest condition) {
    Check check = Check.valueOf(condition.getCheck());
    List<Tuple> result = null;
    switch (check) {
        //CORRECT(테스트 맞은 정보만 가져오기)
        case CORRECT:
            result = queryFactory
                .select(adminSentence.id, adminSentence.korean,
                    adminSentence.english, adminSentence.grammar, adminSentence.situation,
                    adminTestCheck.testCheck)
                .from(adminSentence)
                .where(grammarEq(condition.getGrammar()), situationEq(condition.getSituation()))
                .innerJoin(adminSentence.adminTestChecks, adminTestCheck)
                //이너 조인을 하고 on 조건에
                //해당 userId가 일치하고
                //check 정보가 correct인 정보만 가져오기
                .on(adminTestCheck.testCheck.eq(check),
                    adminTestCheck.users.id.eq(condition.getUserId()))
                .fetch();
            break;
        //NO(맞거나 틀리거나 테스트보지않았거나 - 모든 정보 가져오기)
        case NO:
            result = queryFactory
                .select(adminSentence.id, adminSentence.korean,
                    adminSentence.english, adminSentence.grammar, adminSentence.situation,
                    adminTestCheck.testCheck)
                .from(adminSentence)
                .where(grammarEq(condition.getGrammar()), situationEq(condition.getSituation()))
                //left outer join으로 
			//userId가 일치하는 모든 문장 가져온다.
                .leftJoin(adminSentence.adminTestChecks, adminTestCheck)
                .on(adminTestCheck.users.id.eq(condition.getUserId()))
                .fetch();
            break;
        //WRONG(틀리거나 테스트보지않은 정보만 가져오기)
        case WRONG:
            result = queryFactory
                .select(adminSentence.id, adminSentence.korean,
                    adminSentence.english, adminSentence.grammar, adminSentence.situation,
                    adminTestCheck.testCheck)
                .from(adminSentence)
                .where(grammarEq(condition.getGrammar()), situationEq(condition.getSituation())
                        , adminTestCheck.isNull().or(adminTestCheck.testCheck.eq(Check.WRONG))
                )
                //left outer join으로 check 정보가 없는 문장도 전부
                //가져오는데, 
                //가져온 정보 중에 where 조건 절로 check 정보가 CORRECT가 
                //아닌 정보만 가져오게 설정한다. 
                .leftJoin(adminSentence.adminTestChecks, adminTestCheck)
                .on(adminTestCheck.users.id.eq(condition.getUserId()))
                .fetch();
            break;
    }
    return result;
}

인프런 QueryDsl 질문 中

  1. QueryDsl을 활용하여, 동적쿼리를 제어하는 상황
  2. Where 조건에 따라, 동적 Join 적용에 대한 질문

Untitled

원인