Post

02 JPA Entity Join 설정

02 JPA Entity Join 설정

1. Note

1. Note

  • 기존에는 SQL로 Join으로 작업을 진행했고 디비에서 알아서 필터가 되는 상황이였다면, JPA는 엔티티에서 Join을 설정하고 진행함.

2. N+1 패턴

  • Join을 사용하다보면 N+1 패턴이 발생하고 그와 관련된 여러가지 개념이 필요
  • 상황
    • user(1)와 Order(N) 테이블이 있을때 Lazy 전략을 사용할 경우에,
    • 초기에 User를 조회하면, 캐시에 user 테이블정보만 있고 Order는 없음
    • 이때 Order가 필요한 경우가 생겨버리면,
    • 필요한 Order만큼 N번씩 조회하는 상황이 생김.
    • 나는 조회를 1번했지만 캐시에 전체 Order가 없기 때문에 필요한 order 엔티티를 계속 조회하는 상황.

2. JPA Entity Join

1. 관계정의

애노테이션설명주로 사용되는 곳
@ManyToOne현재 엔티티가 FK를 가지고 있고, 대상 엔티티가 N:1 관계일 때Order → User
@OneToMany대상 엔티티가 FK를 가지고 있는 N:1 관계를 역방향에서 접근할 때User → Order
@OneToOne1:1 관계, FK를 어느 쪽에 둘지에 따라 주체/참조를 결정User → UserProfile
@ManyToManyN:N 관계, 중간 테이블을 통해 조인Student ↔ Course

2. 조인 컬럼 관련

애노테이션설명사용 예시
@JoinColumnFK 컬럼을 직접 지정, 주로 ManyToOne, OneToOne에서 사용@JoinColumn(name="user_id")
referencedColumnNameFK가 참조할 대상 컬럼명 지정 (기본: 대상 테이블 PK)@JoinColumn(name="user_id", referencedColumnName="id")
nullableFK 컬럼 null 허용 여부@JoinColumn(nullable=false)
uniqueFK 컬럼의 유니크 제약@JoinColumn(unique=true)

3. 중간 테이블

애노테이션설명
@JoinTableN:N 관계에서 중간 테이블 정의
joinColumns현재 엔티티 FK 컬럼 정의
inverseJoinColumns상대 엔티티 FK 컬럼 정의
1
2
3
4
5
6
7
  @ManyToMany
  @JoinTable(
      name="student_course",
      joinColumns=@JoinColumn(name="student_id"),
      inverseJoinColumns=@JoinColumn(name="course_id")
  )
  private List<Course> courses;

4. 기타 옵션 관련

애노테이션/옵션설명
fetchLAZY / EAGER 로딩 전략 지정 (@ManyToOne(fetch=FetchType.LAZY))
cascade연관 엔티티에 대한 영속성 전파 (ALL, PERSIST, REMOVE 등)
mappedBy양방향 관계에서 “FK가 있는 필드명” 지정, OneToMany/OneToOne에서 주로 사용
orphanRemoval부모 엔티티에서 제거하면 자식 엔티티도 삭제 여부 결정
  • fetch의 Lazy, Eager

    구분LAZYEAGER
    조회 시점필요할 때즉시
    초기 SQL부모만 조회부모 + 연관
    객체 상태프록시실제 객체
    성능보통 유리상황에 따라 비효율
    기본값@OneToMany / @ManyToMany@ManyToOne / @OneToOne

3. Join 설정

1. Note

  • Order 와 User기준으로,
  • User는 여러주문이 가능할때,
  • Order N : user 1 => N:1 구조

2. ManyToOne

  • Order(Many)는 user(One) 한명에게 여러값을 가짐(2번과 3번)
  • Order Entity
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
     @Entity
     public class Order {
    
     @Id
     @GeneratedValue
     private Long id;
    
     // Order Entity에서는 user_id라는 컬럼이 생기고
     // user의 id값을 FK로하고
     // 엔티티에서 필드는 user로 설정함
     @ManyToOne(fetch = FetchType.LAZY)
     @JoinColumn(name = "user_id")      
     private User user;                 
       
     }
    

3. OneToMany

  • user(One)는 여러 Order(Many)를 가짐(2번과 3번)
  • User Entity
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    @Entity
    public class User {
    
     @Id
     @GeneratedValue
     private Long id;
    
     // User테이블은 FK로 별도 컬럼 생성 X
     // 무엇을 FK할지는 ManytoOne에서 결정하고,
     // FK를 건쪽의 필드값(Order 엔티티의 user라는 필드값이 mappedABY로)
     // 여러가지 Order을 가지기때문에 List로 받게됨.
     @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
     private List<Order> orders = new ArrayList<>(); 
    }
    

4. OneToOne

  • User라는 테이블과 userInfo 테이블등 1:1 매핑
  • userProfile Entity(FK가 있는 쪽)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      @Entity
      public class UserProfile {
      
       @Id
       @GeneratedValue
       private Long id;
      
       @OneToOne
       @JoinColumn(name = "user_id") // FK UserProfile 테이블에 존재
       private User user;
      }
    
  • User Entity(FK가 없는 쪽)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      @Entity
      public class User {
      
       @Id
       @GeneratedValue
       private Long id;
      
       @OneToOne(mappedBy = "user")
       private UserProfile profile;
      }
    

5. ManyToMany

  • N:N인 상황에서는 중간 테이블을 두고 조인을 함.
  • Student Entity
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     @Entity
     public class Student {
        
         @Id
         @GeneratedValue
         private Long id;
        
         private String name;
        
         // StudentCourse 중간 테이블을 통해 Course와 연결
         @OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
         private List<StudentCourse> studentCourses = new ArrayList<>();
     }
    
  • Cource Entity
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     @Entity
     public class Course {
        
         @Id
         @GeneratedValue
         private Long id;
        
         private String title;
        
         // StudentCourse 중간 테이블을 통해 Student와 연결
         @OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true)
         private List<StudentCourse> studentCourses = new ArrayList<>();
     }
    
  • StudentCoure(중간 테이블)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     @Entity
     public class StudentCourse {
        
         @Id
         @GeneratedValue
         private Long id;
        
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "student_id")
         private Student student;
        
         @ManyToOne(fetch = FetchType.LAZY)
         @JoinColumn(name = "course_id")
         private Course course;
        
         private LocalDate enrolledDate; // 선택적: 수강일 등 추가 정보
     }
    
This post is licensed under CC BY 4.0 by the author.