๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๋ฐฑ์—”๋“œ/spring&springboot

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„_Spring Boot ์ˆ™๋ จ์ฃผ์ฐจ] JPA ํ•œ ๊ฑธ์Œ ๋” ๋‚˜์•„๊ฐ€๊ธฐ(Entity ์—ฐ๊ด€๊ด€๊ณ„, 1๋Œ€ 1, N๋Œ€ 1, 1๋Œ€ N, N๋Œ€ M)

by alswlfl 2026. 6. 30.

Entity ์—ฐ๊ด€ ๊ด€๊ณ„

์ฃผ๋ฌธ APP DB table ์„ค๊ณ„

๊ณ ๊ฐ(users) ํ…Œ์ด๋ธ”

id name
1 Robbie
2 Robbert
create table users
(
    id   bigint not null auto_increment,
    name varchar(255),
    primary key (id)
);

 

์Œ์‹(food) ํ…Œ์ด๋ธ”

id name price
1 ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ 15000
2 ์–‘๋… ์น˜ํ‚จ 20000
3 ๊ณ ๊ตฌ๋งˆ ํ”ผ์ž 30000
4 ์•„๋ณด์นด๋„ ํ”ผ์ž 50000
create table food
(
    id    bigint not null auto_increment,
    name  varchar(255),
    price float(53) not null,
    primary key (id)
);

DB ํ…Œ์ด๋ธ” ๊ฐ„์˜ ์—ฐ๊ด€ ๊ด€๊ณ„

๐Ÿšจ ์—ฐ๊ด€ ๊ด€๊ณ„ ๊ณ ๋ฏผ

[๊ณ ๊ฐ ํ…Œ์ด๋ธ” ์ž…์žฅ]

ํ•œ ๋ช…์˜ ๊ณ ๊ฐ์€ ์Œ์‹์„ ์—ฌ๋Ÿฌ๊ฐœ ์ฃผ๋ฌธํ•  ์ˆ˜ ์žˆ์Œ → ๊ณ ๊ฐ๊ณผ ์Œ์‹์€ 1 ๋Œ€ N ๊ด€๊ณ„

  • ๊ณ ๊ฐ ํ…Œ์ด๋ธ”์— ์ฃผ๋ฌธํ•œ ์Œ์‹์˜ ์ •๋ณด๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด food_id ์ปฌ๋Ÿผ ์ถ”๊ฐ€
id name food_id
1 Robbie 1
2 Robbert 1
3 Robbie 2
  • Robbie ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋ฉด ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๊ณ ๊ฐ์˜ ์ด๋ฆ„์ด ์ค‘๋ณต๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒํ•จ

[์Œ์‹ ํ…Œ์ด๋ธ” ์ž…์žฅ]

ํ•˜๋‚˜์˜ ์Œ์‹์€ ์—ฌ๋Ÿฌ๋ช…์˜ ๊ณ ๊ฐ์—๊ฒŒ ์ฃผ๋ฌธ๋  ์ˆ˜ ์žˆ์Œ → ์Œ์‹๊ณผ ๊ณ ๊ฐ์€ 1๋Œ€ N ๊ด€๊ณ„

  • ์Œ์‹ ํ…Œ์ด๋ธ”์— ์ฃผ๋ฌธํ•œ ๊ณ ๊ฐ์˜ ์ •๋ณด๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด user_id ์ปฌ๋Ÿผ ์ถ”๊ฐ€
id name price user_id
1 ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ 15000 1
2 ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ 15000 2
3 ์–‘๋… ์น˜ํ‚จ 20000 1
  • ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ์„ ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ์ฃผ๋ฌธํ•˜๊ฒŒ ๋˜์—ˆ์„ ๋•Œ, ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์Œ์‹์˜ ์ด๋ฆ„์ด ์ค‘๋ณต๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒํ•จ

โ€ผ๏ธ ์œ„ ๋ฌธ์ œ ํ•ด๊ฒฐ๋ฐฉ๋ฒ• โ–บ ์ฃผ๋ฌธ(Order) ํ…Œ์ด๋ธ” ์ƒ์„ฑ

์ฃผ๋ฌธ ํ…Œ์ด๋ธ”์€ ์ฃผ๋ฌธ์— ๋Œ€ํ•œ ์ •๋ณด ๊ธฐ๋ก

create table orders
(
    id         bigint not null auto_increment,
    user_id    bigint,
    food_id    bigint,
    order_date date,
    primary key (id)
);

 

