Spring Data JPA ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘ ๋ฐฉ๋ฒ• ์ •๋ฆฌ with ์˜ˆ์‹œ

2025. 3. 7. 16:07ยท๐ŸŒฟSpring

๋“ค์–ด๊ฐ€๋ฉด์„œ

๊ฐœ์ธ์ ์œผ๋กœ ๊ณต๋ถ€ํ•œ ๋‚ด์šฉ๊ณผ ์ƒ๊ฐํ•œ ๋‚ด์šฉ์„ ๋‹ด์•„ ์ž‘์„ฑํ•œ ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค.
ํ‹€๋ฆฐ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด ๋Œ“๊ธ€๋กœ ๊ณต์œ ํ•ด์ฃผ์„ธ์š”!


โœ… ๊ฐœ๋… ์ฒดํฌ

  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ๋Š” ์™ธ๋ž˜ ํ‚ค๋กœ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ๊ฐ์ฒด ๊ฐ„์˜ ์ฐธ์กฐ๋กœ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • JPA๋ฅผ ํ™œ์šฉํ•œ๋‹ค๋Š” ๊ฒƒ์€ RDB ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ฐ์ฒด ๊ฐ„ ์—ฐ๊ด€๊ด€๊ณ„๋กœ ๋งคํ•‘ํ•œ๋‹ค๋Š” ๊ฒƒ.
  • ์—”ํ‹ฐํ‹ฐ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๊ณผ 1:1๋กœ ๋Œ€์‘๋˜๋Š” ๊ฐœ๋…์ด๋‹ค.
    • ํ•œ ์ธ์Šคํ„ด์Šค๋Š” DB ํ…Œ์ด๋ธ”์˜ ํ•œ row์— ํ•ด๋‹นํ•œ๋‹ค.
    • JPA์—์„œ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์˜ํ•ด ๊ด€๋ฆฌ๋œ๋‹ค.
    • ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์—๋Š” ๋ฐ˜๋“œ์‹œ PK ์‹๋ณ„์ž๊ฐ€ ์žˆ๋‹ค. (@Id ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ •์˜ํ•œ๋‹ค.)
    • ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๋ฅผ ํ•„์ˆ˜๋กœ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค. (@NoArgsConstructor ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•œ๋‹ค.)
    • ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๊ณผ ๋Œ€์‘๋œ๋‹ค.
  • ์–‘๋ฐฉํ–ฅ: ๋‘ ๊ฐ์ฒด๊ฐ€ ์„œ๋กœ ์ฐธ์กฐ์šฉ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
    • A → B, B → A ๋‘˜ ๋‹ค ์ฐธ์กฐ ๊ฐ€๋Šฅ
  • ๋‹จ๋ฐฉํ–ฅ: ๋‘ ๊ฐ์ฒด ์‚ฌ์ด์— ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋งŒ ์ฐธ์กฐ์šฉ ํ•„๋“œ๋ฅผ ๊ฐ–๊ณ  ์ฐธ์กฐํ•œ๋‹ค.
    • A → B๋Š” ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, B → A๋Š” ๋ถˆ๊ฐ€๋Šฅ
  • DB ๊ตฌ์กฐ์ƒ ๋‹จ๋ฐฉํ–ฅ์ด๋ฉด ๋ฐ˜๋“œ์‹œ JPA Entity๋„ ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š”๊ฐ€?
    • No
    • DB ํ…Œ์ด๋ธ”์—์„œ๋Š” ํ…Œ์ด๋ธ” ์‚ฌ์ด์˜ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ FK๋กœ ๋งบ์„ ์ˆ˜ ์žˆ๊ณ  ๋ฐฉํ–ฅ ์ƒ๊ด€์—†์ด ์กฐํšŒ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
    • JPA์—์„œ๋Š” ์ƒ๋Œ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์–ด์•ผ๋งŒ ์ƒ๋Œ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค. "๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ƒ ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์˜ ์กฐํšŒ๋„ ํ•„์š”ํ•  ๋•Œ"์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋” ์‰ฝ๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ์–‘๋ฐฉํ–ฅ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ: ๋‘ ๊ฐ์ฒด์˜ ๊ด€๊ณ„ ์ค‘ ์ œ์–ด์˜ ๊ถŒํ•œ(๋ฐ์ดํ„ฐ ์กฐํšŒ, ์ €์žฅ, ์ˆ˜์ •, ์‚ญ์ œ)์„ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด
    • FK๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”(FK๋ฅผ ํ•„๋“œ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”) ์—”ํ‹ฐํ‹ฐ๋ฅผ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์œผ๋กœ ๋ณธ๋‹ค.

