TIL

[쿼리] JPA & JPQL & NativeQuery

Big Iron 2023. 5. 11. 23:54

JPA (Java Persistence API)

  • JPA는 데이터베이스에 직접적인 쿼리를 실행하지 않으며 Entity 테이블에 매핑하여 사용한다.

    장점

  1. Native Query나 JPQL을 사용해 쿼리를 직접 작성하는 것보다 개발자가 사용하기 쉽도록 자동으로 쿼리를 만들어준다.
  2. 기본(Hibernate)으로 쿼리를 자동 생성한다.

Entity 테이블에 매핑하는 것은 아래의 설명을 보면 알겠지만 @Entity 어노테이션이 달린 User Entity 클래스의 각 인스턴스 변수들을 users 테이블의 열로 정의 하는 것이다.

  import javax.persistence.*;

  @Entity
  @Table(name = "users") // 쿼리에서 user는 예약어기 때문에 users로 변경.
  public class User {

      @Id
      @GeneratedValue(strategy=GenerationType.AUTO)
      private Long id;

      @Column
      private String username;

      @Column
      private String email;

  }

위의 Entity 클래스 각 인스턴스는 테이블의 행을 나타내며 변수는 각 행의 열이 된다.

id username email
1 username1 user1@email.com
2 username2 user2@email.com

단점

  1. 쿼리 작성에 유연하지 않다
  2. 매핑하여 사용하는 만큼 DB에 직접적으로 쿼리를 실행하는 NativeQuery보다 속도적인 부분에서 차이가 있다.

1번과 같은 상황은 여러 테이블에대해 join해야하는 상황이나 보다 복잡한 쿼리를 작성해야 할 때, Entity에서 @OneToOne, @OneToMany, @ManyToOne 또는 @ManyToMany와 같은 어노테이션을 사용해 연관을 지어줘야 하고, JPQL을 사용해야 한다.


JPQL (Java Persistence Query Language)

  • JPQL은 도메인 모델과 일치하는 방식으로 엔터티를 쿼리하도록 설계되었다고 한다.
  • JPQL은 데이터베이스에 직접적인 쿼리를 실행하지 않으며 Entity 테이블에 매핑하여 사용한다.
  • Query, TypedQuery 두 가지 유형을 사용할 수 있다.

    장점

  1. 객체 지향적인 쿼리로 DB 테이블 조인이 아닌 java에서 엔티티관계 및 상속 개념으로 봐야한다.
  2. 반환 유형을 지정할 수 있다.
  3. 특정 유형의 DB(MySQL, Oracle)에 연결된 것이 아니기에 데이터베이스 변경 시에도 유지, 보수 부분에서 유연하며 쿼리를 다시 작성할 필요가 없다.
  4. 코드 실행 단계가 아닌 코드를 작성하는 시점에서 빠르게 오류를 발견할 수 있다.
  • Query 사용 예시

    import javax.persistence.*;
    
    private final EntityManager em;
    
    Query query = em.createQuery("SELECT u FROM User u");
    List<User> users = (List<User>) query.getResultList();
    
    for (User user : users) {
        System.out.println(user.getUsername());
    }
  • TypedQuery 사용 예시

    import javax.persistence.*;
    
    private final EntityManager em;
    
    TypedQuery<User> query = em.createQuery("SELECT u FROM User u", User.class);
    List<User> users = query.getResultList();
    
    for (User user : users) {
        System.out.println(user.getUsername());
    }

TypedQuery의 경우, 반환 유형을 정해놓고 진행하기에 조금 더 가독성 부분에서 좋다. 그리고 JPQL은 도메인 모델과 일치하는 방식으로 엔터티를 쿼리하도록 설계되었다고 했는데 DTO를 구성할 때도 사용할 수 있다.


image
SELECT 뒤로 해당 클래스의 전체적인 경로를 적어주면 사용 가능하다.

단점

  1. 모든 SQL 기능을 지원하지 않아 쿼리 작성에 유연하지 않다
  2. 여러 조인이나 복잡한 쿼리를 작성해야 할 때 어렵다.
  3. 매핑하여 사용하는 만큼 DB에 직접적으로 쿼리를 실행하는 NativeQuery보다 속도적인 부분에서 차이가 있다.

Native Query

  • 데이터베이스에 직접적인 쿼리를 실행한다.

    장점

  1. DB에 직접적으로 쿼리를 실행시켜 속도가 빠르다.
  2. SQL 언어를 전체적으로 사용 가능하다.
  3. 여러 조인이나 복잡한 쿼리를 작성할 때 직관적이며 이해하기 쉽다.

아래는 회원 탈퇴를 위한 user의 연관 테이블(bookmark, terms, application)을 지우는 쿼리다.

@Modifying
    @Query(value = "update users u " +
        "left join bookmark b on u.id = b.user_id " +
        "left join terms t on u.id = t.user_id " +
        "left join application a on u.id = a.user_id " +
        "set u.deleted_at = now(), " +
        "b.deleted_at = now(), " +
        "t.deleted_at = now(), " +
        "a.deleted_at = now() " +
        "where u.id = :userId", nativeQuery = true)
    void deleteUserAndData(@Param("userId") Long userId);

이처럼 Join을 사용할 때 생각해야할 부분도 있다.
예를 들어 inner join을 사용했을 때 join 테이블에대해 전체 조회를 거쳐야 하며 두 테이블의 일치한 값만 조회한다. left join의 경우 오른쪽 테이블에 값이 없더라도 기존 테이블의 값을 가져와 유지할 수 있다.


위의 쿼리에서 left는 user이고 join하는 두 테이블의 일치값이 없을 때 null을 반환하는 것은 오른쪽 테이블인 bookmark, terms, application중 하나이다.
회원 탈퇴를 해야했기에 bookmark, terms, application이 없는 user도 그대로 불러와 delete할 수 있도록 left를 사용했다.

단점

  1. 특정 DB에대해 쿼리를 작성하기에 종속성이 높아질 수 있고 DB가 수정되면 쿼리를 다시 만들어야 한다.. (MySQL에서는 LIMIT / Oracle에서는 ROWNUM 등)
  2. 결과를 Object 리스트로 반환 하기에 JPQL의 TypedQuery보다 안정성 부분에서 미흡하다.
  3. 주의할 점으로 사용자에게 입력된 값을 매개변수로 바인딩하거나 따로 유효성 검사가 필요할 수 있다.

'TIL' 카테고리의 다른 글

[JAVA] 전역 예외 처리  (0) 2023.05.15
[회고] 프로젝트를 마치며..  (0) 2023.05.13
쿠키, 캐시, 세션  (0) 2023.05.06
[자료구조] 스택 & 큐  (0) 2023.05.05
데이터 정규화  (0) 2023.05.04