JPQL 쿼리는 크게 동적 쿼리와 정적 쿼리로 나눌 수 있다.
동적 쿼리
동적 쿼리는 em.createQuery(”select ..”)와 같이 JPQL을 문자로 완성해서 직접 넘기는 것을 동적 쿼리라 한다.
런타임에 특정 조건에 따라 JPQL을 동적으로 구성할 수 있다.
Name Query(정적 쿼리)
Named 쿼리를 어노테이션에 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private int age;
...
}
Named 쿼리 사용
List<Member> resultList = em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
for (Member member : resultList) {
System.out.println("member = " + member);
}
Named 쿼리 환경에 따른 설정
Spring Data JPA에서의 Named 쿼리
Spring Data JPA에서는 Named 쿼리를 인터페이스 메서드에 바로 선언할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.emailAddress = ?1")
Member findByEmailAddress(String emailAddress);
}
간편하게 설정할 수 있으며, Named 쿼리의 장점을 모두 가지고 있다.
SpringDataJPA를 사용하면 Repository method에 @Query
Annotation을 사용해서 위의 과정을 SpringDataJPA에 위임할 수 있다.
@Query(name = "User.findByUsername")
List<User> findByUsername(@Param("username") String username);
앞에서는 Entity에 @NamedQuery
라는 애노테이션 안에서 쿼리문을 명시해서 사용했다. 그래서 Entity에 쿼리가 많이 쌓인다는 단점이 있고, Entity가 쿼리까지도 담당하게 되어서 단일 책임 원칙도 벗어나게 된다.
그래서 SpringDataJPA는 엔티티에서 @NamedQuery
를 사용하지 않고 Repository method에서 쿼리를 바로 정의할 수 있게 @Query
를 제공한다.
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
}
@Query("select u from User u where u.username = :username")
List<User> findUser(@Param("username") String username);
NamedQuery vs @Query
NamedQuery vs @Query
그럼 어느쪽을 사용하는 것이 바람직할까?
NamedQuery 사용시 파라미터가 3개 이상이 되거나 order by 절 등을 사용해서 쿼리가 복잡해지면 메서드명이 엄청나게 복잡해져서 가독성이 저하된다.
이럴때는 @Query를 사용하는 것이 좋다. 실제로 실무에서도 아주 많이 사용한다고 한다.
필요한 부분만 꺼내쓰자!
추가적으로 주의할 사항은 필요한 부분만 꺼내쓰는 것이다. Entity 조회시 Hibernate 캐시에 복사본 저장, 불필요한 컬럼 조회, OneToOne에서의 N+1 쿼리 등 단순 조회 기능에서는 성능 이슈 요소가 많다.
따라서 영속성 컨텍스트에서 Entity를 관리할 필요가 없다면 DTO를 사용하는 것이 바람직하다.
JPA가 좋은 이유가 있는데, 예를 들어 username이라는 필드 대신에 실수로 usernaem이라고 오타를 쳤다고 가정하자.
Node.js의 ORM을 사용하면, 서버에서 해당 리포지토리 메소드가 실행되었을 때 비로소 결함을 발견할 수 있다.
위 방식을 사용하면 애플리케이션을 로딩할 때 @Query의 쿼리문을 파싱을 해서 미리 만들어둔다. 그래서 파싱하는 과정에서 문제를 인식하게 되고, 런타임에러가 발생해서 서버 자체가 올라가지 않는다.
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException:
could not resolve property: usernaem of: study.datajpa.entity.User [select u from study.datajpa.entity.User m where m.usernaem = :username]
출처
자바 ORM 표준 JPA 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.