Spring

TIL Spring #3-3

린예쑰 2023. 12. 18. 13:42

 

Entity 연관관계

 

  • 1:1
  • N:1 
  • 1:N
  • N:M

 


 

 

 

μ‹€μ œλ‘œ ν…Œμ΄λΈ”μ„ λ”± ν•œκ°œλ§Œ μ‚¬μš©ν•˜λŠ” κ²½μš°λŠ” 거의 μ—†λ‹€.

DB table의 연관관계 Entity 객체의 연관관계 차이가 μžˆμ„κΉŒ? μžˆλ‹€λ©΄ μ–΄λ–»κ²Œ λ‹€λ₯ΌκΉŒ?

 

예λ₯Ό λ“€μ–΄, 고객이 μŒμ‹μ„ μ£Όλ¬Έν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•œλ‹€κ³  κ°€μ •ν•΄λ³΄μž.

κ·Έλ ‡λ‹€λ©΄ λ‹Ήμ—°νžˆ μ£Όλ¬Έν•˜λŠ” 고객 ν…Œμ΄λΈ” ν•˜λ‚˜, μŒμ‹ ν…Œμ΄λΈ” ν•˜λ‚˜ 총 λ‘κ°œλŠ” 기본적으둜 ν•„μš”ν•  것이닀.

그런데 고객이 μŒμ‹μ„ μ£Όλ¬Έν–ˆμ„ λ•Œ ν•΄λ‹Ή 주문건에 λŒ€ν•œκ±΄ μœ μ €? μŒμ‹? μ–΄λŠ ν…Œμ΄λΈ”μ— λͺ…μ‹œν•΄μ€˜μ•Όν• κΉŒ?

이λ₯Ό μœ μ €λ‚˜ μŒμ‹ ν…Œμ΄λΈ”μ— λ„£κ²Œ 되면 λ‘˜λ‹€ μ‚¬μš©μž, μŒμ‹ λ‚΄μš©μ΄ μ€‘λ³΅λ˜λŠ” μ΄μŠˆκ°€ λ°œμƒν•œλ‹€. 

 

 

μ΄λ ‡κ²Œ user_id μ»¬λŸΌμ— 1번 2번 μœ μ €κ°€ μ£Όλ¬Έν–ˆλ‹€. λΌλŠ” ν‘œν˜„ 방식도 μ•ˆλ κΉŒ?

일단 RDBMS μƒμ—μ„œλŠ” λΆˆκ°€λŠ₯ν•˜λ‹€. 라고 μ΄ν•΄ν•˜μž.

 

 

- 2 row 둜 ν‘œν˜„μ΄ 되긴 ν•œλ‹€! κ·ΈλŸ¬λ‚˜ μŒμ‹μ˜ 이름이 λΆˆν•„μš”ν•˜κ²Œ μ€‘λ³΅λœλ‹€!

 

 

 

<ν•΄κ²°λ°©μ•ˆ>

μ£Όλ¬Έ 정보λ₯Ό 넣을 쀑간 ν…Œμ΄λΈ”μ„ λ”°λ‘œ λ§Œλ“€μž!

 

 

정리해보면 고객도 μŒμ‹ μ—¬λŸ¬κ°œλ₯Ό μ£Όλ¬Έν•  수 있고 μŒμ‹ λ˜ν•œ μ—¬λŸ¬ κ³ κ°μ—κ²Œ 주문될 수 μžˆλ‹€. -> N:M 관계!

N:M 관계인 ν…Œμ΄λΈ”μ˜ 연관관계λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ€‘κ°„ν…Œμ΄λΈ”μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

 

<ν…Œμ΄λΈ”μ˜ λ°©ν–₯?>

- DB ν…Œμ΄λΈ” κ°„ λ°©ν–₯이 μžˆμ„κΉŒ? 

- ν…Œμ΄λΈ”μ—μ„œλŠ” μ›ν•˜λŠ” 정보λ₯Ό 고객, μŒμ‹ μ–΄λ”œ κΈ°μ€€μœΌλ‘œ ν•˜λ“  JOIN을 μ‚¬μš©ν•΄ μ‘°νšŒν•  수 있으며

λ‘˜μ˜ κ²°κ³ΌλŠ” μ˜€μ°¨μ—†μ΄ λ˜‘κ°™λ‹€. -> λ°©ν–₯의 κ°œλ… μ—†μŒ 

 

 


