JPA 프로그래밍 입문 - chapter08 엔티티 간 N:1 단방향 연관

이 글은 “JPA 프로그래밍 입문 (최범균 저)” 책 내용을 정리한 글입니다.

만약 저작권 관련 문제가 있다면 “gunjuko92@gmail.com”로 메일을 보내주시면, 바로 삭제하도록 하겠습니다.

1. 엔티티의 N:1 연관

  • JPA에서 엔티티 간 N:1 연관은 DB 테이블 간 참조키로 구현한다.

2. 참조키를 이용한 N:1 연관 설정

  • JPA에서 참조키를 이용한 N:1 연관은 간단하게 설정할 수 있다. @ManyToOne 애노테이션과 @JoinColumn 애노테이션을 사용하면 된다.
@Entity
@Table(name = "hotel_review")
public class Review {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @ManyToOne
    @JoinColumn(name = "hotel_id")
    private Hotel hotel;
    private int rate;
    private String comment;
}
  • @ManyToOne : Review 엔티티 입장에서 다수의 Review 엔티티가 한 개의 Hotel 엔티티를 참조하는 N:1 연관임을 설정한다.
  • @JoinColumn : 연관된 Hotel 객체를 참조할 때 사용할 칼럼 이름을 지정한다. 위의 경우 hotel_review 테이블의 hotel_id 칼럼을 이용해서 연관된 Hotel 객체를 찾는다.
  • @ManyToOne으로 설정한 필드에 연관된 객체를 할당한 뒤에 저장하면 매핑된 칼럼에 연관된 객체의 식별자를 저장한다.

3. N:1의 연관 엔티티 로딩

  • @ManyToOne 애노테이션의 fetch 속성은 기본값으로 FetchType.EAGER를 사용한다. 하이버네이트는 외부 조인을 이용해서 연관된 데이터를 하나의 쿼리로 조회한다.
  • 지연 로딩을 적용하고 싶다면 FetchType.LAZY를 사용하면 된다.
  • 두 개의 Review 엔티티가 동일한 Hotel 엔티티를 참조하는 경우, 두 Review 객체는 동일한 Hotel 객체를 참조하게 된다.
    • 이는 즉시 로딩이나 지연 로딩 여부에 상관이 없다.
    • 영속 컨텍스트는 식별자를 기준으로 엔티티를 저장하고 추적하기 때문에 즉시 로딩을 이용해서 동일한 연관 엔티티를 두 번 읽어와도 영속 컨텍스트는 먼저 로딩한 엔티티를 사용한다.
Review review1 = em.find(Review.class, 1L);
Review review2 = em.find(Review.class, 2L);

Hotel hotel1 = review1.getHotel();
Hotel hotel2 = review2.getHotel();

// review1과 review2가 참조하는 Hotel의 식별자가 같다면 hotel1과 hotel2는 동일 객체이다.

assertEquals(hotel1, hotel2);

4. 특정 엔티티와 N:1 연관을 맺은 엔티티 목록 구하기

N:1 연관을 갖는 엔티티에 대해 가장 많이 사용하는 기능 중 하나는 특정 엔티티와 N:1로 연관을 맺은 엔티티의 목록을 구하는 것이다.

  • JPA는 JPQL이라는 쿼리 언어를 이용해서 특정 엔티티 목록을 조회하는 방법을 제공한다. JPQL은 SQL과 비슷한 형태를 가지며 테이블이 아닌 객체 모델을 기준으로 쿼리를 작성한다.
  • JPQL은 테이블 대신에 엔티티 타입을 사용해서 쿼리를 작성한다.
EntityManager em = EMF.createEntityManager();

try {
    Hotel hotel = em.find(Hotel.class, "H100-01");
    
    TypedQuery<Review> query 
        = em.createQuery("select r from Review r " + 
                        "where r.hotel=:hotel order by r.id desc");
    
    query.setParameter("hotel", hotel);  // 파라미터 값을 세팅한다. 위의 쿼리에서 :hotel이 파라미터에 해당한다.
    query.setFirstResult(3); // 조회할 첫번째 행을 지정한다. (0부터 시작)
    query.setMaxResult(3); // 가져올 최대 개수를 지정한다.
    List<Review> reviews = query.getResultList(); // 쿼리를 실행한다.
    
    ...
}