JPA/QueryDSL

[QueryDSL] QueryDSL 동작원리(3) - fetch

IT록흐 2023. 8. 2. 15:54
반응형

 

 

 

QueryDSL은 JPQL 생성 및 실행 권한을 자신에게 위임하여, 개발자가 JPQL을 직접 작성했을 때 발생하는 타입 안정성 체크의 어려움이나 동적 쿼리생성 문제를 해결할 수 있다. 개발자는 그저 JPQL 생성을 위한 메타데이터만 설정하면 되고 QueryDSL은 유연한 데이터 설정을 위해 빌더패턴 구조로 이루어져 있다. 여기까지 지난 포스팅에서 알아본 내용이다. 

 

 

 

[QueryDSL] QueryDSL 동작원리(1) - 빌더패턴

JPA에서 개발자가 원하는 엔티티를 얻으려면, JPQL을 작성하고 이를 EntityManager로 실행해야 한다. 이때 한 가지가 문제가 있는데, JPQL이 문자열이라는 점이다. JPQL이 문자열이기에 타입안정성 체크

lordofkangs.tistory.com

 

[QueryDSL] QueryDSL 동작원리(2) - 메타데이터 설정하기

[QueryDSL] QueryDSL 동작원리(1) - 빌더패턴 JPA에서 개발자가 원하는 엔티티를 얻으려면, JPQL을 작성하고 이를 EntityManager로 실행해야 한다. 이때 한 가지가 문제가 있는데, JPQL이 문자열이라는 점이다.

lordofkangs.tistory.com

 

이번 포스팅에서는 설정된 메타데이터를 가지고 JPQL을 생성및 실행하는 과정을 다루어 보겠다. 

 

public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final JPAQueryFactory queryFactory;

    public MemberRepositoryImpl(EntityManager entityManager){
        this.queryFactory = new JPAQueryFactory(entityManager); // EntityManager 주입하기
    }

    public List<Member> searchMember(MemberSearchCondition condition){
        QMember qMember = QMember.member;
        return queryFactory
                //JPQL 메타데이터 설정 시작
                .select(qMember) //select절 데이터
                .from(qMember) // from절 데이터
                .leftJoin(member.team, team) // leftJoin절 데이터
                .where( //where절 데이터
                        usernameEq(condition.getUsername()), 
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )         
                // JPQL 메타데이터 설정 끝
                // JPQL 생성 및 실행 시작
                .fetch();
     }
}

 

위 코드는 QueryDSL을 사용하여 JPQL을 생성 및 실행하는 Repository 코드이다.  searchMember 메소드를 보자. select 메소드부터 where메소드까지는 JPQL 생성에 필요한 메타데이터를 설정하는 메소드이다.  fetch 메소드는 설정된 데이터를 가지고 JPQL를  생성및 실행하는 메소드이다.  JPQL을 생성하려면 EntityManager가 필요하다.  EntityManager는 JPAQueryFactory를 생성할 때 주입된다.  ( JPAQueryFactory에 대한 자세한 내용은 이전 포스팅을 참고하면 된다.  )

 

public abstract class AbstractJPAQuery<T, Q extends AbstractJPAQuery<T, Q>> extends JPAQueryBase<T, Q> {
   
   //fetch 메소드
    @Override
    @SuppressWarnings("unchecked")
    public List<T> fetch() {
        try {
            Query query = createQuery(); // 1. JPQL 쿼리 객체 생성
            return (List<T>) getResultList(query); // 2. JPQL 쿼리 객체 실행
        } finally {
            reset();
        }
    }
    
    //JPQL 쿼리 객체 생성 메소드 
    public Query createQuery() {
        return createQuery(getMetadata().getModifiers(), false);
    }
    
    //JPQL 쿼리 객체 생성 메소드 
    protected Query createQuery(@Nullable QueryModifiers modifiers, boolean forCount) {
        JPQLSerializer serializer = serialize(forCount); // 메타데이터로 JPQL문 객체 생성하기
        String queryString = serializer.toString(); // JPQL 문자열로 변환
        Query query = entityManager.createQuery(queryString); //EntityManager에 JPQL 전달하기
		//중략...
        return query;
    }
    
    //JPQL 쿼리 객체 실행 메소드
    private List<?> getResultList(Query query) {
        if (projection != null) {
            List<?> results = query.getResultList(); // JPQL 쿼리객체 실행
            List<Object> rv = new ArrayList<Object>(results.size());
			// 중략...
            return rv;
        } else {
            return query.getResultList();
        }
    }
}

 

fetch 메소드를 정의하고 있는 AbstractJPAQuery 클래스를 보자. ( AbstractJPAQuery에 관한 자세한 내용도 이전 포스팅을 참고하면 된다.  )  fetch 메소드를 보면 두 가지 작업을 수행함을 알 수 있다. 

 

Query query = createQuery(); // 1. JPQL 쿼리 객체 생성
return (List<T>) getResultList(query); // 2. JPQL 쿼리 객체 실행

 

두 가지 작업은 개발자가 직접 코드로 작성해서 실행했던 부분이다. JPQL 생성 및 실행 권한이 QueryDSL로 넘어 간 것이다. 개발자는 JPQL 생성을 위한 메타데이터만 설정해주면 된다. createQuery() 메소드를 들여다 보면 개발자가 설정한 메타데이터를 가져와 JPQL 쿼리문을 만드는 과정을 확인할 수 있다. 

 

public Query createQuery() {
       return createQuery(getMetadata().getModifiers(), false); // 메타데이터 가져오기 
}
protected Query createQuery(@Nullable QueryModifiers modifiers, boolean forCount){
        JPQLSerializer serializer = serialize(forCount); // 메타데이터로 JPQL문 객체 만들기
        String queryString = serializer.toString(); // JPQL문 문자열로 변환하기
        Query query = entityManager.createQuery(queryString); //EntityManager로 JPQL문 전달하기
        // 중략...
}

 

serialize 메소드는 메타데이터를 조합하여 JPQL을 생성한다. 그리고 이를 문자열로 변환하여 EntityManager에 전달한다. 

 

클릭시, 크게 볼 수 있음

 

실제로 디버깅을 해보면, 메타데이터로 조립되어 만들어진 JPQL문이 JPQLSerializer 객체 형태로 반환됨을 알 수 있다. 이를 문자열로 변환하고 EntityManager에 전달하면 된다. 그럼 EntityManager는 JPQL 쿼리 객체를 생성하고 반환한다. 

 

 

private List<?> getResultList(Query query){
            //  중략...
            List<?> results = query.getResultList(); // JPQL 쿼리객체 실행
            //  중략...
}

 

반환된 쿼리 객체는 실행되어 결과를 클라이언트에게 반환한다. 

 

 

 

 

정리하면,

 

QueryDSL은 JPQL 생성 및 실행 권한을 자신에게 위임하여, 개발자가 JPQL을 직접 작성했을 때 발생하는 타입 안정성 체크의 어려움이나 동적 쿼리생성 문제를 해결할 수 있다. 개발자는 그저 JPQL 생성을 위한 메타데이터만 설정하면 되고 QueryDSL은 유연한 데이터 설정을 위해 빌더패턴 구조로 이루어져 있다. select나 where절로 데이터 설정을 하고 fetch 메소드로 JPQL 생성및실행 작업을 처리한다. 이렇게 처리된 결과를 클라이언트에게 전달하면 모든 프로세스는 마무리된다. 

 

이것이 QueryDSL의 개략적인 동작원리이다. 

 

 

 

 

반응형