κ·Έλ ‡λ‹€λ©΄ JPA Entityμ—μ„œλŠ”?

 

결둠적으둜 Entityμ—μ„œλŠ” κ°€λŠ₯ν•˜λ‹€. 이것이 DB에 μ μš©λ˜λŠ” 것은 μ•„λ‹ˆμ§€λ§Œ 

객체 ν˜•νƒœμ—μ„œλŠ” μ°Έμ‘°λ₯Ό 단방ν–₯ μ–‘λ°©ν–₯ 두가지 λͺ¨λ‘ λ‹€ κ°€λŠ₯ν•˜λ‹€.

 

μ°Έκ³ ) μ™Έλž˜ ν‚€ 주인만이 μ™Έλž˜ ν‚€λ₯Ό 등둝, μˆ˜μ •, μ‚­μ œν•  수 있으며 주인이 μ•„λ‹Œ μͺ½μ€ 였직 읽기만 κ°€λŠ₯ν•˜λ‹€.

@JoinColumn μ• λ„ˆν…Œμ΄μ…˜μœΌλ‘œ 주인 μ§€μ •

 

예제λ₯Ό 톡해 ν•΄λ‹Ή caseλ³„λ‘œ ν™•μΈν•΄λ³΄μž. (일관성을 μœ„ν•΄ μŒμ‹ Entitydμ—λ§Œ 주인 κΆŒν•œμ„ λΆ€μ—¬ν•œλ‹€.)

 

 

 

<1:1>

 

# 1:1 관계 - 단방ν–₯

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

 

- κ°€μž₯ κ°„λ‹¨ν•˜λ‹€ 주인인 엔티티에 1:1 관계λ₯Ό λ‚˜νƒ€λ‚΄λŠ” @OneToOne μ• λ„ˆν…Œμ΄μ…˜ μž‘μ„±, μ™Έλž˜ν‚€μ˜ μ£ΌμΈμž„μ„ μ•Œλ €μ£ΌλŠ” @JoinColumn μ• λ„ˆν…Œμ΄μ…˜ μž‘μ„±ν•΄μ£Όλ©΄ λœλ‹€.

 

# 1:1 관계 - μ–‘λ°©ν–₯

-μ™Έλž˜ν‚€μ˜ 주인

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

 

- μ™Έλž˜ν‚€ 주인 μ•„λ‹Œ μ—”ν‹°ν‹°

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToOne(mappedBy = "user")
    private Food food;
}

 

- μ–‘λ°©ν–₯ κ΄€κ³„μ—μ„œλŠ” 주인이 μ•„λ‹Œ 엔티티에 ν•˜λ‚˜ μ„€μ •ν•  것이 μžˆλŠ”λ° λ°”λ‘œ mappedBy μ˜΅μ…˜μ΄λ‹€.

 μ™Έλž˜ν‚€μ˜ 주인을 μ§€μ •ν•΄μ€„λ•Œ ν•΄λ‹Ή μ˜΅μ…˜μ„ μ‚¬μš©ν•˜λ©° μ§€κΈˆ λ³΄μ΄λŠ” "user"λΌλŠ” 속성값은 User 클래슀λ₯Ό μ˜λ―Έν•˜λŠ” 것이 μ•„λ‹Œ

 μ™Έλž˜ν‚€μ˜ 주인인 Food μ—”ν‹°ν‹°μ˜ ν•„λ“œλͺ…을 μ˜λ―Έν•œλ‹€. (ν—·κ°ˆλ¦¬μ§€ μ•Šκ²Œ 주의) 

 

- λ””ν΄νŠΈ μ˜΅μ…˜ 적용되기 λ•Œλ¬Έμ— μƒλž΅μ΄ κ°€λŠ₯ν•˜κΈ΄ν•˜λ‚˜ λͺ¨ν˜Έν•œ 경우 JPAμ—μ„œ μ€‘κ°„ν…Œμ΄λΈ”μ„ 생성할 μˆ˜λ„ 있기 λ•Œλ¬Έμ— λ°˜λ“œμ‹œ λͺ…μ‹œν•΄μ£ΌλŠ” 것이 μ’‹λ‹€.

 

<N:1>

 

# N:1 관계 - 단방ν–₯

