Skip to content

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ํ”ผ์ฆˆ ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#595

Open
wontop02 wants to merge 34 commits into
woowacourse:wontop02from
wontop02:jpa
Open

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ํ”ผ์ฆˆ ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#595
wontop02 wants to merge 34 commits into
woowacourse:wontop02from
wontop02:jpa

Conversation

@wontop02

@wontop02 wontop02 commented Jun 18, 2026

Copy link
Copy Markdown

์ฒดํฌ ๋ฆฌ์ŠคํŠธ

  • ๋ฏธ์…˜์˜ ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ชจ๋‘ ๊ตฌํ˜„ํ–ˆ๋‚˜์š”?
  • Gradle test๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, ๋ชจ๋“  ํ…Œ์ŠคํŠธ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผํ–ˆ๋‚˜์š”?
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜๋‚˜์š”?

๋ฒ ์ด์Šค ์ฝ”๋“œ ์„ ํƒ ์ฒดํฌ

  • ์ด์ „ ๋ฏธ์…˜์˜ ๋‚ด ์ฝ”๋“œ์—์„œ ์‹œ์ž‘
  • ์ด์ „ ๋ฏธ์…˜์˜ ํŽ˜์–ด์˜ ์ฝ”๋“œ์—์„œ ์‹œ์ž‘

0. ์‚ฌ์ „ํ•™์Šต - ํ‚ค์›Œ๋“œ

ํŒจ๋Ÿฌ๋‹ค์ž„ ์ฐจ์ด

  • ORM : ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ฐ์ฒด์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ๊ธฐ์ˆ ์ด๋‹ค.
  • ๊ฐ์ฒด-๊ด€๊ณ„ ์ž„ํ”ผ๋˜์Šค ๋ถˆ์ผ์น˜: ๊ฐ์ฒด ์ง€ํ–ฅ ์–ธ์–ด์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๊ฐ€ ๋ถˆ์ผ์น˜ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.
  • ์˜์†์„ฑ: ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•œ ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ  ์œ ์ง€๋˜๋Š” ํŠน์„ฑ์„ ๋งํ•œ๋‹ค.

ํ•ต์‹ฌ ๊ฐœ๋…

  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ: ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜๊ตฌ ์ €์žฅํ•˜๋Š” ํ™˜๊ฒฝ์œผ๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์ด์—์„œ ๊ฐ์ฒด๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๊ฐ€์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค.
  • 1์ฐจ ์บ์‹œ: ์—”ํ‹ฐํ‹ฐ๋ฅผ DB์— ์ €์žฅํ•˜๊ธฐ ์ „์—, ์ž„์‹œ๋กœ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • dirty checking: ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ 1์ฐจ ์บ์‹œ์— ์กด์žฌํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์˜ ์Šค๋ƒ…์ƒท์„ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ์— ํ˜„์žฌ์˜ ์—”ํ‹ฐํ‹ฐ์™€ ๋น„๊ตํ•ด, ๋ณ€๊ฒฝ์„ ๊ฐ์ง€ํ•˜๊ณ  ์ž๋™์œผ๋กœ UPDATE SQL์„ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • ์“ฐ๊ธฐ ์ง€์—ฐ: ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๊ธฐ ์ „๊นŒ์ง€ DB๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ์ง€ ์•Š๊ณ  ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ์“ฐ๊ธฐ ์ง€์—ฐ SQL ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•ด๋‘๋Š” ๊ฒƒ์ด๋‹ค.
  • flush: ํŠธ๋žœ์žญ์…˜์ด ์ปค๋ฐ‹๋˜๋Š” ์‹œ์ ์— SQL์„ ๋ชจ๋‘ DB๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

