NamedEntityGraph를 왜쓰는 거지?

fart man·2026년 1월 26일

EntityGraph 배우면서

NamedEntityGraph를 쓰는 방법과 그냥 EntityGraph에 attributePaths를 지정하는 방법, 두가지가 있다는 사실을 배웠다.

그러면서 든 생각은... 왜 궂이 NamedEntityGraph를 쓰는거지? 걍 attributePaths에다가 다 적으면 되는 거 아닌가? 였다.

그래서 AI한테 물어보니까 나름 실용적인 이유가 있었다.

예를 들어 이런 Entity가 있다 치면

    @Entity(name = "Parent")
    @Table(name = "parents")
    public static class Parent {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child1;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child2;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child3;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child4;
    }

attributePaths 만으로 적을려면 이렇게 적어야 한다.

    @EntityGraph(attributePaths={"child1", "child2", "child3", "child4"})
    @Query("SELECT p from Parent p where p.id = :id")
    Parent getParentAdHocEntityGraph(Long id);

근데 NamedEntityGraph를 쓰면

    @NamedEntityGraph(name = "Parent.children",
        attributeNodes = {
            @NamedAttributeNode("child1"),
            @NamedAttributeNode("child2"),
            @NamedAttributeNode("child3"),
            @NamedAttributeNode("child4"),
        }
    )
    @Entity(name = "Parent")
    @Table(name = "parents")
    public static class Parent {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child1;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child2;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child3;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child4;

        public Long getId() { return this.id; }
    }

나중에 한번에 묶어서 표현 할 수 있다.

    @EntityGraph("Parent.children")
    @Query("SELECT p from Parent p where p.id = :id")
    Parent getParentNamedEntityGraph(Long id);

demo

package com.entitygraph;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import jakarta.persistence.*;

import java.util.Optional;
import java.util.List;
import java.util.ArrayList;

@SpringBootApplication
public class EntityGraphTest {
    // =========================
    // entities
    // =========================

    @NamedEntityGraph(name = "Parent.children",
        attributeNodes = {
            @NamedAttributeNode("child1"),
            @NamedAttributeNode("child2"),
            @NamedAttributeNode("child3"),
            @NamedAttributeNode("child4"),
        }
    )
    @Entity(name = "Parent")
    @Table(name = "parents")
    public static class Parent {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        // STUDY: 
        // OneToOne에서 이렇게 fetch=FetchType.LAZY를 해도 지연 로딩이 안되다는 사실 아셨나요?
        //
        // 이에 대해서는 https://devlog-wjdrbs96.tistory.com/432 참고
        // 
        // 사실 여기서는 fetch = FetchType.LAZY를 빼는 것도 맞는거 같습니다.
        // 그러면 신기하게도 각 child를 select 하는 대신에
        // join을 쓰기 시작합니다. (왠지는 모르겠지만....)
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child1;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child2;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child3;
        @OneToOne(mappedBy = "parent", fetch = FetchType.LAZY) private Child child4;

        public Long getId() { return this.id; }
    }

    @Entity(name = "Child")
    @Table(name = "children")
    public static class Child {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        @Column private String name;

        @OneToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "parent_id")
        private Parent parent;
    }

    @Component
    public class Demo implements CommandLineRunner {
        private final ParentRepo repo;

        public Demo(ParentRepo repo) {
            this.repo = repo;
        }

        @Override
        public void run(String... args) {
            System.out.println("================================");
            Parent p = repo.saveAndFlush(new Parent());
            System.out.println("================================");
            repo.findById(p.getId());
            repo.flush();
            System.out.println("================================");
            repo.getParentNamedEntityGraph(p.getId());
            repo.flush();
            System.out.println("================================");
            repo.getParentAdHocEntityGraph(p.getId());
            repo.flush();
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(EntityGraphTest.class, args);
    }
}
package com.entitygraph;

import org.springframework.data.jpa.repository.JpaRepository;
import com.entitygraph.EntityGraphTest.Parent;
import org.springframework.data.jpa.repository.Query;

import org.springframework.data.jpa.repository.EntityGraph;

public interface ParentRepo extends JpaRepository<EntityGraphTest.Parent, Long> {

    @EntityGraph("Parent.children")
    @Query("SELECT p from Parent p where p.id = :id")
    Parent getParentNamedEntityGraph(Long id);

    @EntityGraph(attributePaths={"child1", "child2", "child3", "child4"})
    @Query("SELECT p from Parent p where p.id = :id")
    Parent getParentAdHocEntityGraph(Long id);
}

0개의 댓글