์ฃผ๋ฌธ ํ…Œ์ด๋ธ”์—์„œ์˜ ๊ณ ๊ฐ/์Œ์‹ ํ…Œ์ด๋ธ” ์™ธ๋ž˜ํ‚ค ์„ค์ •

alter table orders
    add constraint orders_user_fk
        foreign key (user_id)
            references users (id);

alter table orders
    add constraint orders_food_fk
        foreign key (food_id)
            references food (id);

 

INSERT ์ˆ˜ํ–‰

INSERT INTO users (name) VALUES ('Robbie');
INSERT INTO users (name) VALUES ('Robbert');

INSERT INTO food (name, price) VALUES ('ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ', 15000);
INSERT INTO food (name, price) VALUES ('์–‘๋… ์น˜ํ‚จ', 20000);
INSERT INTO food (name, price) VALUES ('๊ณ ๊ตฌ๋งˆ ํ”ผ์ž', 30000);
INSERT INTO food (name, price) VALUES ('์•„๋ณด์นด๋„ ํ”ผ์ž', 50000);

INSERT INTO orders (user_id, food_id, order_date) VALUES (1, 1, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 1, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 2, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (1, 4, SYSDATE());
INSERT INTO orders (user_id, food_id, order_date) VALUES (2, 3, SYSDATE());

INSERT ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ

๏นก์ค‘๋ณต ๋ฌธ์ œ ํ•ด๊ฒฐ๋จ!

  • ๊ณ ๊ฐ 1๋ช…์€ ์Œ์‹ N๊ฐœ๋ฅผ ์ฃผ๋ฌธํ•  ์ˆ˜ ์žˆ์Œ → ๊ณ ๊ฐ : ์Œ์‹ = 1 : N ๊ด€๊ณ„
  • ์Œ์‹ 1๊ฐœ๋Š” ๊ณ ๊ฐ N๋ช…์—๊ฒŒ ์ฃผ๋ฌธ๋  ์ˆ˜ ์žˆ์Œ ์Œ์‹ : ๊ณ ๊ฐ = 1 : N ๊ด€๊ณ„
  • ๊ฒฐ๋ก ์ ์œผ๋กœ ๊ณ ๊ฐ๊ณผ ์Œ์‹์€ N : M ๊ด€๊ณ„์ž„ ๊ณ ๊ฐ : ์Œ์‹ = N : M ๊ด€๊ณ„

๏นกN:M ๊ด€๊ณ„์ธ ํ…Œ์ด๋ธ”๋“ค์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ


ํ…Œ์ด๋ธ” ๊ฐ„์˜ ๋ฐฉํ–ฅ

DB ํ…Œ์ด๋ธ”๊ฐ„์˜ ๊ด€๊ณ„์—์„œ ๋ฐฉํ–ฅ์€ ํฌ๊ฒŒ ๋‹จ๋ฐฉํ–ฅ๊ณผ ์–‘๋ฐฉํ–ฅ์ด ์กด์žฌํ•  ์ˆ˜ ์žˆ์Œ

  • ๋‹จ๋ฐฉํ–ฅ: users ํ…Œ์ด๋ธ”์—์„œ๋งŒ food ํ…Œ์ด๋ธ” ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์„ ๋•Œ
  • ์–‘๋ฐฉํ–ฅ: users ํ…Œ์ด๋ธ”๊ณผ food ํ…Œ์ด๋ธ”์ด ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์„ ๋•Œ

ex) ๊ณ ๊ฐ 'Robbie'๊ฐ€ ์ฃผ๋ฌธํ•œ ์Œ์‹ ์ •๋ณด๋ฅผ users ํ…Œ์ด๋ธ” ๊ธฐ์ค€์œผ๋กœ ์กฐํšŒ

SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM users u
         INNER JOIN orders o on u.id = o.user_id
         INNER JOIN food f on o.food_id = f.id
WHERE o.user_id = 1;

๊ฒฐ๊ณผ

ex) food ํ…Œ์ด๋ธ” ๊ธฐ์ค€์œผ๋กœ 'Robbie'๊ฐ€ ์ฃผ๋ฌธํ•œ ์Œ์‹ ์ •๋ณด ์กฐํšŒ

SELECT u.name as username, f.name as foodname, o.order_date as orderdate
FROM food f
         INNER JOIN orders o on f.id = o.food_id
         INNER JOIN users u on o.user_id = u.id
