@Entity
public class Team {
@Id
private String id;
@OneToMany
@JoinColumn
private Collection<Member> members = new ArrayList<>();
}
하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싸서 사용한다.
Team team = new Team();
System.out.println("Before persist : " + team.getMembers().getClass());
em.persist(parent);
System.out.println("After persist : " + team.getMembers().getClass());
// Before persist = class java.util.ArrayList
// After persist = class org.hibernate.collection.internal.PersistentBag
출력 결과를 보면 ArrayList 타입이었던 컬렉션이 엔티티를 영속상태로 만든 직후 하이버네이트가 제공하는 PersistentBag 타입으로 변경되었다.
하이버네이트는 컬렉션을 효율적으로 관리하기 위해 엔티티를 영속 상태로 만들 떄 원본 컬렉션을 감싸고 있는 내장 컬렉션을 생성해서 이 내장 컬렉션을 사용하도록 참조를 변경한다.
하이버네이트가 제공하는 내장 컬렉션은 원본 컬렉션을 감싸고 있어서 래퍼 컬렉션이라고도 부른다.
하이버네이트는 이런 특징 떄문에 컬렉션을 사용할 때 즉시 초기화해서 사용하는 것을 권장한다.
컬렉션 인터페이스|내장 컬렉션|중복 허용|순서 보관
---|---|---|---
Collection, List|PersistenceBag|O|X
Set|PersistenceSet|X|X
List + @OrderColumn|PersistentList|O|O
1.2 Collection, List
Collection, List 인터페이스는 중복을 허용하고 PersistentBag을 래퍼 컬렉션으로 사용한다.
이 인터페이스는 ArrayList로 초기화하면 된다.
add() 메소드는 내부에서 어떤 비교도 하지 않고 항상 true를 반환한다.
같은 엔티티가 있는지 찾거나 삭제할 때는 equals() 메소드를 사용한다.
Collection, List는 엔티티를 추가할 때 중복된 엔티티가 있는지 비교하지 않고 단순히 저장만 하면 된다. 따라서 엔티티를 추가해도 지연 로딩된 컬렉션을 초기화 하지 않는다.
1.3 Set
Set은 중복을 허용하지 않는 컬렉션이고 PersistentSet을 래퍼 컬렉션으로 사용한다.
중복을 허용하지 않으므로 add() 메소드로 객체를 추가할 때 마다 equlas() 메소드로 같은 객체가 있는지 비교한다.
같은 객체가 없으면 객체를 추가하고 true를 반환하고, 같은 객체가 이미 있어서 추가에 실패하면 false를 반환한다.
Set은 엔티티를 추가할 때 중복된 엔티티가 있는지 비교해야 한다. 따라서 엔티티를 추가할 때 지연 로딩된 컬렉션을 초기화한다.
1.4 List + @OrderColumn
List 인터페이스에 @OrderColumn 을 추가하면 순서가 있는 특수한 컬렉션으로 인식한다.
순서가 있다는 의미는 데이터베이스에 순서 값을 저장해서 조회할 때 사용한다는 의미다.
하이버네이트는 내부 컬렉션인 PersistentList를 사용한다.
@Entity
public class Board {
@Id @GeneratedValue
private Long id;
private String title;
private String content;
@OneToMany(mappedBy="board")
@OrderColumn(name="POSITION")
private List<Comment> comments = new ArrayList<>();
...
}
@Entity
public class Commnet {
@Id @GeneratedValue
private Long id;
private String comment;
@ManyToOne
@JoinColumn(name="BOARD_ID")
private Board board;
...
}
예제의 Board.comments에 List 인터페이스를 사용하고 @OrderColumn 을 추가했다. 따라서 Board.comments는 순서가 있는 컬렉션으로 인식된다.
자바가 제공하는 List 컬렉션은 내부에 위치 값을 가지고 있다. 따라서 List의 위치값을 활용할 수 있다.
list.add(1, data1);
list.get(10);
순서가 있는 컬렉션은 데이터베이스에 순서 값도 함께 관리한다.
JPA는 List 위치 값을 테이블의 POSITION 컬럼에 보관한다. 그런데 Board.comments 컬렉션은 Board 엔티티에 있지만 테이블의 일대다 관계의 특성상 위치 값은 다(N) 쪽에 저장해야 한다. 따라서 실제 POSITION 컬럼은 COMMENT 테이블에 매핑된다.
// 사용 코드
Board board = new Board("제목", "내용1");
em.persist(board);
Comment commnet1 = new Commnet("댓글1");
commnet1.setBoard(board);
board.getComments().add(comment1); // POSITION 0
em.persist(comment1);
Comment commnet2 = new Comments("댓글2");
comment2.setBoard(board);
board.getComments().add(comment2); // POSITION 1
em.persist(comment2);
Comment commnet3 = new Comments("댓글3");
comment3.setBoard(board);
board.getComments().add(comment3); // POSITION 2
em.persist(comment3);
Comment commnet4 = new Comments("댓글4");
comment4.setBoard(board);
board.getComments().add(comment4); // POSITION 3
em.persist(comment4);
@OrderColumn 을 사용해서 List의 위치 값을 보관하면 편리할 것 같지만 실무에서 사용하기에는 단점이 많다.
따라서 @OrderColumn 을 매핑하지 말고 개발자가 직접 POSITION 값을 관리하거나 다음에 설명하는 @OrderBy 를 사용하길 권장한다.
@OrderColumn 의 단점
@OrderColumn 을 Board 엔티티에서 매핑하므로 Comment는 POSITION 값을 알 수 없다. 그래서 Comment를 INSERT 할때는 POSITION 값이 저장되지 않는다. POSITION은 Board.comments의 위치 값으므로, 이 값을 사용해서 POSITION의 값을 UPDATE하는 SQL이 추가로 발생한다.
List를 변경하면 연관된 많은 위치 값을 변경해야 한다. 예를 들어 댓글 2를 삭제하면 댓글 3, 댓글 4의 POSITION 값을 각각 하나씩 줄이는 UPDATE SQL이 추가로 실행된다.
중간에 POSITION 값이 없으면 조회한 List에는 null이 보관된다. 예를 들어 댓글2를 데이터베이스에서 강제로 삭제하고 다른 댓글들의 POSITION 값을 수정하지 않으면 데이터베이스의 POSITION 값은 중간에 1 값이 없다. 이 경우 List를 조회하면 1번 위치에 null값이 보관된다. 따라서 컬렉션을 순회할때 NullPointException이 발생한다.
1.5 @OrderBy
@OrderBy 는 데이터베이스의 ORDER BY절을 사용해서 컬렉션을 정렬한다. 따라서 순서용 컬럼을 매핑하지 않아도 된다.
@OrderBy 는 모든 컬렉션에 사용할 수 있다.
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy="team")
@OrderBy("username desc, id asc")
private Set<Member> members = new HashSet<>();
...
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name="MEMBER_NAME")
private String username;
@ManyToOne
private Team team;
...
}
Team.members를 보면 @OrderBy 를 적용했는데 username desc, id asc를 사용해서 Member의 username 필드로 내림차순 정렬하고 id로 오름차순 정렬했다.
@OrderBy 의 값은 JPQL의 order by절 처럼 엔티티의 필드를 대상으로 한다.
하이버네이트는 Set에 @OrderBy 를 적용해서 결과를 조회하면 순서를 유지하기 위해 순서를 유지하기 위해 HashSet대신 LinkedHashSet을 내부에서 사용한다.
2. @Converter
컨버터를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장 할 수 있다.
회원의 VIP 여부를 boolean 타입을 사용해서 JPA를 이용해 데이터베이스에 저장될 때 0 또는 1인 숫자로 저장되는데 이때 숫자 대신 문자 Y 또는 N으로 저장하고 싶다면 컨버터를 사용하면 된다.
@Entity
public class Member {
@Id
private String id;
private String usernamel;
@Convert(converter=BooleanToYNConverter.class)
private boolean vip;
...
}
@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return (attribute != null && attribute) ? "Y" : "N";
}
@Override
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equals(dbData);
}
}
컨버터 클래스는 @Converter 어노테이션을 사용하고 AttributeConverter 인터페이스를 구현해야 한다.
제네릭에 현재 타입과 변환할 타입을 지정한다.
public interface AttributeConverter<X, Y> {
public Y convertToDatabaseColumn (X attribute);
public X convertToEntityAttribute (Y dbData);
}
convertToDatabaseColumn() : 엔티티의 데이터를 데이터베이스 컬럼에 저장할 데이터로 변환한다.
convertToEntityAttribute() : 데이터베이스에서 조회한 컬럼 데이터를 엔티티의 데이터로 변환한다.
컨버터는 클래스 레벨에도 설정할 수 있다. 이때는 attributeName 속성을 사용해서 어떤 필드에 컨버터를 적용할 지 명시하면 된다.
@Entity
@Convert(converter=BooleanToYNConverter.class, attributeName="vip")
public class Member {
...
private boolean vip;
...
}
2.1 글로벌 설정
모든 Boolean 타입에 컨버터를 적용하려면 @Converter(autoApply=true) 옵션을 적용하면 된다.
@Converter(autoApply=true)
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return (attribute != null && attribute) ? "Y" : "N";
}
@Override
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equals(dbData);
}
}
이렇게 글로벌 설정을 하면 모든 Boolean 타입에 대해 자동으로 컨버터가 적용된다.
3. 리스너
모든 엔티티를 대상으로 언제 어떤 사용자가 삭제를 요청했는지 모두 로그를 남겨야 하는 요구사항이 있다면 애플리케이션 삭제 로직을 하나씩 찾아서 로그를 남기는 것은 너무 비효율적이다.
JPA 리스너 기능을 사용하면 엔티티의 생명주기에 따른 이벤트를 처리할 수 있다.
3.1 이벤트 종류
PostLoad : 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후(2차 캐시에 저장되어 있어도 호출)
PrePersist : persist() 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다.
PreUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다.
PreRemove : remove() 메소드를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 또한 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval에 대해서는 flush나 commit시 호출
PostPersist : flush나 commit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다. 참고로 식별자 생성 전략이 IDENTITY면 식별자를 생성하기 위해 persist()를 호출하면서 데이터베이스에 해당 엔티티를 저장하므로 이때는 persist()를 호출한 직후에 바로 PostPersist가 호출된다.
PostUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.
PostRemove : flush나 commit을 호출해서 엔티티를 데이터베이스에 삭제한 직후에 호출된다.
3.2 이벤트 적용 위치
이벤트는 엔티티에서 직접 받거나 별도의 리스너를 등록해서 받을 수 있다.
엔티티에 직접 적용
별도의 리스너 등록
기본 리스너 사용
더 세밀한 설정
4. 엔티티 그래프
엔티티를 조회할 때 연관된 엔티티들을 함께 조회하려면 글로벌 fetch 옵션을 FetchType.EAGER로 설정한다.
@Entity
class Order {
@ManyToOne(fetch=FetchType.EAGER)
Member member;
}
또는 JPQL에서 페치 조인을 사용한다.
select o from Order o join fetch o.member
JPA 2.1에 추가된 엔티티 그래프 기능을 사용하면 엔티티를 조회하는 시점에 함께 조회할 연관된 엔티티를 선택할 수 있다.
JPQL은 데이터를 조회하는 기능만 수행하면 되고 연관된 엔티티를 함께 조회하는 기능은 엔티티 그래프를 사용하면 된다.
엔티티 그래프는 정적으로 정의하는 Named 엔티티그래프와 동적으로 정의하는 엔티티 그래프가 있다.
4.1 Named 엔티티 그래프
@NamedEntityGraph(name="Order.withMember", attribute={
@NamedAttirubteNode("member")
})
@Entity
@Table(name="ORDERS")
public class Order{
@Id @GeneratedValue
@Column(name="ORDER_ID")
private Long id;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="MEMBER_ID")
private Member member; // 주문 회원
}
Named 엔티티 그래프는 @NamedENtityGraph 로 정의한다.
name : 엔티티 그래프의 이름을 정의한다.
attributeNodes : 함께 조회할 속성을 선택한다. 이때 @NamedAttributeNode 를 사용하고 그 값으로 함께 조회할 속성을 선택하면 된다.
Order.member가 지연 로딩으로 설정되어 있지만, 엔티티 그래프에서 함께 조회할 속성으로 member를 선택했으므로 이 엔티티 그래프를 사용하면 Order를 조회할 때 연관된 member도 함께 조회할 수 있다.
둘 이상 정의하려면 @NamedEntityGraphs 를 사용하면 된다.
4.2 em.find()에서 엔티티 그래프 사용
EntityGraph graph = em.getEntityGraph("Order.withMember");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);
Named 엔티티 그래프를 사용하려면 정의한 엔티티 그래프를 em.getEntityGraph("Order.withMember")를 통해서 찾아오면 된다.
엔티티 그래프는 JPA의 힌트 기능을 사용해서 동작하는데 힌트의 키로 javax.persistence.fetchgraph를 사용하고 힌트의 값으로 찾아온 엔티티 그래프를 사용하면 된다.
em.find(Order.class, orderId, hints)로 Order 엔티티를 조회할 때 힌트 정보도 포함했는데 실행된 SQL을 보면 적용한 Order.withMember 엔티티 그래프를 사용해서 Order와 Member를 함께 조회한다.
select o.*, m.*
from
Order o
inner join
Member m
on o.MEMBER_ID=m.MEMBER_ID
where
o.ORDER_ID=?
4.3 subgraph
Order -> OrderItem -> Item 까지 한번에 조회한다면 Order -> OrderItem은 Order가 관리하는 필드지만 OrderItem -> Item은 Order가 관리하는 필드가 아니므로 subgraph를 사용해야 한다.
@NamedEntityGraph(name="Order.withMember", attribute={
@NamedAttriubteNode("member"),
@NamedAttributeNode(value="orderItems", subgraph="orderItems")
},
subgraphs=@NamedSubgraph(name="orderItems", attributeNodes= {
@NamedAttributeNode("item")
})
)
@Entity
@Table(name="ORDERS")
public class Order{
@Id @GeneratedValue
@Column(name="ORDER_ID")
private Long id;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="MEMBER_ID")
private Member member; // 주문 회원
@OneToMany(mappedBy="order", cascade=CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
...
}
@Entity
@Table(name="ORDER_ITEM")
public class OrderItem{
@Id @GeneratedValue
@Column(name="ORDER_ITEM_ID")
private Long id;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="ITEM_ID")
private Item item; // 주문 상품
...
}
// 사용하는 코드
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", em.getEntityGraph("Order.withAll"));
Order order = em.find(Order.class, orderId, hints);
이 엔티티의 그래프는 Order -> Member, Order -> OrderItem, OrderItem -> Item의 객체 그래프를 함께 조회한다.
OrderItem -> Item은 Order의 객체 그래프가 아니므로 subgraphs 속성으로 정의한다. 여기서는 orderItems라는 이름의 서브 그래프가 item을 함께 조회하도록 정의했다.
Order.withAll이라는 named 엔티티 그래프를 사용해서 Order 엔티티를 조회하면 실행된 SQL은 엔티티 그래프에서 지정한 엔티티들을 함께 조회한다.
select o.*, m.*, oi.*, i.*
from Order o
inner join
Member m
on o.MEMBER_ID=m.MEMBER_ID
left outer join
ORDER_ITEM oi
on o.ORDER_ID=oi.ORDER_ID
left outer join
Item i
on oi.ITEM_ID=i.ITEM_ID
where
o.ORDER_ID=?
4.4 JPQL에서 엔티티 그래프 사용
JPQL에서 엔티티 그래프를 사용하는 방법은 em.find()와 동일하게 힌트만 추가하면 된다.
// JPQL에서 엔티티 그래프 힌트
List<Order> resultList =
em.createQuery("select o from Order o where o.id=:orderId", Order.class)
.setParameter("orderId", orderId)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("Order.withAll"))
.getResultList();
-- 실행된 SQL
select o.*, m.*, oi.*, i.*
from Order o
inner join
Member m
on o.MEMBER_ID=m.MEMBER_ID
left outer join
ORDER_ITEM oi
on o.ORDER_ID=oi.ORDER_ID
left outer join
Item i
on oi.ITEM_ID=i.ITEM_ID
where
o.ORDER_ID=?
Order.member는 필수 관계로 설정되어 있다.
@ManyToOne(fetch=FetchType.LAZY, optional=false) // 필수 관계로 설정
@JoinColumn(name="MEMBER_ID")
private Member member; // 주문 회원
em.find()에서 엔티티 그래프를 사용하면 하이버네이트는 필수 관계를 고려해서 SQL 내부 조인을 사용하지만 JPQL에서 엔티티 그래프를 사용할 때는 항상 SQL 외부 조인을 사용한다.
만약 내부조인을 사용하려면 내부조인을 명시하면 된다.
select o from Order o join fetch o.member where o.id=:orderId
4.5 동적 엔티티 그래프
엔티티 그래프를 동적으로 구성하려면 createEntityGraph() 메소드를 사용한다.
// 동적 엔티티 그래프
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);
// 동적 엔티티 그래프 subgraph
EntityGraph<Order> graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("member");
Subgraph<OrderItem> orderItems = graph.addSubgraph("orderItems");
orderItems.addAttributeNodes("item");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
Order order = em.find(Order.class, orderId, hints);
em.createEntityGraph(Order.class)를 사용해서 동적으로 엔티티 그래프를 만들었고 graph.addAttributeNodes("member")를 사용해서 Order.member 속성을 엔티티 그래프에 포함했다.
subgraph 기능을 동적으로 구성하려면 graph.addSubgraph("orderItems") 메소드를 사용해 서브 그래프가 item 속성을 포함하도록 하면 된다.
엔티티 그래프 정리
ROOT에서 시작
엔티티 그래프는 항상 조회하는 엔티티의 ROOT에서 시작해야 한다.
당연한 이야기지만 Order 엔티티를 조회하는데 Member부터 시작하는 엔티티 그래프를 사용하면 안된다.
이미 로딩된 엔티티
영속성 컨텍스트에 해당 엔티티가 이미 로딩되어 있으면 엔티티 그래프가 적용되지 않는다.(초기화되지 않은 프록시에는 엔티티 그래프가 적용된다)
fetchgraph, loadgraph의 차이
예제에서는 javax.persistence.fetchgraph 힌트를 사용해서 엔티티 그래프를 조회했다. 이것은 엔티티 그래프에 선택한 속성만 함께 조회한다.
javax.persistence.loadgraph 속성은 엔티티 그래프에 선택한 속성뿐만 아니라 글로벌 fetch 모드가 FetchType.EAGER로 설정된 연관관계도 포함해서 함께 조회한다.
14. 컬렉션과 부가 기능
1. 컬렉션
1.1 JPA와 컬렉션
컬렉션 인터페이스|내장 컬렉션|중복 허용|순서 보관
---|---|---|---
Collection, List|PersistenceBag|O|X
Set|PersistenceSet|X|X
List +
@OrderColumn
|PersistentList|O|O1.2 Collection, List
1.3 Set
1.4 List +
@OrderColumn
@OrderColumn
을 추가하면 순서가 있는 특수한 컬렉션으로 인식한다.예제의 Board.comments에 List 인터페이스를 사용하고
@OrderColumn
을 추가했다. 따라서 Board.comments는 순서가 있는 컬렉션으로 인식된다.자바가 제공하는 List 컬렉션은 내부에 위치 값을 가지고 있다. 따라서 List의 위치값을 활용할 수 있다.
순서가 있는 컬렉션은 데이터베이스에 순서 값도 함께 관리한다.
JPA는 List 위치 값을 테이블의 POSITION 컬럼에 보관한다. 그런데 Board.comments 컬렉션은 Board 엔티티에 있지만 테이블의 일대다 관계의 특성상 위치 값은 다(N) 쪽에 저장해야 한다. 따라서 실제 POSITION 컬럼은 COMMENT 테이블에 매핑된다.
@OrderColumn
을 사용해서 List의 위치 값을 보관하면 편리할 것 같지만 실무에서 사용하기에는 단점이 많다.@OrderColumn
을 매핑하지 말고 개발자가 직접 POSITION 값을 관리하거나 다음에 설명하는@OrderBy
를 사용하길 권장한다.@OrderColumn
의 단점@OrderColumn
을 Board 엔티티에서 매핑하므로 Comment는 POSITION 값을 알 수 없다. 그래서 Comment를 INSERT 할때는 POSITION 값이 저장되지 않는다. POSITION은 Board.comments의 위치 값으므로, 이 값을 사용해서 POSITION의 값을 UPDATE하는 SQL이 추가로 발생한다.1.5
@OrderBy
@OrderBy
는 데이터베이스의 ORDER BY절을 사용해서 컬렉션을 정렬한다. 따라서 순서용 컬럼을 매핑하지 않아도 된다.@OrderBy
는 모든 컬렉션에 사용할 수 있다.@OrderBy
를 적용했는데 username desc, id asc를 사용해서 Member의 username 필드로 내림차순 정렬하고 id로 오름차순 정렬했다.@OrderBy
의 값은 JPQL의 order by절 처럼 엔티티의 필드를 대상으로 한다.@OrderBy
를 적용해서 결과를 조회하면 순서를 유지하기 위해 순서를 유지하기 위해 HashSet대신 LinkedHashSet을 내부에서 사용한다.2.
@Converter
@Converter
어노테이션을 사용하고 AttributeConverter 인터페이스를 구현해야 한다.convertToDatabaseColumn() : 엔티티의 데이터를 데이터베이스 컬럼에 저장할 데이터로 변환한다.
convertToEntityAttribute() : 데이터베이스에서 조회한 컬럼 데이터를 엔티티의 데이터로 변환한다.
컨버터는 클래스 레벨에도 설정할 수 있다. 이때는 attributeName 속성을 사용해서 어떤 필드에 컨버터를 적용할 지 명시하면 된다.
2.1 글로벌 설정
@Converter(autoApply=true)
옵션을 적용하면 된다.3. 리스너
3.1 이벤트 종류
3.2 이벤트 적용 위치
엔티티에 직접 적용
별도의 리스너 등록
기본 리스너 사용
더 세밀한 설정
4. 엔티티 그래프
엔티티를 조회할 때 연관된 엔티티들을 함께 조회하려면 글로벌 fetch 옵션을 FetchType.EAGER로 설정한다.
또는 JPQL에서 페치 조인을 사용한다.
JPA 2.1에 추가된 엔티티 그래프 기능을 사용하면 엔티티를 조회하는 시점에 함께 조회할 연관된 엔티티를 선택할 수 있다.
JPQL은 데이터를 조회하는 기능만 수행하면 되고 연관된 엔티티를 함께 조회하는 기능은 엔티티 그래프를 사용하면 된다.
엔티티 그래프는 정적으로 정의하는 Named 엔티티그래프와 동적으로 정의하는 엔티티 그래프가 있다.
4.1 Named 엔티티 그래프
@NamedENtityGraph
로 정의한다.@NamedAttributeNode
를 사용하고 그 값으로 함께 조회할 속성을 선택하면 된다.@NamedEntityGraphs
를 사용하면 된다.4.2 em.find()에서 엔티티 그래프 사용
4.3 subgraph
이 엔티티의 그래프는 Order -> Member, Order -> OrderItem, OrderItem -> Item의 객체 그래프를 함께 조회한다.
OrderItem -> Item은 Order의 객체 그래프가 아니므로 subgraphs 속성으로 정의한다. 여기서는 orderItems라는 이름의 서브 그래프가 item을 함께 조회하도록 정의했다.
Order.withAll이라는 named 엔티티 그래프를 사용해서 Order 엔티티를 조회하면 실행된 SQL은 엔티티 그래프에서 지정한 엔티티들을 함께 조회한다.
4.4 JPQL에서 엔티티 그래프 사용
Order.member는 필수 관계로 설정되어 있다.
4.5 동적 엔티티 그래프
엔티티 그래프 정리
ROOT에서 시작
이미 로딩된 엔티티
fetchgraph, loadgraph의 차이