Home > Spring > πŸƒ[Spring] μ–‘λ°©ν–₯ κ΄€κ³„λž€ λ¬΄μ—‡μΌκΉŒμš”?

πŸƒ[Spring] μ–‘λ°©ν–₯ κ΄€κ³„λž€ λ¬΄μ—‡μΌκΉŒμš”?
Spring Framework

πŸƒ[Spring] μ–‘λ°©ν–₯ κ΄€κ³„λž€ λ¬΄μ—‡μΌκΉŒμš”?

  • μ–‘λ°©ν–₯ κ΄€κ³„λŠ” 두 μ—”ν‹°ν‹°κ°€ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•  수 μžˆλŠ” 관계λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.
    • 즉, ν•œ μ—”ν‹°ν‹°μ—μ„œ λ‹€λ₯Έ μ—”ν‹°ν‹°λ₯Ό μ°Έμ‘°ν•  수 μžˆμ„ 뿐만 μ•„λ‹ˆλΌ, λ°˜λŒ€λ‘œ λ‹€λ₯Έ μ—”ν‹°ν‹°μ—μ„œλ„ 이λ₯Ό μ°Έμ‘°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ–‘λ°©ν–₯ 관계λ₯Ό μ‚¬μš©ν•˜λ©΄ 두 μ—”ν‹°ν‹° κ°„μ˜ 데이터 탐색이 μ–‘μͺ½ λ°©ν–₯으둜 λͺ¨λ‘ κ°€λŠ₯해지며, μ΄λŠ” JPAμ—μ„œ λ‹€μŒκ³Ό 같은 관계 μ–΄λ…Έν…Œμ΄μ…˜ μ‘°ν•©μœΌλ‘œ κ΅¬ν˜„λ©λ‹ˆλ‹€.
    • @OneToMany + @ManyToOne
    • @OneToOne + @OneToOne
    • @ManyToMany + @ManyToMany

1️⃣ μ–‘λ°©ν–₯ κ΄€κ³„μ˜ νŠΉμ§•.

1️⃣ μ–‘μͺ½μ—μ„œ μ°Έμ‘° κ°€λŠ₯.

  • μ–‘μͺ½ μ—”ν‹°ν‹°κ°€ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•˜μ—¬ 데이터 탐색이 κ°€λŠ₯ν•©λ‹ˆλ‹€.
    • 예λ₯Ό λ“€μ–΄, λΆ€λͺ¨ μ—”ν‹°ν‹°μ—μ„œ μžμ‹ 엔티티듀을 μ‘°νšŒν•˜κ±°λ‚˜, μžμ‹ μ—”ν‹°ν‹°μ—μ„œ λΆ€λͺ¨ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

2️⃣ 주인(Owner)κ³Ό 비주인(Inverse) μ„€μ •.

  • JPAμ—μ„œ μ–‘λ°©ν–₯ 관계λ₯Ό μ„€μ •ν•  λ•Œ, λ°˜λ“œμ‹œ 주인(Owner)κ³Ό 비주인(Inverse)을 λͺ…μ‹œν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • 주인(Owner) : μ™Έλž˜ ν‚€(Foreign Key)λ₯Ό μ‹€μ œλ‘œ κ΄€λ¦¬ν•˜λŠ” μͺ½.
  • 비주인(Inverse) : 읽기 μ „μš©μœΌλ‘œ 참쑰만 κ°€λŠ₯ν•˜λ©°, mappedBy 속성을 톡해 주인을 λͺ…μ‹œν•©λ‹ˆλ‹€.

3️⃣ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ™Έλž˜ ν‚€λŠ” ν•œμͺ½μ—λ§Œ 쑴재.

  • λ°μ΄ν„°λ² μ΄μŠ€μ˜ μ™Έλž˜ ν‚€(Foreign Key)λŠ” κ΄€κ³„μ˜ 주인에 ν•΄λ‹Ήν•˜λŠ” μ—”ν‹°ν‹°μ˜ ν…Œμ΄λΈ”μ—λ§Œ μ‘΄μž¬ν•©λ‹ˆλ‹€.

2️⃣ μ–‘λ°©ν–₯ κ΄€κ³„μ˜ κ΅¬ν˜„ ν˜•νƒœ.

1️⃣ @OneToMany + @ManyToOne

  • λΆ€λͺ¨-μžμ‹ 관계λ₯Ό κ΅¬ν˜„ν•˜λ©°, λΆ€λͺ¨λŠ” μžμ‹μ˜ μ»¬λ ‰μ…˜μ„ 가지고 있고, μžμ‹μ€ λΆ€λͺ¨λ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€.

