컬렉션

  • JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원하고 다음 경우에 이 컬렉션을 사용할 수 있다.
    • @OneToMany, @ManyToMany를 사용해서 일대다나 다대다 엔티티 관계를 매핑할 때
    • @ElementCollection을 사용해서 값 타입을 하나 이상 보관할 때

JPA와 컬렉션

  • 하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싸서 사용한다.

Collection, List

  • ArrayList로 초기화하면 된다.
  • Collection, List는 중복을 허용한다고 가정하므로 add() 메소드는 내부에서 어떤 비교도 하지 않고 항상 true를 반환한다.
  • 같은 엔티티가 있는지 찾거나 삭제할 때는 equals() 메소드를 사용한다.
  • Collection, List는 엔티티를 추가할 때 중복된 엔티티가 있는지 비교하지 않고 단순히 저장만 하면된다.
    • 따라서 엔티티를 추가해도 지연 로딩된 컬렉션을 초기화하지 않는다.

Set

  • HashSet으로 초기화하면 된다.
  • 중복을 허용하지 않는다.
  • HashSet은 해시 알고리즘을 사용하므로 hashCode()도 함께 사용해서 비교한다.
  • 엔티티를 추가할 때 중복된 엔티티가 있는지 비교해야 한다. 따라서 엔티티를 추가할 때 지연 로딩된 컬렉션을 초기화한다.

List + @OrderColumn

  • 데이터베이스에 순서 값을 저장해서 조회할 때 사용한다는 의미다.
  • 순서가 있는 컬렉션은 데이터베이스에 순서 값도 함께 관리한다.
  • @OrderColumn의 단점
    • @OrderColumnBoard 엔티티에서 매핑하므로 Comment는 POSITION의 값을 알 수 없다. 그래서 Comment를 저장할 때는 POSITION 값이 저장되지 않는다. POSITION은 Board.comemnts의 위치 값이므로, 이 값을 사용해서 POSITION의 값을 UPDATE하는 SQL이 추가로 발생한다.
    • List를 변경하면 연관된 많은 위치 값을 변경해야 한다. 예를 들어 댓글2를 삭제하면 댓글3, 댓글4의 POSITOIN 값을 하나씩 줄이는 UPDATE SQL이 2번 추가로 실행된다.
    • 중간에 POSITION 값이 없으면 조회한 List에는 null이 보관된다. 예를 들어 POSITION 값이 [0, 2, 3]이 되어서 중간에 1이 없는 경우, List를 조회하면 1번 위치에 null 값이 보관된다.

@OrderBy

  • @OrderBy는 모든 컬렉션에 사용할 수 있다.
  • @OrderBy의 값은 JPQL의 order by절처럼 엔티티의 필드를 대상으로 한다.
  • 하이버네이트에는 Set@OrderBy를 적용해서 결과를 조회하면 순서를 유지하기 위해 HashSet 대신에 LinkedHashSet을 내부에서 사용한다.

@Converter

  • 컨버터를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있다.
  • @Converter 어노테이션을 사용하고 AttributeConverter 인터페이스를 구현해야 한다.
  • 컨버터는 클래스 레벨에도 설정할 수 있다. 단 이때는 attributeName 속성을 사용해서 어떤 필드에 컨버터를 적용할지 명시해야 한다.
  • 글로벌 설정을하려면 @Converter(autoApply = true) 옵션을 적용하면 된다.

리스너

  • JPA 리스너 기능을 사용하면 엔티티의 생명주기에 따른 이벤트를 처리할 수 있다.