WHERE o.user_id = 1;

๊ฒฐ๊ณผ

์œ„ ๋‘ ๊ฒฐ๊ณผ๋Š” ๋™์ผํ•จ! ์ฆ‰, DB์—์„œ๋Š” ์–ด๋–ค ํ…Œ์ด๋ธ”์„ ๊ธฐ์ค€์œผ๋กœ ํ•˜๋“  ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ JOIN์„ ์‚ฌ์šฉํ•ด์„œ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Œ!

๋”ฐ๋ผ์„œ, DB์—์„œ๋Š” ํ…Œ์ด๋ธ” ์‚ฌ์ด์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ FK(์™ธ๋ž˜ ํ‚ค)๋กœ ๋งบ์„ ์ˆ˜ ์žˆ๊ณ , ๋ฐฉํ–ฅ ์ƒ๊ด€์—†์ด ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅ(๋ฐฉํ–ฅ ๊ฐœ๋… X)


Entity ๊ฐ„์˜ ์—ฐ๊ด€ ๊ด€๊ณ„

JPA Entity์—์„œ์˜ ํ…Œ์ด๋ธ”๊ฐ„์˜ ์—ฐ๊ด€๊ด€๊ณ„ ํ‘œํ˜„ ๋ฐฉ์‹

[์Œ์‹ : ๊ณ ๊ฐ = N : 1 ๊ด€๊ณ„ ํ‘œํ˜„]

// ์Œ์‹ ํ…Œ์ด๋ธ”
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;
    
    @ManyToOne
    @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")
    private List<Food> foodList = new ArrayList<>();
}
  • ํ•œ ๋ช…์˜ ๊ณ ๊ฐ์€ ์—ฌ๋Ÿฌ๋ฒˆ ์ฃผ๋ฌธ์ด ๊ฐ€๋Šฅํ•œ ์ƒํ™ฉ์œผ๋กœ, ์ด๋ฅผ Entity์—์„œ ์—ฌ๋Ÿฌ๋ฒˆ ๊ฐ€๋Šฅํ•จ์„ ํ‘œํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Java ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด List ํ˜•์‹์œผ๋กœ ํ‘œํ˜„
  • DB ํ…Œ์ด๋ธ”์—์„œ๋Š” ๊ณ ๊ฐ ํ…Œ์ด๋ธ” ๊ธฐ์ค€์œผ๋กœ ์Œ์‹์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋ ค๊ณ  ํ•  ๋•Œ JOIN์„ ์‚ฌ์šฉํ•ด ๋ฐ”๋กœ ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ,
    ๊ณ ๊ฐ Entity ์ž…์žฅ์—์„œ๋Š” ์Œ์‹ Entity ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์œผ๋ฉด ์Œ์‹์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ๋ฐฉ๋ฒ•์ด ์—†์Œ!
  • ๋”ฐ๋ผ์„œ DB ํ…Œ์ด๋ธ”์— ์‹ค์ œ ์ปฌ๋Ÿผ์œผ๋กœ ์กด์žฌํ•˜์ง€ ์•Š์ง€๋งŒ Entity ์ƒํƒœ์—์„œ ๋‹ค๋ฅธ Entity๋ฅผ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ• ์‚ฌ์šฉ
  • ํ˜„์žฌ ์Œ์‹ Entity์™€ ๊ณ ๊ฐ Entity๋Š” ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ด๋Ÿฌํ•œ ๊ด€๊ณ„๋ฅผ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋ผ๊ณ  ๋ถ€๋ฆ„

[๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„]

// ์Œ์‹ ํ…Œ์ด๋ธ”
@Entity
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;
    
    @ManyToOne
    @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;
}
  • ์Œ์‹ Entity์—์„œ๋งŒ ๊ณ ๊ฐ Entity ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Œ
  • ๊ณ ๊ฐ Entity์—๋Š” ์Œ์‹ Entity์˜ ์ •๋ณด๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์Œ์‹ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์—†์Œ

์ฆ‰, Entity์—์„œ๋Š” ์ƒ๋Œ€ Entity๋ฅผ ์ฐธ์กฐํ•ด Entity ์‚ฌ์ด์˜ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๋งบ์„ ์ˆ˜ ์žˆ์Œ! ๋”ฐ๋ผ์„œ Entity์—์„œ๋Š” DB ํ…Œ์ด๋ธ”์—๋Š” ์—†๋Š” ๋ฐฉํ–ฅ์˜ ๊ฐœ๋…์ด ์กด์žฌํ•จ