๋งคํ•‘

  • @Entity: ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋งคํ•‘๋˜๋Š” ์˜์†์„ฑ์„ ๊ฐ€์ง„ ์—”ํ‹ฐํ‹ฐ๋ผ๋Š” ๊ฒƒ์„ ํ‘œํ˜„ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
  • @Id: ํ•ด๋‹น ํ•„๋“œ๊ฐ€ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‹๋ณ„ํ•˜๋Š” Primary Key๋ผ๋Š” ๊ฒƒ์„ ํ‘œํ˜„ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
  • @GeneratedValue: Primary Key์˜ ๊ฐ’์„ ์ƒ์„ฑํ•˜๋Š” ์ „๋žต์„ ํ‘œํ˜„ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
  • @Column: ๊ฐ์ฒด์˜ ํ•„๋“œ๋ฅผ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ์— ๋งคํ•‘์‹œ์ผœ์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
  • @Table: ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์˜ ํ…Œ์ด๋ธ”์„ ์ •์˜ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

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

  • @ManyToOne: ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ @ManyToOne์ด ์ •์˜๋œ ํ•ด๋‹น ํ•„๋“œ์˜ ๋™์ผํ•œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.
  • @OneToMany: ํ•˜๋‚˜์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ @OneToMany๊ฐ€ ์ •์˜๋œ ํ•ด๋‹น ํ•„๋“œ์˜ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.
  • ๋‹จ/์–‘๋ฐฉํ–ฅ
    • ๋‹จ๋ฐฉํ–ฅ: ๋‘ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ด€๊ณ„๋ฅผ ๋งบ์„ ๋•Œ, ํ•œ ์ชฝ์˜ ์—”ํ‹ฐํ‹ฐ๋งŒ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
    • ์–‘๋ฐฉํ–ฅ: ๋‘ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๊ด€๊ณ„๋ฅผ ๋งบ์„ ๋•Œ, ์–‘ ์ชฝ์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์„œ๋กœ ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ: ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ์—”ํ‹ฐํ‹ฐ ์ค‘ ๋งŽ์€ ์ชฝ, ์ฆ‰ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์˜ ์™ธ๋ž˜ํ‚ค๋ฅผ ๊ฐ€์ง€๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์ด ๋˜์–ด ์™ธ๋ž˜ ํ‚ค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
  • cascade: ํŠน์ • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค ๋•Œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋„ ํ•จ๊ป˜ ์˜์† ์ƒํƒœ๋กœ ์ „์ด๋˜๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.
  • orphanRemoval: ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์™€์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ๋Š์–ด์ง„ ์ž์‹ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ž๋™์œผ๋กœ ์‚ญ์ œํ•˜๋Š” ์˜ต์…˜์ด๋‹ค.

ํŽ˜์น˜

  • EAGER: ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์˜๋ฏธํ•˜๋Š” ๋ง๋กœ, ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • LAZY: ์ง€์—ฐ ๋กœ๋”ฉ์„ ์˜๋ฏธํ•˜๋Š” ๋ง๋กœ, ๋ฐ์ดํ„ฐ ์กฐํšŒ ์‹œ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋Š” ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋งŒ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
  • fetch join: ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜์„ SQL ์กฐ์ธ๋ฌธ์œผ๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
  • @EntityGraph: JPQL๋กœ fetch join์„ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ , ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์„œ fetch join์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฟผ๋ฆฌ

  • JPQL: ํ…Œ์ด๋ธ”์ด ์•„๋‹Œ ์—”ํ‹ฐํ‹ฐ ๊ฐ์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ฐ์ฒด์ง€ํ–ฅ ์ฟผ๋ฆฌ ์–ธ์–ด์ด๋‹ค.
  • Native Query: JPA์—์„œ JPQL ๋Œ€์‹  SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

1. 1๋‹จ๊ณ„ - ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ด€์ฐฐ

1. ๊ด€์ฐฐ ๋Œ€์ƒ: dirty checking

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: @Transactionalย ๋ฉ”์„œ๋“œ์—์„œ entity ํ•„๋“œ ์ˆ˜์ • ํ›„ save ๋ฏธํ˜ธ์ถœ
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: commit ์‹œ์ ์— UPDATE ์ž๋™ ๋ฐœํ–‰
@Transactional  
public Theme save(Theme theme) {  
    themeRepository.save(theme);  
    theme.setName("change");  
    System.out.println("์•„์ด๋”” ์ƒ์„ฑ๋จ: " + theme.getId());  
    return theme;  
}
image
  • @Transactional ๋‚ด๋ถ€์—์„œ ์ˆ˜์ •ํ•ด์•ผ ํ•จ
    • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” ํŠธ๋žœ์žญ์…˜๊ณผ ์ƒ๋ช… ์ฃผ๊ธฐ๊ฐ€ ๊ฐ™๊ธฐ ๋•Œ๋ฌธ์—, ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ๋‚ด์—์„œ์˜ ๋ณ€ํ™”๋งŒ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
    • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๋Š” ๊ฐ์ฒด๊ฐ€ ๊ด€๋ฆฌ๋˜๋Š” ๊ณต๊ฐ„์„ ๋งํ•จ
  • ์˜์† ์ƒํƒœ์—ฌ์•ผ ํ•จ (save, find ๋“ฑ)