- 이도 1:1 단방ν–₯ κ²½μš°μ™€ λ§ˆμ°¬κ°€μ§€λ‘œ 주인 μ™Έλž˜ν‚€ μͺ½μ— μ• λ„ˆν…Œμ΄μ…˜λ§Œ 잘 μž‘μ„±ν•΄μ£Όλ©΄ λœλ‹€.

 λŒ€μ‹  OneToOne λŒ€μ‹  @ManyToOne μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•œλ‹€.

 

 

# N:1 관계 - μ–‘λ°©ν–₯

- 이 κ²½μš°λ„ 1:1 μ–‘λ°©ν–₯ κ²½μš°μ™€ λΉ„μŠ·ν•˜λ‹€!

- λ‹€λ§Œ, μ™Έλž˜ν‚€μ˜ 주인이 μ•„λ‹Œ ν΄λž˜μŠ€μ— @OneToMany λΌλŠ” μ• λ„ˆν…Œμ΄μ…˜ μž‘μ„± ν•„μš”.

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToOne //N:1 관계λ₯Ό λ‚˜νƒ€λ‚΄λŠ” μ• λ„ˆν…Œμ΄μ…˜
    @JoinColumn(name = "user_id") //μ™Έλž˜ν‚€μ˜ 주인을 μ§€μ •ν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜
    private User user;
}

 

 

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "user") //μ™Έλž˜ν‚€μ˜ 주인을 μ•Œλ €μ€Œ.
    								//User 클래슀 μ•„λ‹ˆκ³ 
                                    //μ™Έλž˜ν‚€μ˜ 주인인 Food 클래슀의 ν•„λ“œμΈ userμž„!
    private List<Food> foodList = new ArrayList<>();
}

 

- ν•˜λ‚˜ μ•Œμ•„ λ‘˜ 것은 μ–‘λ°©ν–₯ μ°Έμ‘°λ₯Ό μœ„ν•΄ 고객 μ—”ν‹°ν‹°μ—μ„œ μžλ°” μ»¬λ ‰μ…˜μ„ μ‚¬μš©ν•˜μ—¬ μŒμ‹ μ—”ν‹°ν‹°λ₯Ό μ°Έμ‘°ν•œλ‹€! (μŒμ‹μ„ ν•˜λ‚˜λ§Œ μ£Όλ¬Έν•  것은 μ•„λ‹ˆκΈ°μ— μ—¬λŸ¬κ°œλ₯Ό λ‹΄μ•„μ•Όν•œλ‹€.)

 

<1:N>

 

@OneToMany μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš©

μœ„μ—μ„œ λ§ˆμ°¬κ°€μ§€λ‘œ μ™Έλž˜ν‚€ 주인을 μŒμ‹μœΌλ‘œ λ‘μž.

그런데 1:N κ΄€κ³„μ—μ„œ N의 ν…Œμ΄λΈ”μ΄ μ™Έλž˜ν‚€λ₯Ό κ°€μ§ˆ 수 있기 λ•Œλ¬Έμ— μ‹€μ œ μ™Έλž˜ν‚€λŠ” 고객에 λ‘”λ‹€.

 

-단방ν–₯

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToMany
    @JoinColumn(name = "food_id") // users ν…Œμ΄λΈ”μ— food_id 컬럼
    private List<User> userList = new ArrayList<>();
}

 

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

N 관계인 User에 μž μ‹œ μ™Έλž˜ν‚€λ₯Ό λ³΄κ΄€ν•˜κ³  μŒμ‹μ—μ„œ κ΄€λ¦¬ν•œλ‹€. 창고같은 λŠλ‚Œ..

 

- μ–‘λ°©ν–₯ 

ν•΄λ‹Ή κ΄€κ³„μ—μ„œλŠ” 일반적으둜 μ–‘λ°©ν–₯ 관계가 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€.

mappedBy 속성 λ˜ν•œ μ œκ³΅ν•˜μ§€ μ•ŠλŠ”λ‹€.

@JoinColumn에 insert/update에 false 값을 μ£Όμ–΄ μ–‘λ°©ν–₯처럼 μ„€μ •ν•  μˆ˜λŠ” μžˆλ‹€. 

 

 

 

<N:M>

@ManyToMany μ• λ„ˆν…Œμ΄μ…˜ μ‚¬μš©

 