1 ๋Œ€ 1 ๊ด€๊ณ„

@OneToOne ์–ด๋…ธํ…Œ์ด์…˜์€ 1๋Œ€ 1 ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์ฃผ๋Š” ์—ญํ•  ์ˆ˜ํ–‰

ex) ๊ณ ๊ฐ Entity์™€ ์Œ์‹ Entity๊ฐ€ 1๋Œ€ 1 ๊ด€๊ณ„๋ผ ๊ฐ€์ •ํ•˜๊ณ  ๊ด€๊ณ„ ๋งบ์–ด๋ณด๊ธฐ

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ ์ •ํ•˜๊ธฐ

  • Entity์—์„œ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์€ ์ผ๋ฐ˜์ ์œผ๋กœ N(๋‹ค)์˜ ๊ด€๊ณ„์ธ Entity ์ด์ง€๋งŒ, 1๋Œ€ 1 ๊ด€๊ณ„์—์„œ๋Š” ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์„ ์ง์ ‘ ์ง€์ •ํ•ด์•ผํ•จ
  • ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ๋งŒ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๋“ฑ๋ก, ์ˆ˜์ •, ์‚ญ์ œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ฃผ์ธ์ด ์•„๋‹Œ ์ชฝ์€ ์˜ค์ง ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ฝ๊ธฐ๋งŒ ๊ฐ€๋Šฅ!
  • @JoinColumn()์€ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ํ™œ์šฉํ•˜๋Š” ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ, ์ปฌ๋Ÿผ๋ช…, null ์—ฌ๋ถ€, unique ์—ฌ๋ถ€ ๋“ฑ ์ง€์ • ๊ฐ€๋Šฅ

1) ์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ 1:1 ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@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;
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

 

2) ๊ณ ๊ฐ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

๊ณ ๊ฐ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ 1:1 ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ

package com.sparta.jpaadvance.relation;

import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@SpringBootTest
public class OneToOneTest {
    @Autowired
    FoodRepository foodRepository;
    @Autowired
    UserRepository userRepository;

    @Test
    @Rollback(value = false) // ํ…Œ์ŠคํŠธ์—์„œ๋Š” @Transactional ์— ์˜ํ•ด ์ž๋™ rollback ๋จ์œผ๋กœ false ์„ค์ •ํ•ด์ค€๋‹ค.
    @DisplayName("1๋Œ€1 ๋‹จ๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
    void test1() {

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

        // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ Food Entity user ํ•„๋“œ์— user ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•ด ์ค๋‹ˆ๋‹ค.
        Food food = new Food();
        food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
        food.setPrice(15000);
        food.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

        userRepository.save(user);
        foodRepository.save(food);
    }
}

ํ…Œ์ŠคํŠธ ํ›„ DB

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์„ ์ง€์ •ํ•ด ์ค„ ๋•Œ mappedBy ์˜ต์…˜ ์‚ฌ์šฉํ•จ
    - mappedBy์˜ ์†์„ฑ๊ฐ’์€ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ ์ƒ๋Œ€ Entity์˜ ํ•„๋“œ๋ช…์„ ์˜๋ฏธ
  • ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์€ ์ƒ๋Œ€ Entity ํƒ€์ž…์˜ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๋ฉด์„œ @JoinColumn()์„ ํ™œ์šฉํ•ด ์™ธ๋ž˜ ํ‚ค์˜ ์†์„ฑ ์„ค์ •
  • ์ƒ๋Œ€ Entity๋Š” ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ Entity ํƒ€์ž…์˜ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๋ฉด์„œ mappedBy ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ์†์„ฑ ๊ฐ’์œผ๋กœ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ Entity์— ์„ ์–ธ๋œ @JoinColumn()์œผ๋กœ ์„ค์ •๋˜๊ณ  ์žˆ๋Š” ํ•„๋“œ๋ช… ๋„ฃ์–ด์ฃผ๋ฉด๋จ!

1) ์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ 1:1 ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@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;
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

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

    public void addFood(Food food) {
        this.food = food;
        food.setUser(this);
    }
}

 

2) ๊ณ ๊ฐ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

๊ณ ๊ฐ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค ์ฃผ์ธ์ธ 1:1 ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 1