2. ๊ด€์ฐฐ ๋Œ€์ƒ: 1์ฐจ ์บ์‹œ

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์—์„œย findByIdย ๋‘ ๋ฒˆ ํ˜ธ์ถœ
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: ๋‘ ๋ฒˆ์งธ SELECT ์ƒ๋žต (1์ฐจ ์บ์‹œ ์ ์ค‘)
public Reservation findReservation(Long reservationId) {  
    System.out.println("์ฒซ ๋ฒˆ์งธ findById() ํ˜ธ์ถœ ์‹œ์ž‘");  
    reservationRepository.findById(reservationId);  
    System.out.println("์ฒซ ๋ฒˆ์งธ findById() ํ˜ธ์ถœ ๋");  
    System.out.println("๋‘ ๋ฒˆ์งธ findById() ํ˜ธ์ถœ ์‹œ์ž‘");  
    Optional<Reservation> reservation = reservationRepository.findById(reservationId);  
    System.out.println("๋‘ ๋ฒˆ์งธ findById() ํ˜ธ์ถœ ๋");  
    return reservation.get();  
}
image
  • ๋‘ ๋ฒˆ์งธ๋Š” SQL๋ฌธ์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ

3. ๊ด€์ฐฐ ๋Œ€์ƒ: ์“ฐ๊ธฐ ์ง€์—ฐ

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: saveย ํ˜ธ์ถœ ํ›„ย flushย ์ „ยทํ›„์˜ DB ์ƒํƒœ ๋น„๊ต
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: INSERT๊ฐ€ commit/flush ์‹œ์ ์— ์ผ๊ด„ ๋ฐœํ–‰
// Theme์˜ GeneratedValue ์ฃผ์„ ์ฒ˜๋ฆฌ

@Test  
void test() {  
    Theme theme1 = new Theme(1L, "๋ฐฉํƒˆ์ถœ1", "์„ค๋ช…", "์ธ๋„ค์ผ");  
    Theme theme2 = new Theme(2L, "๋ฐฉํƒˆ์ถœ2", "์„ค๋ช…", "์ธ๋„ค์ผ");  
  
    System.out.println("์ฒซ ๋ฒˆ์งธ save ํ˜ธ์ถœ ์ „");  
    themeRepository.save(theme1);  
    System.out.println("์ฒซ ๋ฒˆ์งธ save ํ˜ธ์ถœ ๋");  
  
    System.out.println("๋‘ ๋ฒˆ์งธ save ํ˜ธ์ถœ ์ „");  
    themeRepository.save(theme2);  
    System.out.println("๋‘ ๋ฒˆ์งธ save ํ˜ธ์ถœ ๋");  
  
    System.out.println("flush ํ˜ธ์ถœ ์ „");  
    entityManager.flush();  
    System.out.println("flush ํ˜ธ์ถœ ํ›„");  
}
image
  • select๋Š” ๊ฐ์ฒด๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœ๋˜๋Š” ๊ฒƒ
    • ์—†์œผ๋ฉด INSERT ์ฟผ๋ฆฌ๋ฅผ ์“ฐ๊ธฐ ์ง€์—ฐ ์ €์žฅ์†Œ์— ๋‹ด์•„๋‘ 
    • ์žˆ์œผ๋ฉด UPDATE ์ฟผ๋ฆฌ๋ฅผ ์“ฐ๊ธฐ ์ง€์—ฐ ์ €์žฅ์†Œ์— ๋‹ด์•„๋‘ 
  • flush ์‹œ์ ์— INSERT๊ฐ€ ๋‚ ์•„๊ฐ

4. ๊ด€์ฐฐ ๋Œ€์ƒ: flush ์‹œ์ 

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: ๋ช…์‹œ์ ย flushย ํ˜ธ์ถœ / ํŠธ๋žœ์žญ์…˜ ์ข…๋ฃŒ / JPQL ์‹คํ–‰ ์ง์ „
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ โ†’ DB ๋™๊ธฐํ™” ํŠธ๋ฆฌ๊ฑฐ
// Theme์˜ GeneratedValue ์ฃผ์„ ์ฒ˜๋ฆฌ