๐ŸŽฏ 1:1 ๊ด€๊ณ„(One-to-One)

ํ•œ ๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋‹ค๋ฅธ ํ•˜๋‚˜์˜ ์—”ํ‹ฐํ‹ฐ์™€ ๋‹จ ํ•˜๋‚˜์˜ ๊ด€๊ฒŒ๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒฝ์šฐ์ด๋‹ค.
์ฆ‰, ์—”ํ‹ฐํ‹ฐ A์˜ ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜๋Š” ์—”ํ‹ฐํ‹ฐ B์˜ ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜์™€ ๋Œ€์‘๋œ๋‹ค.

๋‹จ๋ฐฉํ–ฅ์ธ ๊ฒฝ์šฐ์™€ ์–‘๋ฐฉํ–ฅ์ธ ๊ฒฝ์šฐ๋ฅผ ๋‚˜๋ˆ„์–ด ์ƒ๊ฐํ•ด๋ณด๊ฒ ๋‹ค.


๋‹จ๋ฐฉํ–ฅ

DB์—์„œ ๋ชจ๋“  ์‚ฌ์šฉ์ž๋Š” users ํ…Œ์ด๋ธ”์—, ์„œ๋น„์Šค์— ๋“ฑ๋ก๋œ ๋ชจ๋“  ์‚ฌ์ง„์€ photos ํ…Œ์ด๋ธ”์— ์ €์žฅ๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•˜์ž. photos ํ…Œ์ด๋ธ”์—๋Š” ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋„ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.

 

์‚ฌ์šฉ์ž(User) 1๋ช…์€ 1๊ฐœ์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€(Photo)์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
1๊ฐœ์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๋Š” ์‚ฌ์šฉ์ž 1๋ช…์—๊ฒŒ ์†ํ•œ๋‹ค.

 

๋‹จ๋ฐฉํ–ฅ์ธ ์ด์œ 

 

์ผ๋ฐ˜์ ์ธ ์„œ๋น„์Šค์—์„œ ํ”„๋กœํ•„ ์‚ฌ์ง„์€ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค.
ํ”„๋กœํ•„ ์‚ฌ์ง„์ด ๋”ฐ๋กœ ๋…๋ฆฝ์ ์œผ๋กœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ํ”„๋กœํ•„ ์‚ฌ์ง„์ด ์‚ฌ์šฉ์ž ์ •๋ณด๊นŒ์ง€ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์—†๋‹ค.

 

User๋Š” ์ž์‹ ์ด ๊ฐ€์ง„ Photo๋ฅผ ์•Œ์•„์•ผ ํ•˜์ง€๋งŒ
Photo๋Š” ์ž์‹ ์„ ๊ฐ€์ง„ User๋ฅผ ๋ชฐ๋ผ๋„ ๋œ๋‹ค.


์ด๋Ÿฌํ•œ ์ด์œ ๋กœ User -> Photo ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

JPA์—์„œ ๋‹จ๋ฐฉํ–ฅ, ์–‘๋ฐฉํ–ฅ์„ ์ •ํ•  ๋•Œ๋Š” "๊ฐ์ฒด๊ฐ€ ์„œ๋กœ๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€?"๋ฅผ ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋œ๋‹ค.

์–ด๋А ํ…Œ์ด๋ธ”์ด FK๋ฅผ ๊ฐ€์ ธ์•ผ ํ• ๊นŒ?

์ผ๋ฐ˜์ ์œผ๋กœ FK์˜ ์ฃผ์ธ์€ 1:N์ธ ๊ด€๊ณ„์—์„œ๋Š” N์ธ ์ชฝ์— ์†ํ•˜์ง€๋งŒ,
1:1์ธ ๊ด€๊ณ„์—์„œ๋Š” ์–ด๋А ์ชฝ์ด FK๋ฅผ ๊ด€๋ฆฌํ• ์ง€,
์ฆ‰ ์–ด๋А ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—ฐ๊ด€ ๊ด€๊ณ„์˜ ์ฃผ์ธ์ด ๋ ์ง€ ์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