@Test
@Rollback(value = false)
@DisplayName("1๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ")
void test2() {
    Food food = new Food();
    food.setName("๊ณ ๊ตฌ๋งˆ ํ”ผ์ž");
    food.setPrice(30000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์ €์žฅํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
    User user = new User();
    user.setName("Robbie");
    user.setFood(food);

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

    // ํ™•์ธํ•ด ๋ณด์‹œ๋ฉด user_id ๊ฐ’์ด ๋“ค์–ด๊ฐ€ ์žˆ์ง€ ์•Š์€ ๊ฒƒ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
}

๊ฒฐ๊ณผ

โ–บ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ๋งŒ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ์Œ!

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 2

@Test
@Rollback(value = false)
@DisplayName("1๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ -> ์„ฑ๊ณต")
void test3() {
    Food food = new Food();
    food.setName("๊ณ ๊ตฌ๋งˆ ํ”ผ์ž");
    food.setPrice(30000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด addFood() ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ • food.setUser(this); ์ถ”๊ฐ€
    User user = new User();
    user.setName("Robbie");
    user.addFood(food);

    userRepository.save(user);
    foodRepository.save(food);
}

๊ฒฐ๊ณผ

โ–บ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ๋งŒ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ์ปจํŠธ๋กคํ•  ์ˆ˜ ์žˆ์Œ!

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 3

@Test
@Rollback(value = false)
@DisplayName("1๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test4() {
    User user = new User();
    user.setName("Robbert");

    Food food = new Food();
    food.setName("๊ณ ๊ตฌ๋งˆ ํ”ผ์ž");
    food.setPrice(30000);
    food.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

    userRepository.save(user);
    foodRepository.save(food);
}

 

โš ๏ธ ์ฃผ์˜

1. ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ Entity์—์„œ @JoinColumn() ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ default ์˜ต์…˜์ด ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋žต ๊ฐ€๋Šฅํ•จ!

๋‹ค๋งŒ, 1๋Œ€ N ๊ด€๊ณ„์—์„œ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ Entity๊ฐ€ @JoinColumn() ์–ด๋…ธํ…Œ์ด์…˜์„ ์ƒ๋žตํ•œ๋‹ค๋ฉด, JPA๊ฐ€ ์™ธ๋ž˜ํ‚ค๋ฅผ ์ €์žฅํ•  ์ปฌ๋Ÿผ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์–ด์„œ ์˜๋„ํ•˜์ง€ ์•Š์€ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ƒ์„ฑ๋จ

 

2. ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ mappedBy ์˜ต์…˜์„ ์ƒ๋žตํ•  ๊ฒฝ์šฐ JPA๊ฐ€ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ Entity๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์–ด ์˜๋„ํ•˜์ง€ ์•Š์€ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ˜๋“œ์‹œ ์„ค์ •ํ•ด์ฃผ๋Š”๊ฒŒ ์ข‹์Œ!


N๋Œ€ 1 ๊ด€๊ณ„

@ManyToOne ์–ด๋…ธํ…Œ์ด์…˜์€ N ๋Œ€ 1 ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ

ex) ์Œ์‹ Entity์™€ ๊ณ ๊ฐ Entity๊ฐ€ N ๋Œ€ 1 ๊ด€๊ณ„๋ผ ๊ฐ€์ •ํ•˜๊ณ  ๊ด€๊ณ„๋ฅผ ๋งบ์–ด๋ณด๊ธฐ

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์Œ์‹ Entity๊ฐ€ N์˜ ๊ด€๊ณ„๋กœ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

N๋Œ€ 1 ๊ด€๊ณ„์ธ ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

 

 

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€1 ๋‹จ๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test1() {
    User user = new User();
    user.setName("Robbie");

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);
    food.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

    Food food2 = new Food();
    food2.setName("์–‘๋… ์น˜ํ‚จ");
    food2.setPrice(20000);
    food2.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

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

๊ฒฐ๊ณผ

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์–‘๋ฐฉํ–ฅ ์ฐธ์กฐ๋ฅผ ์œ„ํ•ด ๊ณ ๊ฐ Entity์—์„œ Java ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์Œ์‹ Entity ์ฐธ์กฐ

N๋Œ€ 1 ๊ด€๊ณ„์ธ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

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

    @OneToMany(mappedBy = "user")
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.setUser(this); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •
    }
}

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 1

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ")
void test2() {

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);

    Food food2 = new Food();
    food2.setName("์–‘๋… ์น˜ํ‚จ");
    food2.setPrice(20000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์ €์žฅํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
    User user = new User();
    user.setName("Robbie");
    user.getFoodList().add(food);
    user.getFoodList().add(food2);

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

    // ํ™•์ธํ•ด ๋ณด์‹œ๋ฉด user_id ๊ฐ’์ด ๋“ค์–ด๊ฐ€ ์žˆ์ง€ ์•Š์€ ๊ฒƒ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
}