예제: User와 UserSaveHistory

  • User μ—”ν‹°ν‹°(λΆ€λͺ¨)
    @Entity
    public class User {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
      private List<UserSaveHistory> saveHistories = new ArrayList<>();
        
      public void addSaveHistory(UserSaveHistory history) {
          saveHistories.add(history);
          history.setUser(this);
      }
        
      public void removeSaveHistory(UserSaveHistory history) {
          saveHistories.remove(history);
          history.setUser(null);
      }
    }
    
  • UserSaveHistory μ—”ν‹°ν‹°(μžμ‹)
    @Entity
    public class UserSaveHistory {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "user_id")
      private User user;
    }
    

2️⃣ @OneToOne + @OneToOne

  • 1:1 관계λ₯Ό κ΅¬ν˜„ν•˜λ©°, μ–‘μͺ½ μ—”ν‹°ν‹°κ°€ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•©λ‹ˆλ‹€.

예제: Passport와 Person

  • Person μ—”ν‹°ν‹°.
    @Entity
    public class Person {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
      private Passport passport;
    }
    
  • Passport μ—”ν‹°ν‹°.
    @Entity
    public class Passport {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @OneToOne
      @JoinColumn(name = "person_id")
      private Person person;
    }
    

3️⃣ @ManyToMany + @ManyToMany

  • λ‹€λŒ€λ‹€ 관계λ₯Ό κ΅¬ν˜„ν•˜λ©°, 쀑간 ν…Œμ΄λΈ”μ„ 톡해 두 μ—”ν‹°ν‹°κ°€ μ—°κ²°λ©λ‹ˆλ‹€.

예제: Student와 Course

  • Student μ—”ν‹°ν‹°.
    @Entity
    public class Student {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @ManyToMany
      @JoinTable(
          name = "student_course",
          joinColumns = @JoinColumn(name = "student_id"),
          inverseJoinColumns = @JoinColumn(name = "course_id")
      )
      private List<Course> courses = new ArrayList<>();
    }
    
  • Course μ—”ν‹°ν‹°.
    @Entity
    public class Course {
        
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
        
      @ManyToMany(mappedBy = "courses")
      private List<Student> students = new ArrayList<>();
    }
    

3️⃣ μ–‘λ°©ν–₯ κ΄€κ³„μ—μ„œ 주의점.

1️⃣ 주인(Owner)κ³Ό 비주인(Inverse)

  • JPAμ—μ„œλŠ” μ–‘λ°©ν–₯ 관계λ₯Ό μ„€μ •ν•  λ•Œ λ°˜λ“œμ‹œ 주인을 λͺ…μ‹œν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • 주인은 μ™Έλž˜ ν‚€(Foreign Key)λ₯Ό κ΄€λ¦¬ν•˜λ©°, 데이터 λ³€κ²½(INSERT, UPDATE)에 영ν–₯을 λ―ΈμΉ©λ‹ˆλ‹€.
  • 비주인은 mappedBy 속성을 μ‚¬μš©ν•΄ 주인을 μ§€μ •ν•˜λ©°, 읽기 μ „μš©μž…λ‹ˆλ‹€.

2️⃣ μ—°κ΄€ 관계 편의 λ©”μ„œλ“œ.

  • λΆ€λͺ¨μ™€ μžμ‹ κ°„μ˜ 관계λ₯Ό μΌκ΄€λ˜κ²Œ μœ μ§€ν•˜λ €λ©΄ 편의 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.
    • 예λ₯Ό λ“€μ–΄, λΆ€λͺ¨μ˜ addChild λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μžμ‹μ˜ λΆ€λͺ¨λ„ μžλ™μœΌλ‘œ μ„€μ •λ˜λ„λ‘ κ΅¬ν˜„ν•©λ‹ˆλ‹€.
      public void addSaveHistory(UserSaveHistory history) {
        saveHistories.add(history);
        history.setUser(this);
      }
      

πŸ™‹β€β™‚οΈ 편의 λ©”μ„œλ“œ

관계λ₯Ό λ™κΈ°ν™”ν•˜κΈ° μœ„ν•œ 편의 λ©”μ„œλ“œλž€ μ–‘λ°©ν–₯ μ—°κ΄€ κ΄€κ³„μ—μ„œ 두 μ—”ν‹°ν‹° κ°„μ˜ μ—°κ΄€ 관계λ₯Ό 일관성 있게 μœ μ§€ν•˜κΈ° μœ„ν•΄ μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œλ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.

