JPQL이란? query DSL 이란?

보람찬하루·2023년 11월 30일
4

JPA(Java Persistence API)를 사용하여 서비스를 구현하다 보면 JPA의 Query Methods만으로는 조회가 불가능한 경우가 생깁니다!

예를들면 사용자 이름과 이메일로 조회하고 싶거나 나이가 20살 이상인 사용자를 조회하고싶거나 등등 이런 경우가 있을 수 있겠죠??

이러한 경우 JPQL(Java Persistence Query Language)를 이용하여 SQL과 비슷한 형태의 쿼리를 작성하여 조회를 할 수 있습니다.

 @Query Annotation과 EntityManager.createQuery등을 사용하여 JPQL를 작성하는 방법에 대해서 알아보겠습니다!


JPQL이란?

(Java Persistence Query Language) 객체지향 쿼리로 JPA가 지원하는 다양한 쿼리 방법 중 하나

  • vs SQL
    • SQL : 테이블을 대상으로 쿼리
    • JPQL : 엔티티 객체를 대상으로 쿼리

@Query

  • @Query 는 무엇인가요?

    @Query는 더 구체적인 쿼리 메서드를 작성하기 위해 사용하는 쿼리 메서드의 custom버전


  • @Query Annotation는 Entity의 JpaRepository를 상속받는 인터페이스에 정의합니다
    public interface UserRepository extends JpaRepository<User, Long> {
        @Query("쿼리문")
        List<User> methodName();

  • 작성방법: from 구문에 Entity의 객체를 선언하여 해당 객체의 속성명을 통해서 조건과 파라미터를 작성

  • 주의 : 각 문장에 끝에 띄어쓰기를 추가 각 문장이 문자열로 이어져있기 때문에 , 문자의 처음 또는 끝에 띄어쓰기가 없는 경우 한 문장으로 인식되어 정상적인 문장이 아니게 됩니다.

  • 예시
    @Entity
    @Getter
    @Setter
    @NoArgsConstructor
    @Table(name = "user")
    @AllArgsConstructor
    public class User {
        @Id
        private String id;
    
        private String name;
    
        private String phone;
    
        private String registerInfo;
    
        private String deptId;
    }
    위와 같은 User 엔티티가 있을 때
    public interface UserRepository extends JpaRepository<User, String> {
    
        @Query(value = "select user " +
                "from User user " +
                "where user.name = :name")
        List<User> findByName(@Param("name") String name);
    이렇게 사용해줄 수 있습니다!

  • 우리가 많이 사용하는 function과 join을 사용하기 위해선 정의한 Entity의 속성외에 속성이 추가 될것입니다! 이를 위해 DTO 반환이 필요합니다 @Query Annotation를 사용하여 DTO 반환을 하기위해서는 select 구분에서 생성자를 통해서 객체를 반환하면 됩니다

    • join 예시
      @Getter
      @Setter
      @NoArgsConstructor
      @AllArgsConstructor
      public class UserDto {
      
          private String id;
      
          private String name;
      
          private String phone;
      
          private String deptId;
      
          private String deptName;
      }
      이런 DTO가 있을때
      public interface UserRepository extends JpaRepository<User, String> {
          @Query(value = "select " +
                  "new UserDto(user.id, user.name, user.phone, user.deptId, dept.name) " +
                  "from User user " +
                  "left outer join Dept dept on user.deptId = dept.id ")
          List<UserDto> findUserDept();
      }
      이렇게 반환해주면 됩니다!


    • function 예시
      JPQL에서는 기본적으로 select의 maxmincountsumavg를 제공하며 기본 function으로는 COALESCELOWERUPPER등을 지원합니다. 그 중 max로 코드를 작성해보겠습니다!
      public interface UserRepository extends JpaRepository<User, String> {
          @Query(value = "select max(user.id) " +
                  "from User user " +
                  "where user.deptId is not null")
          String findMaxUserId();
      }
      하지만 JPQL에서 지원하는 함수만으론 한계가 존재합니다..!!

EntityManager

EntityManager를 이용하여 NativeQuery를 작성하는 방법은 createNativeQuery()를 통해서 SQL 문장을 작성합니다.

 EntityManager를 사용하는 경우 Hibernate의 NativeQuery.class를 이용하여 setResultTransformer를 통해 DTO class를 매핑하여 결과를 리턴받는 방법이 있습니다.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {

    private String id;

    private String name;

    private String phone;

    private String deptId;

    private String deptName;
}

이런 DTO가 있을 때

public List<UserDto> test7(){
        return entityManager.createNativeQuery("select user.id as id, user.name as name, user.phone as phone, user.dept_id as deptId, dept.name as deptName " +
                "from user user " +
                "left outer join dept dept on user.dept_id = dept.id " +
                "where match (user.name) against (:name in boolean mode) > 0").setParameter("name","le")
                .unwrap(NativeQuery.class)
                .setResultTransformer(Transformers.aliasToBean(UserDto.class))
                .getResultList();
    }

이런식으로 작성이 가능합니다!




 이번엔 @Query Annotation과 EntityManager.createQuery등을 사용하여 JPQL를 작성하는 방법에 대해서 알아보았는데요

이런 JPQL에는 쿼리를 String 형태로 작성하고 있다는 문제가 있습니다.

그러다보니 아래와 같은 문제가 발생합니다!

JPQL의 문제점

  1. JPQL은 문자열(=String) 형태이기 때문에 개발자 의존적 형태

2. Compile 단계에서 Type-Check가 불가능

3. RunTime 단계에서 오류 발견 가능 (장애 risk 상승)


query DSL

그래서 JPQL보완을 위해 나온 query DSL을 알아보겠습니다


query DSL이란?

정적 타입을 이용해서 SQL, JPQL을 코드로 작성할 수 있도록 도와주는 오픈소스 빌더 API

  • 어떻게 문자열 형태인 JPQL을 보완 했을가요?

    바로 쿼리에 대한 내용을 함수 형태로 제공하여 보완했습니다!


  • 예시

    @PersistenceContext
    EntityManager em;
     
    public List<Person> selectPersonByNm(String firstNm, String lastNm){
    	JPAQueryFactory jqf = new JPAQueryFactory(em);
    	QPerson person = QPerson.person;
      
    	List<Person> personList = jpf
    								.selectFrom(person)
    								.where(person.firstName.eq(firstNm)
    									.and(person.lastName.eq(lastNm))
    								.fetch();
                                    
    	return personList;
    }

  • 단점 : 코드 라인이 늘어난다

  • 장점

    1. 문자가 아닌 코드로 작성

    2. Compile 단계에서 문법 오류를 확인 가능

    1. 코드 자동 완성 기능 활용 가능

    4. 동적 쿼리 구현 가능

  • 참고

    https://velog.io/@cho876/JPQL-vs-query-DSL

    https://velog.io/@youmakemesmile/Spring-Data-JPA-JPQL-사용-방법Query-nativeQuery-DTO-Mapping-function

profile
를 만들어 가자

2개의 댓글

comment-user-thumbnail
2024년 9월 23일

유익한 글 잘봤습니다!
그럼 같은 동적쿼리일 때 QueryDsl을 사용해야하는 기준 같은 것도 있을까요?
두 기술의 선택 판단 기준이 궁금합니다!

1개의 답글