๊ฒฐ๊ณผ

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 2

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ -> ์„ฑ๊ณต")
void test3() {

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);

    Food food2 = new Food();
    food2.setName("์–‘๋… ์น˜ํ‚จ");
    food2.setPrice(20000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์‰ฝ๊ฒŒ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด addFoodList() ๋ฉ”์„œ๋“œ ์ƒ์„ฑํ•˜๊ณ 
    // ํ•ด๋‹น ๋ฉ”์„œ๋“œ์— ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ • food.setUser(this); ์ถ”๊ฐ€
    User user = new User();
    user.setName("Robbie");
    user.addFoodList(food);
    user.addFoodList(food2);

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

๊ฒฐ๊ณผ

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 3

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€1 ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test4() {
    User user = new User();
    user.setName("Robbert");

    Food food = new Food();
    food.setName("๊ณ ๊ตฌ๋งˆ ํ”ผ์ž");
    food.setPrice(30000);
    food.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

    Food food2 = new Food();
    food2.setName("์•„๋ณด์นด๋„ ํ”ผ์ž");
    food2.setPrice(50000);
    food2.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

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

๊ฒฐ๊ณผ


1๋Œ€ N ๊ด€๊ณ„

@OneToMany ์–ด๋…ธํ…Œ์ด์…˜์€ 1 ๋Œ€ ๋‹ค ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ

ex) ์Œ์‹ Entity์™€ ๊ณ ๊ฐ Entity๊ฐ€ 1 ๋Œ€ ๋‹ค ๊ด€๊ณ„๋ผ ๊ฐ€์ •ํ•˜๊ณ  ๊ด€๊ณ„๋ฅผ ๋งบ์–ด๋ณด๊ธฐ

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฃผ์ธ์€ ์Œ์‹ Entity์ด์ง€๋งŒ ์‹ค์ œ ์™ธ๋ž˜ ํ‚ค๋Š” ๊ณ ๊ฐ Entity๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
    → 1 : N์—์„œ N ๊ด€๊ณ„์˜ ํ…Œ์ด๋ธ”์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์™ธ๋ž˜ ํ‚ค๋Š” N ๊ด€๊ณ„์ธ users ํ…Œ์ด๋ธ”์— ์™ธ๋ž˜ ํ‚ค ์ปฌ๋Ÿผ์„ ๋งŒ๋“ค์–ด ์ถ”๊ฐ€ํ•˜์ง€๋งŒ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ ์Œ์‹ Entity๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•จ

N๋Œ€ 1 ๊ด€๊ณ„์ธ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

 

  • ์™ธ๋ž˜ ํ‚ค๋ฅผ ์Œ์‹ Entity๊ฐ€ ์ง์ ‘ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋ฉด INSERT ๋ฐœ์ƒ ์‹œ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์‹ค์ œ DB์—์„œ ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ณ ๊ฐ ํ…Œ์ด๋ธ”์ด ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ UPDATE๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์กด์žฌํ•จ
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
@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<>();
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

 

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ

@Test
@Rollback(value = false)
@DisplayName("1๋Œ€N ๋‹จ๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test1() {
    User user = new User();
    user.setName("Robbie");

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

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);
    food.getUserList().add(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •
    food.getUserList().add(user2); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

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

    // ์ถ”๊ฐ€์ ์ธ UPDATE ์ฟผ๋ฆฌ ๋ฐœ์ƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
}

๊ฒฐ๊ณผ: Update ์ฟผ๋ฆฌ ์ˆ˜ํ–‰๋ฌธ ์ถ”๊ฐ€ ๋ฐœ์ƒํ•จ

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

  • 1๋Œ€ N ๊ด€๊ณ„์—์„œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ
  • 1๋Œ€ N ๊ด€๊ณ„์—์„œ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ๋งบ์œผ๋ ค๋ฉด ์Œ์‹ Entity๋ฅผ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์œผ๋กœ ์ •ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด ๊ณ ๊ฐ Entity์—์„œ mappedBy ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜์ง€๋งŒ @ManyToOne ์–ด๋…ธํ…Œ์ด์…˜์€ mappedBy ์†์„ฑ์„ ์ œ๊ณตํ•˜์ง€ ์•Š์Œ
  • N ๊ด€๊ณ„์˜ Entity์ธ ๊ณ ๊ฐ Entity์—์„œ @JoinColumn์˜ insertable๊ณผ updatable ์˜ต์…˜์„ false๋กœ ์„ค์ •ํ•ด ์–‘์ชฝ์œผ๋กœ JOIN ์„ค์ •์„ ํ•˜๋ฉด ์–‘๋ฐฉํ–ฅ์ฒ˜๋Ÿผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ถ”์ฒœํ•˜์ง€๋Š” ์•Š์Œ!
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @ManyToOne
    @JoinColumn(name = "food_id", insertable = false, updatable = false)
    private Food food;
}

 