3️⃣ 지연 λ‘œλ”©(Lazy Loading)

  • μ–‘λ°©ν–₯ κ΄€κ³„μ—μ„œλŠ” @OneToMany와 @ManyToOne λͺ¨λ‘ 기본적으둜 지연 λ‘œλ”©(Lazy Loading)을 μ‚¬μš©ν•˜μ—¬ μ„±λŠ₯을 μ΅œμ ν™”ν•©λ‹ˆλ‹€.
  • ν•„μš”μ‹œ fetch = FetchType.EAGER둜 μ„€μ •ν•  수 μžˆμ§€λ§Œ, μ΄λŠ” 데이터 λ‘œλ”© μ‹œ μ„±λŠ₯에 영ν–₯을 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.

4️⃣ λ¬΄ν•œ 루프 문제

  • μ–‘λ°©ν–₯ 관계λ₯Ό JSON으둜 직렬화할 λ•Œ, λΆ€λͺ¨μ™€ μžμ‹μ΄ μ„œλ‘œλ₯Ό μ°Έμ‘°ν•˜λ©° λ¬΄ν•œ 루프가 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν•΄κ²° 방법
      • @JsonIgnore : νŠΉμ • ν•„λ“œλ₯Ό μ§λ ¬ν™”ν•˜μ§€ μ•Šλ„λ‘ μ„€μ •.
      • @JsonManagedReference와 @JsonBackReference: Jackson λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ 관계λ₯Ό μ²˜λ¦¬ν•˜λŠ” μ–΄λ…Έν…Œμ΄μ…˜.

4️⃣ μž₯점과 단점.

1️⃣ μž₯점.

1️⃣ μ–‘μͺ½ 탐색 κΈ°λŠ₯.

  • λΆ€λͺ¨μ™€ μžμ‹ λͺ¨λ‘μ—μ„œ 관계λ₯Ό 탐색할 수 μžˆμŠ΅λ‹ˆλ‹€.

2️⃣ λͺ…ν™•ν•œ 관계 ν‘œν˜„.

  • 객체 λͺ¨λΈμ—μ„œ 두 μ—”ν‹°ν‹° κ°„μ˜ 관계λ₯Ό λͺ…ν™•ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

2️⃣ 단점.

1️⃣ 섀계 λ³΅μž‘μ„± 증가.

  • 관계λ₯Ό μ–‘μͺ½μ—μ„œ 관리해야 ν•˜λ―€λ‘œ μ½”λ“œκ°€ λ³΅μž‘ν•΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

2️⃣ μ„±λŠ₯ 문제.

  • μ–‘λ°©ν–₯ 관계λ₯Ό μ‚¬μš©ν•  경우 λΆˆν•„μš”ν•œ 데이터 λ‘œλ”©μ΄ λ°œμƒν•  수 μžˆμœΌλ―€λ‘œ λ‘œλ”© μ „λž΅μ„ μ‹ μ€‘νžˆ 선택해야 ν•©λ‹ˆλ‹€.

5️⃣ κ²°λ‘ .

  • μ–‘λ°©ν–₯ κ΄€κ³„λŠ” 두 μ—”ν‹°ν‹° 간에 μƒν˜Έ μ°Έμ‘°κ°€ ν•„μš”ν•  λ•Œ μ‚¬μš©λ˜λ©°, 섀계가 λ³΅μž‘ν•΄μ§ˆ 수 μžˆμ§€λ§Œ 데이터λ₯Ό μ–‘μͺ½ λ°©ν–₯으둜 탐색해야 ν•˜λŠ” 경우 μœ μš©ν•©λ‹ˆλ‹€.
  • JPAμ—μ„œλŠ” 주인(Owner)을 λͺ…ν™•νžˆ μ„€μ •ν•˜κ³ , 편의 λ©”μ„œλ“œλ₯Ό 톡해 관계λ₯Ό κ΄€λ¦¬ν•¨μœΌλ‘œμ¨ λ°μ΄ν„°μ˜ 일관성을 μœ μ§€ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • ν•„μš” μ—†λŠ” 경우 단방ν–₯ 관계λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 κ°„λ‹¨ν•˜κ³  μ„±λŠ₯상 μœ λ¦¬ν•©λ‹ˆλ‹€.