Server/Spring JPA

Spring Data JPA - Named query 정리 (+ @Query) !!

JaeHoney 2022. 4. 8. 17:25

Spring Data JPA Named query

Repository에서 SQL문을 직접 작성할 떄는 JPA의 Named query라는 것을 사용할 수 있습니다.\

 

Named query의 사용 방법과 장단점에 대해서 알아보겠습니다.

@NamedQuery

JPA에서는 NamedQuery라는 것을 제공하는데, 엔터티에 @NamedQuery 애노테이션을 사용해서 name과 query를 지정합니다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NamedQuery(
        name = "User.findByUseranme",
        query = "select u from User u where u.username = :username"
)
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String username;
}

JPA의 EntityManager의 createNameQuery 메서드를 사용해서 다음과 같이 해당 쿼리를 사용할 수 있습니다. name을 동일하게 매핑시켜줘서 해당 쿼리가 사용되는 방식입니다.

public List<User> findByUsername(String username) {
    return entityManager.createNamedQuery("User.findByUsername", User.class)
        .setParameter("username", "이름")
        .getResultList();
}

@Query

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에서 쿼리를 바로 정의할 수 있게 기능을 제공합니다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
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

추가적으로 Spring Data JPA의 명명규칙을 사용해서 NamedQuery를 사용할 수 있습니다. 아래 두개의 쿼리는 동일한 기능을 제공합니다.

public interface AuthorRepository extends JpaRepository<User, Long> {
    
    List<User> findByFirstNameAndLastName(String firstName, String lastName);
    
    @Query("select u from user u where u.firstName = :firstName and u.lastName = :lastName")
    List<User> findByName(String firstName, String lastName);
 
}

그럼 어느쪽을 사용하는 것이 바람직할까요? 위 경우에는 첫 번째 경우가 더 바람직할 수 있습니다.

 

하지만 파라미터가 3개 이상이 되거나 order by 절 등을 사용해서 쿼리가 복잡해지면 메서드명이 엄청나게 복잡해져서 가독성을 저하합니다.

 

그 때는 @Query를 사용하는 것도 좋습니다. 실제로 실무에서도 아주 많이 사용합니다.

TIP) 필요한 부분만 꺼내쓰자!

추가적으로 주의할 사항은 필요한 부분만 꺼내쓰는 것입니다. Entity 조회시 Hibernate 캐시에 복사본 저장, 불필요한 컬럼 조회, OneToOne에서의 N+1 쿼리  단순 조회 기능에서는 성능 이슈 요소가 많습니다.

 

따라서 영속성 컨텍스트에서 Entity를 관리할 필요가 없다면 DTO를 사용하는 것이 바람직합니다.

 

그리고 만약 Max(order)를 조회한다면 엔터티 전체가 아니라 컬럼하나만 조회하는 것이 성능상도 좋고, 굳이 엔터티가 필요없다면 Max에 대해 Integer 값만 받는 것이 도메인 이해에 도움을 줍니다.

 

감사합니다.

 

마무리

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]

개인적으로, 자바가 무겁지만 이런 장점이 있어서 선호하는 것 같습니다.

 

그리고 추가적으로, 쿼리문을 미리 만들어두고 새롭게 SQL문을 Build하지 않고 사용하니까 성능도 향상됩니다.

 

감사합니다.