๋‹ค์–‘ํ•œ ์ด์œ ๊ฐ€ ์žˆ๊ฒ ์ง€๋งŒ
๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๋กœ ์ •ํ–ˆ๋‹ค.

 

1. ์˜๋ฏธ์  ๊ด€๊ณ„: ๋ˆ„๊ฐ€ ์ด ๊ด€๊ณ„๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‚˜?

๊ฐ€์žฅ ๋จผ์ € ๋– ์˜ค๋ฅด๋Š” ์ด์œ ๋‹ค.
User์™€ Photo์˜ ์˜๋ฏธ๋Š” ์‚ฌ์šฉ์ž์™€ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์ด๋‹ค.

 

์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉ์ž์˜ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์— ํฌํ•จ๋˜๊ธฐ ๋ณด๋‹ค๋Š”, ํ”„๋กœํ•„ ์ด๋ฏธ์ง€๊ฐ€ ์‚ฌ์šฉ์ž ์ •๋ณด์— ํฌํ•จ๋˜๋Š” ์ชฝ์ด ์˜ณ๋‹ค.

์‚ฌ์ง„์ด "์†Œ์†๋  ์‚ฌ์šฉ์ž"๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค
์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœํ•„ ์‚ฌ์ง„์„ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฐœ๋…์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.


User๊ฐ€ Photo ๊ฐ์ฒด๋ฅผ ์†Œ์œ ํ•˜๊ณ , Photo ๊ฐ์ฒด๋Š” ํŠน์ • User ๊ฐ์ฒด์— ์ข…์†๋œ๋‹ค.

๐Ÿ’ก ์ฃผ์ฒด๊ฐ€ ๋˜๋Š” ์—”ํ‹ฐํ‹ฐ(์†Œ์œ ์ž)๊ฐ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒŒ ์ผ๋ฐ˜์  -> ์†Œ์œ ์ž๊ฐ€ FK๋ฅผ ๊ฐ€์ง„๋‹ค.

 

์ด ๊ฒฝ์šฐ์—๋Š” User๊ฐ€ Photo์˜ id ์ปฌ๋Ÿผ๊ณผ ๋งคํ•‘๋˜๋Š” photo_id๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒŒ ๋งž๋‹ค.

 

2. ์กฐํšŒ ์„ฑ๋Šฅ ๊ณ ๋ ค

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ User ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ํ”„๋กœํ•„ ์‚ฌ์ง„์„ ํ•จ๊ป˜ ์กฐํšŒํ•  ๊ฒƒ์ด๋‹ค.
์ด ์ƒํ™ฉ์ด ํ”„๋กœํ•„ ์‚ฌ์ง„์„ ์กฐํšŒํ•  ๋•Œ ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฐœ๋…๋ณด๋‹ค ์ต์ˆ™ํ•˜๋‹ค.

 

์ด๋•Œ User์— FK๊ฐ€ ์žˆ๋‹ค๋ฉด ์ฆ‰์‹œ Photo์™€ joinํ•ด์„œ User ์ •๋ณด์™€ ํŠน์ • Photo๋ฅผ ์กฐํšŒํ•˜๊ธฐ ํŽธํ•˜๋‹ค.
๋งŒ์•ฝ Photo์— FK๋ฅผ ๋‘”๋‹ค๋ฉด, ๋จผ์ € User ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ ํ›„ user_id๋ฅผ ๊ฐ€์ง„ Photo๋ฅผ ํ…Œ์ด๋ธ” ์ „์ฒด์—์„œ ์กฐํšŒํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿ“Œ ์ฃผ์š” ํ‚ค์›Œ๋“œ

  • @OneToOne: 1:1 ๊ด€๊ณ„๋ฅผ ๋ช…์‹œํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
  • @JoinColumn: ์—ฐ๊ด€ ๊ด€๊ณ„์˜ ์ฃผ์ธ์ด FK๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ์ง€์ •ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
    • @JoinColumn(name = food_id) ์ด๋Ÿฐ ์‹์œผ๋กœ ์ปฌ๋Ÿผ๋ช…์„ ์›ํ•˜๋Š”๋Œ€๋กœ ์ง€์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
    • FK๋ฅผ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด์—์„œ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.

