NamedQuery와 Dynamic Query

kkambbak1·2024년 1월 9일

동적 쿼리와 정적 쿼리

동적 쿼리

파라미터에 따라 달라질 수 있는 쿼리

 쿼리 형태를 예측하기가 어렵다
조금만 조건이 까다로워지면 추측하기도 힘든 쿼리가 될 것이다.

정적 쿼리

  • 변수에 문자열을 대입하여 쿼리문을 작성하는 것이 아닌 일반적으로 작성된 SQL 쿼리

  • 애플리케이션 로딩시점에 오류를 확인할 수 있다.

  • 정적 쿼리로 작성된 Stored Procedure는 새로 캐싱되지 않아 재사용성을 떨어트리지 않는다.

NamedQuery

엔티티 위에 @NamedQuery 사용

@Entity
@NamedQuery(
name="Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}

스프링 데이터 JPA로 Named 쿼리 호출

public interface MemberRepository extends JpaRepository<Member, Long> { 
	// 여기 선언한 Member 도메인 클래스
	@Query(name = "Member.findByUsername")
	List<Member> findByUsername(@Param("username") String username);
}

@Query 생략가능하다.

스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) + 메서드 이름"으로 Named 쿼리를 찾아서 실행
만약 실행할 Named 쿼리가 없으면 메서드 이름으로 쿼리 생성 전략을 사용한다.
필요하면 전략을 변경할 수 있지만 권장하지 않는다.

@Query, 리포지토리 메소드에 쿼리 정의하기

실무에서 자주 사용.
메소드이름으로 쿼리 생성기능은 파라미터가 증가하면 메서드 이름이 매우 지저분해지므로.

@Query("select m from Member m where m.username= :username and m.age = :age")
	List<Member> findUser(@Param("username") String username, @Param("age") int age);

@org.springframework.data.jpa.repository.Query 어노테이션을 사용
실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음
JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)

동적 쿼리

동적 네이티브 쿼리

  • 하이버네이트를 직접 활용
  • 스프링 JdbcTemplate, myBatis, jooq같은 외부 라이브러리 사용

예) 하이버네이트 기능 사용

String sql = "select m.username as username from member m";
List<MemberDto> result = em.createNativeQuery(sql)
	.setFirstResult(0)
	.setMaxResults(10)
	.unwrap(NativeQuery.class)
	.addScalar("username")
	.setResultTransformer(Transformers.aliasToBean(MemberDto.class))
	.getResultList();
}

JPA에서 동적 쿼리를 생성하는 방법은 JPQL, Criteria, Specification, Querydsl 이렇게 4가지가 있습니다.

QueryDSL

@Entity (java.persistence.Entity) 를 이용해 정의된 Entity 클래스들을 찾아 AnnotationProcessor를 이용해 Q Type class를 생성

Q Type 클래스에서 생성해준 필드와 메서드들을 통해 type safe한 JPQL을 만들 수 있다.

public Slice<Member> findWithSearchConditions(final String gitHubId, final CareerLevel careerLevel,
                                                  final JobType jobType,
                                                  final Pageable pageable) {
    BooleanBuilder builder = new BooleanBuilder();
    if (StringUtils.hasText(gitHubId)) {
        builder.and(member.gitHubId.contains(gitHubId));
    }
    if (careerLevel != null) {
        builder.and(member.careerLevel.eq(careerLevel));
    }
    if (jobType != null) {
        builder.and(member.jobType.eq(jobType));
    }
    JPAQuery<Member> jpaQuery = jpaQueryFactory.selectFrom(member)
            .where(builder)
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize() + 1)
            .orderBy(makeOrderSpecifiers(member, pageable));

    return toSlice(pageable, jpaQuery.fetch());
}

booleanbuilder를 사용하는 코드를 분리해내면,

public Slice<Member> findWithSearchConditions(final String gitHubId, final CareerLevel careerLevel,
                                               final JobType jobType,
                                               final Pageable pageable) {
    JPAQuery<Member> jpaQuery = jpaQueryFactory.selectFrom(member)
            .where(
                    toContainsExpression(gitHubId),
                    eqCareerLevel(careerLevel),
                    eqJobType(jobType)
            )
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize() + 1)
            .orderBy(makeOrderSpecifiers(member, pageable));

    return toSlice(pageable, jpaQuery.fetch());
}

private BooleanExpression toContainsExpression(final String gitHubId) {
    if (StringUtils.hasText(gitHubId)) {
        return null;
    }
    return member.gitHubId.contains(gitHubId);
}

private BooleanExpression eqCareerLevel(final CareerLevel careerLevel) {
    if (careerLevel == null) {
        return null;
    }
    return member.careerLevel.eq(careerLevel);
}

private BooleanExpression eqJobType(final JobType jobType) {
    if (jobType == null) {
        return null;
    }
    return member.jobType.eq(jobType);
}

와 같아지며, 매우 가독성이 좋아졌다.

https://tecoble.techcourse.co.kr/post/2022-10-11-jpa-dynamic-query/

profile
윤성

0개의 댓글