파라미터에 따라 달라질 수 있는 쿼리
쿼리 형태를 예측하기가 어렵다
조금만 조건이 까다로워지면 추측하기도 힘든 쿼리가 될 것이다.
변수에 문자열을 대입하여 쿼리문을 작성하는 것이 아닌 일반적으로 작성된 SQL 쿼리
애플리케이션 로딩시점에 오류를 확인할 수 있다.
정적 쿼리로 작성된 Stored Procedure는 새로 캐싱되지 않아 재사용성을 떨어트리지 않는다.
엔티티 위에 @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("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 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있음(매우 큰 장점!)
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가지가 있습니다.
@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/