์ฝ”๋“œ

User Entity

@Entity
@Table(name="users")
@NoargsConstructor    // Entity๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์ˆ˜์ ์ž„
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne
    @JoinColumn(name = "photo_id")
    private Photo photo;

    ...
}

Photo Entity

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

    private String fileName;
    private String contentType;
    private Long size;
    ...
}

์–‘๋ฐฉํ–ฅ

๊ทธ๋ ‡๋‹ค๋ฉด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์„œ๋กœ 1:1๋กœ ๋Œ€์‘ํ•˜๊ณ , ์„œ๋กœ์˜ ์กด์žฌ๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š” ์˜ˆ์‹œ๋Š” ๋ฌด์—‡์ด ์žˆ์„๊นŒ?

 

์‹๋‹น์—์„œ 1๋ช…์˜ ๊ณ ๊ฐ์€ 1๊ฐœ์˜ ์Œ์‹๋งŒ ์ฃผ๋ฌธํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.

๊ณ ๊ฐ์€ ๋‚˜์ค‘์— ๊ฒฐ์ œํ•˜๋ ค๋ฉด ์ž์‹ ์ด ์ฃผ๋ฌธํ•œ ์Œ์‹์„ ์•Œ์•„์•ผ ํ•œ๋‹ค.
์Œ์‹๋„ ์›จ์ดํ„ฐ๊ฐ€ ์„œ๋น™ํ•˜๋ ค๋ฉด ์ž์‹ ์„ ์ฃผ๋ฌธํ•œ ๊ณ ๊ฐ์„ ์•Œ์•„์•ผ ํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ๊ณ ๊ฐ - ์Œ์‹์€ ์„œ๋กœ ๊ฐ์ฒด๋ฅผ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ด€๊ณ„์ด๋ฏ€๋กœ ์–‘๋ฐฉํ–ฅ์ด๋‹ค.


์ด๋•Œ๋Š” ์„œ๋กœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.

๊ณ ๊ฐ์„ Customer, ์Œ์‹์„ Food๋ผ๊ณ  ํ•˜์ž.

์ฐธ๊ณ ๋กœ DB์—์„œ๋Š” ๋‹จ๋ฐฉํ–ฅ ๊ด€๊ณ„๋กœ ์„ค๊ณ„๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ
์กฐํšŒ(์Œ์‹ ์„œ๋น™) ๊ธฐ๋Šฅ์„ ํŽธํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด JPA์—์„œ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด ๊ด€๊ณ„์˜ ์ฃผ์ธ์€ ๋ˆ„๊ตฌ์ผ๊นŒ?
์˜๋ฏธ์ ์œผ๋กœ ๋ดค์„ ๋•Œ, ๊ณ ๊ฐ์ด ํŠน์ • ์Œ์‹์„ ์ฃผ๋ฌธํ•˜๊ฑฐ๋‚˜ ์„ ํƒํ•˜๋Š” "๊ด€๋ฆฌ ์ฃผ์ฒด"์ด๋ฏ€๋กœ Customer๊ฐ€ ์ฃผ์ธ์ด๋‹ค. Customer ํด๋ž˜์Šค๊ฐ€ FK๋ฅผ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

๐Ÿ“Œ ์ฃผ์š” ํ‚ค์›Œ๋“œ

  • mappedBy: ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ ์ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ(์ฃผ์ธ)๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.
    • ์ฃผ์ธ์ด ์•„๋‹Œ ์ชฝ์—์„œ ์„ค์ •ํ•œ๋‹ค.
    • ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ํ•„๋“œ ์ด๋ฆ„์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.
    • DB ํ…Œ์ด๋ธ” ๋‚ด FK ์ปฌ๋Ÿผ ์ด๋ฆ„์ด ์•„๋‹ˆ๋ผ ์ฃผ์ธ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ์ฐธ์กฐ ํ•„๋“œ ์ด๋ฆ„์ด๋‹ค!

์ฝ”๋“œ