@Test  
void test() {  
    Theme theme1 = new Theme(1L, "๋ฐฉํƒˆ์ถœ1", "์„ค๋ช…", "์ธ๋„ค์ผ");  
    Theme theme2 = new Theme(2L, "๋ฐฉํƒˆ์ถœ2", "์„ค๋ช…", "์ธ๋„ค์ผ");  
  
    System.out.println("์ฒซ ๋ฒˆ์งธ save ํ˜ธ์ถœ ์ „");  
    themeRepository.save(theme1);  
    System.out.println("์ฒซ ๋ฒˆ์งธ save ํ˜ธ์ถœ ๋");  
  
    System.out.println("๋‘ ๋ฒˆ์งธ save ํ˜ธ์ถœ ์ „");  
    themeRepository.save(theme2);  
    System.out.println("๋‘ ๋ฒˆ์งธ save ํ˜ธ์ถœ ๋");  
  
    System.out.println("findAll ํ˜ธ์ถœ ์ „");  
    themeRepository.findAll();  
    System.out.println("findAll ํ˜ธ์ถœ ํ›„");  
}
image
  • save๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” insert๋ฌธ์ด ๋˜์ง€ ์•Š๋Š”๋ฐ, findAll(JPQL)์ด ํ˜ธ์ถœ๋˜๋‹ˆ flush๋ฅผ ์ง์ ‘ ํ•˜์ง€ ์•Š์•„๋„ ๊ธฐ์กด์˜ insert๊ฐ€ ๋ชจ๋‘ ํ˜ธ์ถœ๋จ
  • JPQL ์‹คํ–‰ ์ง์ „์— flush๊ฐ€ ์ž๋™์œผ๋กœ ํ˜ธ์ถœ๋จ
  • flush ๊ธฐ์ค€์œผ๋กœ ํŠธ๋žœ์žญ์…˜์ด ๋๋‚˜๋Š” ๊ฑด ์ค„ ์•Œ์•˜๋Š”๋ฐ, ๊ทธ๊ฑด ๋˜ ์•„๋‹ˆ๋ผ๊ณ  ํ•จ. ํŠธ๋žœ์žญ์…˜์„ ๋๋‚˜๋ ค๋ฉด commit์„ ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์Šคํ”„๋ง์ด ํŠธ๋žœ์žญ์…˜์„ ๋๋‚ผ ๋•Œ ์•Œ์•„์„œ flush๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  commitํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ—ท๊ฐˆ๋ฆฐ ๊ฒƒ

5. ๊ด€์ฐฐ ๋Œ€์ƒ: fetch ๊ธฐ๋ณธ๊ฐ’

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: @ManyToOneย vsย @OneToManyย ๋ฌด๋ช…์‹œ ์‹œ
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ โ†’ DB ๋™๊ธฐํ™” ํŠธ๋ฆฌ๊ฑฐ
// Slot์˜ @ManyToOne LAZY ๋ฌด๋ช…์‹œ, ๋ช…์‹œ
  • fetch ์ฆ‰์‹œ ๋กœ๋”ฉ(๊ธฐ๋ณธ๊ฐ’) - EAGER
image
  • fetch ์ง€์—ฐ ๋กœ๋”ฉ - LAZY
image
  • select๋ฌธ์„ 2๋ฒˆ ๋‚ ๋ ค Reservation๊ณผ ReservationTime์„ ๋”ฐ๋กœ ๊ฐ€์ ธ์˜ค๊ณ , ์•ˆ ์“ฐ์ด๊ณ  ์žˆ๋Š” Theme์€ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค.

6. ๊ด€์ฐฐ ๋Œ€์ƒ: LazyInitializationException

  • ์–ด๋–ค ์ฝ”๋“œ๋กœ ํ™•์ธํ•˜๋Š”๊ฐ€: ํŠธ๋žœ์žญ์…˜ ๋ฐ–์—์„œ LAZY ํ•„๋“œ ์ ‘๊ทผ
  • ๋ฌด์—‡์„ ๋ณธ๋‹ค: ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๋‹ซํžŒ ํ›„ ํ”„๋ก์‹œ ๋ฏธ์ดˆ๊ธฐํ™”