N๋Œ€ M ๊ด€๊ณ„

@ManyToMany ์–ด๋…ธํ…Œ์ด์…˜์€ N ๋Œ€ M ๊ด€๊ณ„๋ฅผ ๋งบ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•จ

ex) ์Œ์‹ Entity์™€ ๊ณ ๊ฐ Entity๊ฐ€ N ๋Œ€ M ๊ด€๊ณ„๋ผ ๊ฐ€์ •ํ•˜๊ณ  ๊ด€๊ณ„๋ฅผ ๋งบ์–ด๋ณด๊ธฐ

 

N๋Œ€ M๊ด€๊ณ„๋ฅผ ์œ„ํ•ด ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•จ

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

N๋Œ€ M ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
@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<>();
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}
  • ์ƒ์„ฑ๋˜๋Š” ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์€ PK๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ. ์ฆ‰, ์ƒ์„ฑ๋˜๋Š” ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์„ ์ปจํŠธ๋กคํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์ถ”ํ›„์— ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์˜ ๋ณ€๊ฒฝ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ

๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€M ๋‹จ๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test1() {

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

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

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);
    food.getUserList().add(user);
    food.getUserList().add(user2);

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

    // ์ž๋™์œผ๋กœ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ” orders ๊ฐ€ create ๋˜๊ณ  insert ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
}

์ค‘๊ฐ„ ํ…Œ์ด๋ธ” order ์ƒ์„ฑ ๋ฐ INSERT๋ฌธ ์ˆ˜ํ–‰

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

  • ์Œ์‹ Entity๊ฐ€ ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ธ ๊ฒฝ์šฐ

N๋Œ€ M ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
@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<>();
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter
@Setter
@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<>();
}
  • ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์ธ ๊ณ ๊ฐ Entity์— @ManyToMany๋กœ ์Œ์‹ Entity๋กœ ์—ฐ๊ฒฐํ•˜๊ณ  mappedBy ์˜ต์…˜์„ ์„ค์ •ํ•ด ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์„ ์„ค์ •ํ•˜๋ฉด ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ๋งบ์Œ์ด ๊ฐ€๋Šฅํ•จ

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 1

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€M ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ")
void test2() {

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);

    Food food2 = new Food();
    food2.setName("์–‘๋… ์น˜ํ‚จ");
    food2.setPrice(20000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์ €์žฅํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
    User user = new User();
    user.setName("Robbie");
    user.getFoodList().add(food);
    user.getFoodList().add(food2);

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

    // ํ™•์ธํ•ด ๋ณด์‹œ๋ฉด orders ํ…Œ์ด๋ธ”์— food_id, user_id ๊ฐ’์ด ๋“ค์–ด๊ฐ€ ์žˆ์ง€ ์•Š์€ ๊ฒƒ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
}

๊ฒฐ๊ณผ: ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์ธ order ํ…Œ์ด๋ธ”์— ์ •๋ณด๊ฐ€ INSERT ๋˜์ง€ ์•Š์Œ

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 2

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€M ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ : ์™ธ๋ž˜ ํ‚ค ์ €์žฅ ์‹คํŒจ -> ์„ฑ๊ณต")
void test3() {

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);

    Food food2 = new Food();
    food2.setName("์–‘๋… ์น˜ํ‚จ");
    food2.setPrice(20000);

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ์—์„œ Food ๋ฅผ ์‰ฝ๊ฒŒ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด addFoodList() ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •์„ ์œ„ํ•ด Food ์—์„œ userList ๋ฅผ ํ˜ธ์ถœํ•ด user ๊ฐ์ฒด ์ž์‹ ์„ add ํ•ฉ๋‹ˆ๋‹ค.
    User user = new User();
    user.setName("Robbie");
    user.addFoodList(food);
    user.addFoodList(food2);


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