이벤트 종류

  1. PostLoad: 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후(2차 캐시에 저장되어 있어도 호출된다)
  2. PrePersist: persist() 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다.
  3. PreUpdate: flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다.
  4. PreRemove: remove() 메소드를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 또한 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval에 대해서는 flush나 commit시에 호출된다.
  5. PostPersist: flush나 commit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다. 참고로 식별자 생성 전략이 IDENTITY면 식별자를 생성하기 위해 persistn()를 호출하면서 데이터베이스에 해당 엔티티를 저장하므로 이때는 persist()를 호출한 직후에 바로 PostPersist가 호출된다.
  6. PostUpdate: flsuh나 commit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.
  7. PostRemove: flush나 commit을 호출해서 엔티티를 데이터베이스에 삭제한 직후에 호출된다.

이벤트 적용 위치

  • 엔티티에 직접 적용
  • 별도의 리스너 등록
  • 기본 리스너 사용
    • META-INF/orm.xml에 기본 리스너로 등록하면 된다.
  • 여러 리스너를 등록했을 때 이벤트 호출 순서
    1. 기본 리스너
    2. 부모 클래스 리스너
    3. 리스너
    4. 엔티티
  • 더 세밀한 설정을 위한 어노테이션도 있다.
    • ExcludeDefaultListeners: 기본 리스너 무시
    • ExcludeSuperclassListeners: 상위 클래스 이벤트 리스너 무시

엔티티 그래프

  • 엔티티를 조회할 때 연관된 엔티티를 함께 조회하려고, FetchType.EAGER이나 페치 조인을 사용하면 문제가 단점이 존재한다.
    • FetchType.EAGER: 애플리케이션 전체에 영향을 주고 변경할 수 없다.
    • 페치 조인: 조회할 엔티티에 따라 매번 다른 JPQL을 사용해야 한다.
  • JPA 2.1에 추가된 엔티티 그래프 기능을 사용하면 엔티티를 조회하는 시점에 함께 조회할 연관된 엔티티를 선택할 수 있다.
    • 정적으로 정의하는 Named 엔티티 그래프
    • 동적으로 정의하는 엔티티 그래프

Named 엔티티 그래프

  • Named 엔티티 그래프는 @NamedEntityGraph로 정의한다.
    • name: 엔티티 그래프 이름을 정의한다.
    • attributeNodes: 함께 조회할 속성을 선택한다.
  • Order.member가 지연 로딩으로 설정되어 있지만, 엔티티 그래프에서 함께 조회할 속성으로 member를 선택했으므로 이 엔티티 그래프를 사용하면 Order를 조회할 때 연관된 member도 함께 조회할 수 있다.
  • 둘 이상 정의하려면 @NamedEntityGraphs를 사용하면 된다.

em.find()에서 엔티티 그래프 사용

  • 힌트 기능을 사용해서 힌트의 키로 javax.persistence.fetchgraph를 사용하고 힌트의 값으로 찾아온 엔티티 그래프를 사용하면 된다.

subgraph

  • Order -> OrderItem -> Item까지 함께 조회하려면 subgraph를 사용해야 된다.
  • ItemOrder의 객체 그래프가 아니므로 subgraphs 속성으로 정의해야 한다.

JPQL에서 엔티티 그래프 사용

  • em.find에서 엔티티 그래프를 사용하면 하이버네이트틑 필수 관계를 고려해서 SQL 내부조인을 사용하지만 JPQL은 엔티티 그래프를 사용할 때는 항상 SQL 외부 조인을 사용한다. 만약 SQL 내부 조인을 사용하려면 다음처럼 내부 조인을 명시하면 된다.

동적 엔티티 그래프

  • 엔티티 그래프를 동적으로 구성하려면 createEntityGraph() 메소드를 사용하면 된다.

엔티티 그래프 정리

  • 아래처럼 영속성 컨텍스트에 해당 엔티티가 이미 로딩되어 있으면 엔티티 그래프가 적용되지 않는다.
  • fetchgraph, loadgraph의 차이
    • 힌트 중에 javax.persistence.loadgraph도 있는데 엔티티 그래프 속성뿐만 아니라 글로벌 fetch 모드가 FetchType.EAGER로 설정된 연관관계도 퐇마해서 함께 조회한다.