@Test  
void test() {  
    Theme theme = themeRepository.save(new Theme("๊ณตํฌ ๋ฐฉํƒˆ์ถœ", "์„ค๋ช…", "์ธ๋„ค์ผ"));  
    ReservationTime time = timeRepository.save(new ReservationTime(LocalTime.of(14, 0)));  
    Reservation savedReservation = reservationRepository.save(  
            new Reservation("์ฃผ์˜", new Slot(LocalDate.of(2026, 5, 2), time, theme)));  
  
    Long targetId = savedReservation.getId();
  
    System.out.println("์˜ˆ์•ฝ ์กฐํšŒ ์‹œ์ž‘ (Reservation ๋‚ด๋ถ€์—์„œ ํŠธ๋žœ์žญ์…˜ ์—ด๋ฆฌ๊ณ  ๋‹ซํž˜)");  
    Reservation reservation = reservationRepository.findById(targetId).orElseThrow();  
    System.out.println("์˜ˆ์•ฝ ์กฐํšŒ ๋ (์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ์‚ฌ๋ผ์ง, reservation์€ ์ค€์˜์† ์ƒํƒœ)");  
  
    System.out.println("ํ”„๋ก์‹œ ๊ฐ์ฒด ๊ฐ€์ ธ์˜ค๊ธฐ");  
    ReservationTime proxyTime = reservation.getTime();  
    System.out.println("ํ”„๋ก์‹œ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” ์‹œ๋„");  
    proxyTime.getStartAt();  
}
image
  • Reservation์ด timeId์™€ themeId๋ฅผ ๊ฐ–๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ID๋งŒ ๊ฐ–๊ณ  ์žˆ์Œ. ๊ทธ๋ž˜์„œ proxyTime.getId();ํ•˜๋ฉด ์˜ˆ์™ธ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ

2. 2-3๋‹จ๊ณ„ - LAZY ๋กœ๋”ฉ์˜ ํ•œ๊ณ„

LAZY ๋กœ๋”ฉ์„ ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ๋‹ค.

  • LAZY ๋กœ๋”ฉ ๋ฐฉ์‹
  1. findByMemberId๋Š” Reservation ํ…Œ์ด๋ธ”๋งŒ SELECTํ•ด์„œ ๊ฐ€์ ธ์˜จ๋‹ค.
  2. Response DTO๋ฅผ ๋งŒ๋“ค ๋•Œ Reservation์ด ๊ฐ–๊ณ  ์žˆ๋Š” Time, Theme ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ์— ์ ‘๊ทผํ•˜๋ฉด LAZY ๋กœ๋”ฉ์ด ๋ฐœ๋™ํ•ด์„œ Time, Theme ํ…Œ์ด๋ธ”์—์„œ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
    => ์˜ˆ์•ฝ์ด 10๊ฐœ์ด๋ฉด ๊ฐ ์˜ˆ์•ฝ์˜ ํ…Œ๋งˆ์™€ ์‹œ๊ฐ„์„ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ 20๋ฒˆ ๋‚˜๊ฐ€๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

์ด ๊ฒฝ์šฐ๋Š” @Query๋ฅผ ์จ์„œ JPQL๋กœ ํŽ˜์น˜ ์กฐ์ธ์„ ๋ช…์‹œํ•˜๊ฑฐ๋‚˜, @EntityGraph๋ฅผ ์จ์•ผ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

4๋‹จ๊ณ„ - ์˜ˆ์•ฝ ์ทจ์†Œ, ๋Œ€๊ธฐ ์ž๋™ ์Šน์ธ

  • ์˜ˆ์•ฝ ์ทจ์†Œํ•˜๊ณ , ์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ์Šน์ธํ•˜๋Š” ๊ณผ์ •์˜ SQL๋ฌธ์„ ๋ณด๋ ค๊ณ  ํ–ˆ์œผ๋‚˜ DataIntegrityViolationException์ด ๋ฐœ์ƒํ•จ(Unique ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜)

    • ์ด์œ : ๋กœ์ง์€ ์˜ˆ์•ฝ DELETE ์ดํ›„ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ๋ณ€ํ™˜ํ•ด ์˜ˆ์•ฝ INSERT ์ˆœ์„œ๋Œ€๋กœ ๋˜์–ด ์žˆ์ง€๋งŒ, ํ•˜์ด๋ฒ„๋„ค์ดํŠธ(JPA)์˜ ์ฟผ๋ฆฌ ์‹คํ–‰ ์ˆœ์„œ๋Š” ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ INSERT ๋จผ์ € ์‹คํ–‰ํ•˜๊ณ  DELETE๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ
  • ์ž‘์„ฑ ์ฝ”๋“œ