Customer Entity

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

    private String name;
    private int seatIndex;
    ...

    // ๊ณ ๊ฐ์ด ์‹œํ‚ค๋Š” 1๊ฐœ์˜ ๋ฉ”๋‰ด
    @OneToOne
    @JoinColumn(name="food_id")
    private Food food;
}

 

Food Entity

์ฃผ์ธ์ธ Customer์—์„œ Food๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํ•„๋“œ๋Š” food์ด๋‹ค.
๋”ฐ๋ผ์„œ Food ์—”ํ‹ฐํ‹ฐ์—์„œ mappedBy ์˜ต์…˜์œผ๋กœ Customer์—์„œ food ํ•„๋“œ๊ฐ€ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์ž„์„ ๋ช…์‹œํ•œ๋‹ค.

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

    private String name;
    private int price;
    ...

    // ๊ณ ๊ฐ์ด ์‹œํ‚ค๋Š” 1๊ฐœ์˜ ๋ฉ”๋‰ด
    @OneToOne(mappedBy = "food")
    private Customer customer;
}

๐ŸŽฏ 1:N/N:1 ๊ด€๊ณ„(One-to-Many/Many-to-One)

์—”ํ‹ฐํ‹ฐ A์˜ 1๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์—”ํ‹ฐํ‹ฐ B์˜ N๊ฐœ์˜ ์ธ์Šคํ„ด์Šค์™€ ๋Œ€์‘ํ•  ๋•Œ A์™€ B์˜ ๊ด€๊ณ„๋ฅผ 1:N์ด๋ผ๊ณ  ํ•œ๋‹ค.

1๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒŒ์‹œ๊ธ€์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
์‚ฌ์šฉ์ž๋ฅผ User, ๊ฒŒ์‹œ๊ธ€์„ Post๋ผ๊ณ  ํ•ด๋ณด์ž.

DB ์ƒ์—์„œ์˜ ๊ตฌ์กฐ๋Š” ์œ„์™€ ๊ฐ™๋‹ค.

Post์—์„œ FK๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์€ Post์ด๋‹ค.


๋‹จ๋ฐฉํ–ฅ

Post๋Š” ์ž์‹ ์„ ์ž‘์„ฑํ•œ User ์ •๋ณด๋ฅผ ์•Œ์•„์•ผ ํ•˜์ง€๋งŒ
User๋Š” ์ž์‹ ์ด ์ž‘์„ฑํ•œ Post ์ •๋ณด๊ฐ€ ํ•„์ˆ˜์ ์ธ ๊ฑด ์•„๋‹ ๋•Œ๋Š” ๋‹จ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ“Œ ์ฃผ์š” ํ‚ค์›Œ๋“œ

  • @ManyToOne: N:1 ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
    • ์—ฌ๋Ÿฌ ๊ฐœ์˜ Post๊ฐ€ ํ•˜๋‚˜์˜ User๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ƒํ™ฉ์ด๋‹ค.
    • FK๋Š” N์ชฝ์—์„œ ๊ด€๋ฆฌ, ์ฆ‰ Post๊ฐ€ ์ฃผ์ธ์ด๋‹ค.
    • Many์— ํ•ด๋‹นํ•˜๋Š” Post์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.

์ฝ”๋“œ

User Entity

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

    private String name;
    private String email;
    ...
}

Post Entity

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

    private String title;
    private String content;
    ...
    @ManyToOne
    @JoinColumn(name="author_id")
    private User user;
}

์–‘๋ฐฉํ–ฅ

๋งŒ์•ฝ Post๊ฐ€ ์ž‘์„ฑ์ž User๋ฅผ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•˜๊ณ ,
User๋„ ์ž์‹ ์ด ์“ด Post๋ฅผ ์•Œ์•„์•ผ ํ•  ๋•Œ๋Š” ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋กœ ์„ค๊ณ„ํ•œ๋‹ค.
์ผ๋ฐ˜์ ์ธ ์„œ๋น„์Šค์—์„œ๋Š” ์ด ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ง€ ์•Š์„๊นŒ?

๐Ÿ“Œ ์ฃผ์š” ํ‚ค์›Œ๋“œ

  • @OneToMany: 1:N ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
    • 1๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ง„๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.
    • ํ•œ ๋ช…์˜ User๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ Post๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.
    • 1์ชฝ์— ์†ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.
    • ๋ฐ˜๋“œ์‹œ mappedBy ์˜ต์…˜์œผ๋กœ ์ฃผ์ธ(@ManyToOne์„ ๊ฐ€์ง„ ์—”ํ‹ฐํ‹ฐ)์„ ๋ช…์‹œํ•ด์•ผ ํ•œ๋‹ค.

์ฝ”๋“œ

User Entity

1:N์ธ ๊ด€๊ณ„๋‹ˆ N์ธ ๊ฐ์ฒด๋ฅผ List, ๋ฐฐ์—ด ๊ฐ™์€ ๊ฐ’์œผ๋กœ ์ €์žฅํ•œ๋‹ค.

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

    private String name;
    private String email;
    ...

    @OneToMany(mappedBy="author")
    List<Post> posts;
}

Post Entity

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

    private String title;
    private String content;
    ...
    @ManyToOne
    @JoinColumn(name="author_id")
    private User author;
}

๐ŸŽฏ N:M ๊ด€๊ณ„(Many-to-Many)

์—”ํ‹ฐํ‹ฐ A์˜ N๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์—”ํ‹ฐํ‹ฐ B์˜ M๊ฐœ์˜ ์ธ์Šคํ„ด์Šค์™€ ๋Œ€์‘ํ•  ๋•Œ A์™€ B์˜ ๊ด€๊ณ„๋ฅผ N:M์ด๋ผ๊ณ  ํ•œ๋‹ค.

๊ฐ€์žฅ ๋งŽ์ด ๋“œ๋Š” ์˜ˆ์‹œ๋Š” ๋Œ€ํ•™๊ต์—์„œ "ํ•™์ƒ๊ณผ ๊ฐ•์˜"์˜ ๊ด€๊ณ„์ด๋‹ค.

ํ•œ ๋ช…์˜ ํ•™์ƒ์€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ˆ˜์—…์„ ์ˆ˜๊ฐ•ํ•  ์ˆ˜ ์žˆ๋‹ค. → N:1 ๊ด€๊ณ„ (Student → Course)
ํ•œ ๊ฐœ์˜ ์ˆ˜์—…์—๋Š” ์—ฌ๋Ÿฌ ๋ช…์˜ ํ•™์ƒ์ด ๋“ฑ๋ก๋  ์ˆ˜ ์žˆ๋‹ค. → 1:N ๊ด€๊ณ„ (Course → Student)
๋”ฐ๋ผ์„œ, N:M ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑํ•œ๋‹ค.

 

์ด๋•Œ๋Š” ๋‘ ๊ฐ์ฒด ๊ฐ„์˜ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์ด ๋”ฐ๋กœ ํ•„์š”ํ•˜๋‹ค.
N:M ๊ด€๊ณ„๋Š” ์ง์ ‘ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ด ํ…Œ์ด๋ธ”์€ ์‹ค์ œ๋กœ ์œ ์˜๋ฏธํ•œ ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ํ…Œ์ด๋ธ”์ด ์•„๋‹ˆ๋ผ
ํ•™์ƒ๊ณผ ๊ฐ•์˜์˜ ๊ด€๊ณ„๋งŒ์„ ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

์ด๋•Œ ์—ฐ๊ด€ ๊ด€๊ณ„์˜ ์ฃผ์ธ์„ ๋ช…ํ™•ํžˆ ๋งํ•˜๊ธฐ๋Š” ์กฐ๊ธˆ ์–ด๋ ต๋‹ค.
students, courses ํ…Œ์ด๋ธ” ๋ชจ๋‘ ์„œ๋กœ๋ฅผ FK๋กœ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๊ทธ๋ƒฅ ๋‘˜ ์ค‘ ํŽธํ•œ ๊ฒƒ์„ ์ฃผ์ธ์œผ๋กœ ํ•œ๋‹ค.
ํ›„์— ๊ธฐ์ˆ ํ•  @JoinTable์„ ์“ด ์ชฝ์„ ์ฃผ์ธ์ด๋ผ๊ณ  ๋ณธ๋‹ค.
์‹ค๋ฌด์—์„œ๋Š” ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ์ง€์–‘ํ•˜๋ฏ€๋กœ ์ด ์ •๋„๋งŒ ํ•ด๋„ ์ถฉ๋ถ„ํ•œ ๋“ฏ ํ•˜๋‹ค.
์ฐธ๊ณ : ์ธํ”„๋Ÿฐ QnA

๐Ÿ“Œ ์ฃผ์š” ํ‚ค์›Œ๋“œ

  • @ManyToMany: ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด JPA๊ฐ€ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ด์ค€๋‹ค.
    • ์–‘์ชฝ ์—”ํ‹ฐํ‹ฐ ๋ชจ๋‘์— ์‚ฌ์šฉํ•œ๋‹ค.
  • @JoinTable: ๋ช…์‹œ์ ์œผ๋กœ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์„ ์„ค์ •ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์ฃผ์ธ ์—”ํ‹ฐํ‹ฐ, ํ•œ ์—”ํ‹ฐํ‹ฐ์—์„œ๋งŒ ๋ช…์‹œํ•˜๋ฉด ๋œ๋‹ค.
    • name: ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ” ์ด๋ฆ„
    • joinColumns: ํ˜„์žฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ์žˆ๋Š” ๊ฐ’ ์ค‘ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์—์„œ FK๋กœ ์“ธ ์ปฌ๋Ÿผ๋ช…๋“ค
      • Student ์—”ํ‹ฐํ‹ฐ์—์„œ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ ๋‹ค๋ฉด Student์˜ id ํ•„๋“œ๋ฅผ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์˜ FK๋กœ ์“ด๋‹ค.
    • inverseJoinColumns: ๋ฐ˜๋Œ€ ์—”ํ‹ฐํ‹ฐ์—์„œ ์žˆ๋Š” ๊ฐ’ ์ค‘ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์—์„œ FK๋กœ ์“ธ ์ปฌ๋Ÿผ๋ช…๋“ค
      • Student ์—”ํ‹ฐํ‹ฐ์—์„œ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ ๋‹ค๋ฉด Course์˜ id ํ•„๋“œ๋ฅผ ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ”์˜ FK๋กœ ์“ด๋‹ค.
  • mappedBy: @JoinTable์„ ์“ฐ์ง€ ์•Š์€ ๋‹ค๋ฅธ ํ•œ ์ชฝ์—์„œ๋Š” mappedBy ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์—ฐ๊ด€ ๊ด€๊ณ„์˜ ์ฃผ์ธ/๋น„์ฃผ์ธ์„ ๊ตฌ๋ถ„ํ•œ๋‹ค.
    • Student๊ฐ€ ์ฃผ์ธ, Course๊ฐ€ ๋น„์ฃผ์ธ์ด๋ฏ€๋กœ Course์—์„œ ์‚ฌ์šฉํ•œ๋‹ค.

์ฝ”๋“œ

Student Entity

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

    private String name;
    private String department;
    private String email;
    ...
    @ManyToMany // N:M ๊ด€๊ณ„ ๋ช…์‹œ
    // ์—ฐ๊ฒฐ ํ…Œ์ด๋ธ” ์ง€์ •
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses;
}

Course Entity

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

    private String name;
    private String department;
    private String semester;
    private String professor;
    ...
    @ManyToMany(mappedBy="courses") // N:M ๊ด€๊ณ„ ๋ช…์‹œ
    private List<Student> students;
}

์ฐธ๊ณ ์ž๋ฃŒ

[JPA] ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘ ์ฃผ์ธ์— ๋Œ€ํ•ด์„œ (mappedBy)
TIL-15 JPA Entity ์—ฐ๊ด€๊ด€๊ณ„ 1๋Œ€1 ๊ด€๊ณ„
+๊ธฐํƒ€ ์ˆ˜์—… ์ž๋ฃŒ

๋ฐ˜์‘ํ˜•

'๐ŸŒฟSpring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring Security] #1 ์ธ์ฆ(Authentication) ๊ณผ ์ธ๊ฐ€(Authorization)์˜ ์ฐจ์ด  (1) 2025.05.13
[ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…] ํ™˜๊ฒฝ๋ณ€์ˆ˜ .env ์ ์šฉ ์ค‘: ์—ฐ๊ฒฐ์— ๋Œ€ํ•œ ์„ค์ •์ด pg_hba.conf ํŒŒ์ผ์— ์—†์Šต๋‹ˆ๋‹ค.  (1) 2025.04.09
Spring BOOT์˜ @RestController: HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ์ฒ˜๋ฆฌ ๊ณผ์ •  (2) 2025.03.04
[Spring] SpringBoot์—์„œ์˜ Bean ๊ฐœ๋… & ๋“ฑ๋ก ๋ฐฉ๋ฒ• ์ •๋ฆฌ  (0) 2025.02.17
[Spring] Spring์„ ์“ฐ๋Š” ์ด์œ : Spring์˜ ๋“ฑ์žฅ ๋ฐฐ๊ฒฝ๊ณผ ํŠน์ง• feat. EJB  (0) 2025.02.16
'๐ŸŒฟSpring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [Spring Security] #1 ์ธ์ฆ(Authentication) ๊ณผ ์ธ๊ฐ€(Authorization)์˜ ์ฐจ์ด
  • [ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…] ํ™˜๊ฒฝ๋ณ€์ˆ˜ .env ์ ์šฉ ์ค‘: ์—ฐ๊ฒฐ์— ๋Œ€ํ•œ ์„ค์ •์ด pg_hba.conf ํŒŒ์ผ์— ์—†์Šต๋‹ˆ๋‹ค.
  • Spring BOOT์˜ @RestController: HTTP ์š”์ฒญ์— ๋Œ€ํ•œ ์‘๋‹ต ์ฒ˜๋ฆฌ ๊ณผ์ •
  • [Spring] SpringBoot์—์„œ์˜ Bean ๊ฐœ๋… & ๋“ฑ๋ก ๋ฐฉ๋ฒ• ์ •๋ฆฌ
์†Œ์˜ ๐Ÿ€
์†Œ์˜ ๐Ÿ€
Hello World โœจ
  • ์†Œ์˜ ๐Ÿ€
    Soyoung's Dev Lab
    ์†Œ์˜ ๐Ÿ€
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
  • ๊ธ€์“ฐ๊ธฐ ๊ด€๋ฆฌ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (47)
      • ๐Ÿ“ข ๊ฒŒ์‹œํŒ (0)
      • ๐Ÿ“š ์ž๋ฃŒ๊ตฌ์กฐ & ์•Œ๊ณ ๋ฆฌ์ฆ˜ (1)
      • ๐ŸŒฟSpring (15)
      • โ˜•Java (8)
      • ๐Ÿ“Š ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (3)
      • ๐Ÿ“ค ๋ฐฐํฌ (4)
        • Docker (4)
        • AWS (0)
      • โš™๏ธ CS & ๊ธฐํƒ€ ๊ฐœ๋ฐœ ์ž๋ฃŒ (14)
      • ๐Ÿ–ฅ๏ธ ํ”„๋กœ์ ํŠธ (0)
      • ๐Ÿ‘ฉ‍๐Ÿ’ป ํ™œ๋™ & ํ›„๊ธฐ (0)
      • ๐Ÿต ์ด์•ผ๊ธฐ (2)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํƒœ๊ทธ
  • ๋งํฌ

    • github
    • velog
  • ๊ณต์ง€์‚ฌํ•ญ

  • ์ธ๊ธฐ ๊ธ€

  • ํƒœ๊ทธ

    ์ฝ”๋“œ์ž‡ ์Šคํ”„๋ฆฐํŠธ
    Java
    docker
    ๋ฐฐํฌ
    Spring
    GIT
    ์•Œ๊ณ ๋ฆฌ์ฆ˜
    ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
    ๊ฐœ๋ฐœ
    ์ฝ”๋”ฉํ…Œ์ŠคํŠธ
    ์„œ๋ฒ„
    ๊ฐ์ฒด์ง€ํ–ฅํ”„๋กœ๊ทธ๋ž˜๋ฐ
    ์ž๋ฃŒ๊ตฌ์กฐ
    ์œ„ํด๋ฆฌ ํŽ˜์ดํผ
    Spring Security
  • ์ตœ๊ทผ ๋Œ“๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
์†Œ์˜ ๐Ÿ€
Spring Data JPA ์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘ ๋ฐฉ๋ฒ• ์ •๋ฆฌ with ์˜ˆ์‹œ
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”