JPA EntityGraph 활용하기
JPA EntityGraph
는 복잡한 엔티티 간의 관계를 효율적으로 조회하기 위한 강력한 도구입니다. EntityGraph
는 JPQL
또는 Criteria API
를 사용하지 않고도 관련 엔티티를 쉽게 fetch
하는 방법을 제공합니다. 특히 복잡한 관계를 가진 엔티티를 조회할 때 N+1 문제를 예방하는 데 유용합니다.
1. EntityGraph란?
EntityGraph
는 JPA
2.1에서 도입된 기능으로, 특정 엔티티를 조회할 때 관련 엔티티들을 한 번에 함께 조회할 수 있도록 도와줍니다. 이를 통해 쿼리의 성능을 최적화할 수 있으며, 특히 엔티티 간의 연관 관계가 많을 경우 유용합니다.
보통 JPA
에서 엔티티를 조회할 때, 연관된 엔티티들은 LAZY
로딩에 의해 필요할 때 조회됩니다. 하지만 이 경우, 연관된 엔티티들이 여러 번의 추가 쿼리로 인해 불필요하게 많은 데이터베이스 접근이 발생할 수 있습니다. 이를 방지하기 위해 EntityGraph
를 사용하여 필요한 연관된 엔티티들을 즉시 로딩(EAGER Fetching
)으로 한 번에 가져올 수 있습니다.
2. EntityGraph 사용 방법
EntityGraph
는 크게 두 가지 방식으로 사용됩니다: 어노테이션을 사용하여 정의하는 방식과, JPQL
쿼리에 동적으로 추가하는 방식입니다.
2.1. 어노테이션 사용하여 EntityGraph 정의
어노테이션을 사용하여 EntityGraph
를 사전 정의할 수 있습니다. 예를 들어, User
엔티티와 Address
엔티티가 연관되어 있는 상황에서 User
를 조회할 때 Address
도 함께 조회하고 싶다면, 아래와 같이 EntityGraph
를 정의할 수 있습니다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(fetch = FetchType.LAZY)
private Address address;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String city;
private String street;
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String item;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
}
이제 User
엔티티에서 EntityGraph
를 정의해보겠습니다. 우선 조금 복잡하지만 재사용이 가능한 방식에 대해 살펴보겠습니다.
@Entity
@NamedEntityGraph(
name = "User.withAddress",
attributeNodes = @NamedAttributeNode("address")
)
@NamedEntityGraph(
name = "User.withAddressAndOrders",
attributeNodes = {
@NamedAttributeNode("address"),
@NamedAttributeNode("orders")
}
)
public class User {
// 기존 필드 및 메소드
}
위와 같이 @NamedEntityGraph
를 사용하여 다수의 EntityGraph
를 정의하는 것이 가능합니다. 필요에 따라 선택적으로 사용하거나 더 복잡한 정의를 적용할 때 사용할 수 있습니다.
User.withAddress
라는 이름의 그래프는 User
엔티티를 조회할 때 address
필드를 즉시 로딩하도록 설정합니다.
User.withAddressAndOrders
라는 이름의 그래프는 User
엔티티를 조회할 때 address
필드 및 orders
필드를 즉시 로딩하도록 설정합니다.
2.2. EntityGraph를 사용한 데이터 조회
이제 정의된 EntityGraph
를 활용하여 데이터를 조회해보겠습니다. 이때 EntityGraph
는 EntityManager
또는 Spring Data JPA
의 Repository
에서 사용할 수 있습니다.
1. EntityManager
사용 시
public User findUserWithAddress(Long id) {
EntityGraph<?> entityGraph = entityManager.getEntityGraph("User.withAddress");
return entityManager.find(User.class, id, Collections.singletonMap("javax.persistence.fetchgraph", entityGraph));
}
2. Spring Data JPA의 Repository
사용 시
Spring Data JPA의 Repository
에서는 메서드에 @EntityGraph
어노테이션 붙여서 간편하게 사용할 수 있습니다.
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(value = "User.withAddress", type = EntityGraph.EntityGraphType.FETCH)
Optional<User> findById(Long id);
}
3. attributePaths
로 빠르게 적용
@NamedEntityGraph
를 사용하지 않고 Repository
에서 attributePaths
를 이용해 다음과 같이 사용하는 것도 가능합니다. 복잡한 설정이 필요하지 않다면 이 방법이 가장 사용하기 쉽고 단순하여 개인적으로 선호하는 방식입니다.
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = {"address", "orders"})
Optional<User> findById(Long id);
}
위 예제에서는 findById
메서드를 호출할 때, User
와 Address
, Order
가 함께 페치됩니다.
실제 SQL 쿼리는 다음과 같이 실행됩니다.
SELECT
u.id AS id1_0_,
u.name AS name2_0_,
a.id AS id1_1_,
a.city AS city2_1_,
a.street AS street3_1_,
o.id AS id1_2_,
o.item AS item2_2_,
o.user_id AS user_id3_2_
FROM
user u
LEFT JOIN
address a
ON
u.address_id = a.id
LEFT JOIN
orders o
ON
u.id = o.user_id
WHERE
u.id = 1;
만약 EntityGraph
를 사용하지 않고 기존의 LAZY
방식이나 EAGER
방식으로 조회한 경우라면 다음과 유사하게 3번의 SQL문이 실행되었을 것입니다.
SELECT
u.id AS id1_0_,
u.address_id AS address_id3_0_,
u.name AS name2_0_
FROM
user u
WHERE
u.id = 1;
SELECT
a.id AS id1_1_,
a.city AS city2_1_,
a.street AS street3_1_
FROM
address a
WHERE
a.id = ?;
SELECT
o.id AS id1_1_,
o.item AS item2_1_,
o.user AS user3_1_
FROM
order o
WHERE
o.user.id = ?;
3. Dynamic EntityGraph 사용
정적인 EntityGraph
정의 외에도, 동적으로 엔티티 그래프를 생성하여 사용할 수도 있습니다. 이는 상황에 따라 다른 엔티티 관계를 가져와야 할 때 유용합니다.
public List<User> findUsersWithDynamicGraph() {
EntityGraph<User> entityGraph = entityManager.createEntityGraph(User.class);
entityGraph.addAttributeNodes("address");
return entityManager.createQuery("SELECT u FROM User u", User.class)
.setHint("javax.persistence.fetchgraph", entityGraph)
.getResultList();
}
위 예제에서는 EntityManager
를 통해 동적으로 EntityGraph
를 생성하고 이를 사용하여 쿼리를 수행합니다.
4. 요약
EntityGraph
는 복잡한 엔티티 간의 연관 관계를 효율적으로 처리하고, 불필요한 쿼리 호출을 줄이며, 성능을 최적화할 수 있는 강력한 도구입니다. 특히, 어노테이션 기반의 정적 정의와 동적 생성 방식을 통해 유연하게 사용할 수 있습니다. JPA를 사용할 때 발생할 수 있는 N+1 문제를 해결하기 위한 좋은 방법 중 하나이므로, 상황에 맞게 활용하는 것이 중요합니다.