@Transactional  
public void deleteReservation(Long id) {  
    Reservation reservation = reservationService.findReservation(id);  
  
    Slot slot = new Slot(reservation.getDate(), reservation.getTime(), reservation.getTheme());  
    Waits waits = waitService.findBySlot(slot);  
  
    if (waits.isEmptyWaitsBySlot(slot)) {  
        reservationService.delete(reservation, false);  
        return;  
    }  
    System.out.println("์˜ˆ์•ฝ ์‚ญ์ œ ์‹œ์ž‘");  
    reservationService.deleteAndFlush(reservation, false);  
    System.out.println("์˜ˆ์•ฝ ์‚ญ์ œ ์™„๋ฃŒ");  
    confirmFirstWait(waits.firstWaitBySlot(slot));  
    System.out.println("์˜ˆ์•ฝ ์‚ญ์ œ, ์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์ž๋™ ์Šน์ธ ๋");  
}

private void confirmFirstWait(Wait firstWait) {  
    Reservation reservationWithoutId = new Reservation(firstWait.getMember(),  
            new Slot(firstWait.getReservationDate(), firstWait.getTime(), firstWait.getTheme()));  
    System.out.println("์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ์ƒ์„ฑ ์‹œ์ž‘");  
    reservationService.save(reservationWithoutId, true);  
    System.out.println("์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ์ƒ์„ฑ ๋");  
    System.out.println("์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์‚ญ์ œ ์‹œ์ž‘");  
    waitService.delete(firstWait.getId(), true);  
    System.out.println("์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์‚ญ์ œ ๋");  
}
  • ์‹คํ–‰ ๊ฒฐ๊ณผ
์˜ˆ์•ฝ ์‚ญ์ œ ์‹œ์ž‘
Hibernate: 
    delete 
    from
        reservation 
    where
        id=?
์˜ˆ์•ฝ ์‚ญ์ œ ์™„๋ฃŒ
์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ์ƒ์„ฑ ์‹œ์ž‘
Hibernate: 
    insert 
    into
        reservation
        (member_id, reservation_date, theme_id, time_id, id) 
    values
        (?, ?, ?, ?, default)
์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ๋ฅผ ์˜ˆ์•ฝ์œผ๋กœ ์ƒ์„ฑ ๋
์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์‚ญ์ œ ์‹œ์ž‘
์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์‚ญ์ œ ๋
์˜ˆ์•ฝ ์‚ญ์ œ, ์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์ž๋™ ์Šน์ธ ๋
Hibernate: 
    delete 
    from
        wait 
    where
        id=?
  • "์˜ˆ์•ฝ ์‚ญ์ œ, ์ฒซ ๋ฒˆ์งธ ๋Œ€๊ธฐ ์ž๋™ ์Šน์ธ ๋"์ด๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ”๋“œ ๋งจ ๋ฐ‘์— ์ž‘์„ฑํ•ด๋’€๋Š”๋ฐ, ๋ฉ”์„œ๋“œ๊ฐ€ ๋‹ค ์‹คํ–‰๋œ ํ›„์— DELETE ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋จ. ์ฆ‰, flush๋ฅผ ์•ˆ ํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ๋๋‚  ๋•Œ ์ฟผ๋ฆฌ๋ฌธ์ด DB๋กœ ๋ณด๋‚ด์ง„๋‹ค๋Š” ๋œป

  • ํŠธ๋žœ์žญ์…˜์ด ๋๋‚œ ํ›„ ์ฟผ๋ฆฌ๋ฌธ์€ INSERT -> UPDATE -> DELETE ์ˆœ์œผ๋กœ ๋ณด๋‚ด์ง

wontop02 added 30 commits May 31, 2026 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant