JPA(Java Persistence API)
를 사용하여 서비스를 구현하다 보면 JPA의 Query Methods
만으로는 조회가 불가능한 경우가 생깁니다!
예를들면 사용자 이름과 이메일로 조회하고 싶거나 나이가 20살 이상인 사용자를 조회하고싶거나 등등 이런 경우가 있을 수 있겠죠??
이러한 경우 JPQL(Java Persistence Query Language)
를 이용하여 SQL과 비슷한 형태의 쿼리를 작성하여 조회를 할 수 있습니다.
@Query
Annotation과 EntityManager.createQuery
등을 사용하여 JPQL를 작성하는 방법에 대해서 알아보겠습니다!
(Java Persistence Query Language) 객체지향 쿼리로 JPA가 지원하는 다양한 쿼리 방법 중 하나
- vs SQL
- SQL : 테이블을 대상으로 쿼리
- JPQL : 엔티티 객체를 대상으로 쿼리
@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
의 max
, min
, count
, sum
, avg
를 제공하며 기본 function
으로는 COALESCE
, LOWER
, UPPER
등을 지원합니다. 그 중 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
를 이용하여 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의 문제점
- JPQL은
문자열(=String) 형태
이기 때문에개발자 의존적
형태2.
Compile 단계
에서 Type-Check가 불가능3.
RunTime 단계
에서 오류 발견 가능 (장애 risk 상승)
그래서 JPQL
보완을 위해 나온 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 단계
에서문법 오류
를 확인 가능
- 코드
자동 완성 기능
활용 가능4.
동적 쿼리
구현 가능
참고
https://velog.io/@cho876/JPQL-vs-query-DSL
https://velog.io/@youmakemesmile/Spring-Data-JPA-JPQL-사용-방법Query-nativeQuery-DTO-Mapping-function
유익한 글 잘봤습니다!
그럼 같은 동적쿼리일 때 QueryDsl을 사용해야하는 기준 같은 것도 있을까요?
두 기술의 선택 판단 기준이 궁금합니다!