κΈ€ μ•žλ¨Έλ¦¬μ—μ„œ 봀던 κ²ƒμ²˜λŸΌ ν•΄λ‹Ή κ΄€κ³„μ—μ„œλŠ” μ€‘κ°„ν…Œμ΄λΈ”μ„ λ§Œλ“€μ–΄ μ‚¬μš©ν•œλ‹€. (orders)

 

- 단방ν–₯

@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @ManyToMany
    @JoinTable(name = "orders")// 쀑간 ν…Œμ΄λΈ” 생성
    joinColumns = @JoinColumn(name = "food_id"), // ν˜„μž¬ μœ„μΉ˜μΈ Food Entity μ—μ„œ 쀑간 ν…Œμ΄λΈ”λ‘œ 쑰인할 컬럼 μ„€μ •
    inverseJoinColumns = @JoinColumn(name = "user_id")) // λ°˜λŒ€ μœ„μΉ˜μΈ User Entity μ—μ„œ 쀑간 ν…Œμ΄λΈ”λ‘œ 쑰인할 컬럼 μ„€μ •
    private List<User> userList = new ArrayList<>();
}

 

- ν•΄λ‹Ή orders ν…Œμ΄λΈ”μ€ JPA에 μ˜ν•΄ μžλ™μœΌλ‘œ λ§Œλ“€μ–΄μ§„λ‹€. PK μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. 

 

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

 

- μ–‘λ°©ν–₯

 

- μ™Έλž˜ν‚€ 주인이 μ•„λ‹Œ 고객의 μ½”λ“œλ§Œ λ³€κ²½λœλ‹€.

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToMany(mappedBy = "userList")
    private List<Food> foodList = new ArrayList<>();
}

 

# N:M μ–‘λ°©ν–₯ ν…ŒμŠ€νŠΈ μ½”λ“œ

@Test
@Rollback(value = false)
@DisplayName("NλŒ€M μ–‘λ°©ν–₯ ν…ŒμŠ€νŠΈ : 객체와 μ–‘λ°©ν–₯의 μž₯점 ν™œμš©")
void test5() {

    User user = new User();
    user.setName("Robbie");

    User user2 = new User();
    user2.setName("Robbert");


    // addUserList() λ©”μ„œλ“œλ₯Ό 생성해 user 정보λ₯Ό μΆ”κ°€ν•˜κ³ 
    // ν•΄λ‹Ή λ©”μ„œλ“œμ— 객체 ν™œμš©μ„ μœ„ν•΄ user 객체에 food 정보λ₯Ό μΆ”κ°€ν•˜λŠ” μ½”λ“œλ₯Ό μΆ”κ°€ν•©λ‹ˆλ‹€. user.getFoodList().add(this);
    Food food = new Food();
    food.setName("아보카도 ν”Όμž");
    food.setPrice(50000);
    food.addUserList(user);
    food.addUserList(user2);

    Food food2 = new Food();
    food2.setName("고ꡬ마 ν”Όμž");
    food2.setPrice(30000);
    food2.addUserList(user);


    userRepository.save(user);
    userRepository.save(user2);
    foodRepository.save(food);
    foodRepository.save(food2);

    // User λ₯Ό 톡해 food 의 정보 쑰회
    System.out.println("user.getName() = " + user.getName());

    List<Food> foodList = user.getFoodList();
    for (Food f : foodList) {
        System.out.println("f.getName() = " + f.getName());
        System.out.println("f.getPrice() = " + f.getPrice());
    }
}

 

- food.addUserList λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•΄ 보닀 객체지ν–₯적인 λ°©λ²•μœΌλ‘œ μ™Έλž˜ν‚€λ₯Ό μ„€μ •ν•΄μ€€λ‹€.

 

<μ€‘κ°„ν…Œμ΄λΈ”μ„ 직접 μƒμ„±ν•˜μ—¬ μž‘μ„±ν•΄λ³΄κΈ°>

- order ν…Œμ΄λΈ”μ„ 직접 μƒμ„±ν•΄μ„œ μ‚¬μš©ν•œλ‹€. -> λ³€κ²½λ°œμƒμ‹œ μ»¨νŠΈλ‘€ν•˜κΈ° 쉽기 λ•Œλ¬Έμ— ν™•μž₯성에 μš©μ΄ν•˜λ‹€.

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

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

 

- 이제 μ™Έλž˜ν‚€μ˜ 주인은 μŒμ‹, 고객이 μ•„λ‹Œ 주문이 λœλ‹€.