๊ฒฐ๊ณผ: Order ํ…Œ์ด๋ธ”์— ์ถ”๊ฐ€๋จ

 

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 3

@Test
@Rollback(value = false)
@DisplayName("N๋Œ€M ์–‘๋ฐฉํ–ฅ ํ…Œ์ŠคํŠธ")
void test4() {

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

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


    Food food = new Food();
    food.setName("์•„๋ณด์นด๋„ ํ”ผ์ž");
    food.setPrice(50000);
    food.getUserList().add(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •
    food.getUserList().add(user2); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

    Food food2 = new Food();
    food2.setName("๊ณ ๊ตฌ๋งˆ ํ”ผ์ž");
    food2.setPrice(30000);
    food2.getUserList().add(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());
    }

    // ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ๊ฐ์ฒด์— Food ์˜ ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์•„๋„ DB ์ €์žฅ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ
    // ์ด์ฒ˜๋Ÿผ User ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ food ์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค.
}

๊ฒฐ๊ณผ: foodList์— ๊ฐ’์ด ๋“ค์–ด๊ฐ€์ง€ ์•Š์Œ

  • ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ User ๊ฐ์ฒด์— Food ์ •๋ณด๋ฅผ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถœ๋ ฅ๋˜์ง€ ์•Š์Œ
  • ์ฆ‰ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์ผ ๋•Œ, ์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด ์•„๋‹Œ ์ชฝ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์œผ๋ฉด ๋กœ์ง ์ˆ˜ํ–‰ X

์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„ ํ…Œ์ŠคํŠธ 4

@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());
    }
}

๊ฒฐ๊ณผ

์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ๋งŒ๋“ค๊ธฐ

์œ„์˜ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์€ ์ปจํŠธ๋กคํ•˜๊ธฐ ํž˜๋“ค๊ธฐ ๋•Œ๋ฌธ์—, ์ง์ ‘ ์ค‘๊ฐ„ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ด์„œ ์‚ฌ์šฉํ•ด ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

 

์™ธ๋ž˜ ํ‚ค์˜ ์ฃผ์ธ์ด Order Entity๊ฐ€ ๋จ!

N๋Œ€ M ์–‘๋ฐฉํ–ฅ(์ค‘๊ฐ„ ํ…Œ์ด๋ธ” ์ƒ์„ฑ)

Order Entity(์ค‘๊ฐ„ ํ…Œ์ด๋ธ”)

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@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;
}

 

Food Entity

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

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

    @OneToMany(mappedBy = "food")
    private List<Order> orderList = new ArrayList<>();
}

 

User Entity

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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

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

    @OneToMany(mappedBy = "user")
    private List<Order> orderList = new ArrayList<>();
}

 

ํ…Œ์ŠคํŠธ 1

@Test
@Rollback(value = false)
@DisplayName("์ค‘๊ฐ„ ํ…Œ์ด๋ธ” Order Entity ํ…Œ์ŠคํŠธ")
void test1() {

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

    Food food = new Food();
    food.setName("ํ›„๋ผ์ด๋“œ ์น˜ํ‚จ");
    food.setPrice(15000);

    // ์ฃผ๋ฌธ ์ €์žฅ
    Order order = new Order();
    order.setUser(user); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •
    order.setFood(food); // ์™ธ๋ž˜ ํ‚ค(์—ฐ๊ด€ ๊ด€๊ณ„) ์„ค์ •

    userRepository.save(user);
    foodRepository.save(food);
    orderRepository.save(order);
}

๊ฒฐ๊ณผ: 3๋ฒˆ๋งŒ์— ์ˆ˜ํ–‰

์กฐํšŒ ํ…Œ์ŠคํŠธ 2

@Test
@DisplayName("์ค‘๊ฐ„ ํ…Œ์ด๋ธ” Order Entity ์กฐํšŒ")
void test2() {
    // 1๋ฒˆ ์ฃผ๋ฌธ ์กฐํšŒ
    Order order = orderRepository.findById(1L).orElseThrow(NullPointerException::new);

    // order ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ๊ฐ ์ •๋ณด ์กฐํšŒ
    User user = order.getUser();
    System.out.println("user.getName() = " + user.getName());

    // order ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Œ์‹ ์ •๋ณด ์กฐํšŒ
    Food food = order.getFood();
    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());
}

์กฐํšŒ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