Skip to content

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ์„œ์—ฌ(๋ฐ•์„ฑ์—ด) ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#586

Open
yeo-li wants to merge 2 commits into
woowacourse:yeo-lifrom
yeo-li:jpa-mission
Open

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ์„œ์—ฌ(๋ฐ•์„ฑ์—ด) ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#586
yeo-li wants to merge 2 commits into
woowacourse:yeo-lifrom
yeo-li:jpa-mission

Conversation

@yeo-li

@yeo-li yeo-li commented Jun 18, 2026

Copy link
Copy Markdown

๋‹จ๊ณ„๋ณ„ ๋„๋‹ฌ ์ง€์ 

0๋‹จ๊ณ„

  • ๊ธฐ์กด JDBC Repository์—์„œ ์‚ฌ์šฉํ•˜๋˜ SQL ํŒจํ„ด๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ์ œ์•ฝ์„ ๋จผ์ € ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.
  • schema.sql์˜ generated column, unique index๊ฐ€ ๋‹จ์ˆœ DDL์ด ์•„๋‹ˆ๋ผ ์˜ˆ์•ฝ ์ค‘๋ณต/soft delete ์ •์ฑ…์„ ๋ณด์žฅํ•˜๋Š” ํ•ต์‹ฌ ์žฅ์น˜๋ผ๋Š” ์ ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

1๋‹จ๊ณ„ - JPA ์ „ํ™˜

  • Reservation, Time, Theme๋ฅผ JPA Entity๋กœ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ReservationRepository๋ฅผ JpaRepository ๊ธฐ๋ฐ˜์œผ๋กœ ์ „ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋‹จ์ˆœ ์กฐํšŒ๋Š” ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋กœ, ๋ฝ/๋ณตํ•ฉ ์กฐ๊ฑด/๋ฒŒํฌ soft delete๋Š” JPQL๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • schema.sql์˜ ์ œ์•ฝ ์กฐ๊ฑด๊ณผ ์ตœ๋Œ€ํ•œ ๋งž๋„๋ก generated column, index, enum, version ์ปฌ๋Ÿผ์„ Entity์— ๋ฐ˜์˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

2๋‹จ๊ณ„ - ๋ณธ์ธ ์˜ˆ์•ฝ ์กฐํšŒ์™€ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ด€์ฐฐ

  • GET /reservations-mine?name={name} API๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋กœ๊ทธ์ธ/ํšŒ์› ๋„๋ฉ”์ธ์ด ์•„์ง ์—†์–ด์„œ "๋ณธ์ธ" ์‹๋ณ„์€ name query parameter๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • findReservationByName ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.
  • Repository์˜ ์ปค์Šคํ…€ update()๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  dirty checking ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.
  • @Version์„ ์ ์šฉํ•˜๊ณ , ์š”์ฒญ version๊ณผ ํ˜„์žฌ entity version์„ ๋น„๊ตํ•ด ์˜ค๋ž˜๋œ ์ˆ˜์ • ์š”์ฒญ์„ ๊ฐ์ง€ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์•ฝ ์ทจ์†Œ, ์˜ˆ์•ฝ ์ˆ˜์ •, ๋Œ€๊ธฐ ์Šน์ธ ํ๋ฆ„๋„ managed entity ์ƒํƒœ ๋ณ€๊ฒฝ์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์•„์ง ๋‚จ์€ ๊ฒƒ

  • 3~4๋‹จ๊ณ„๋Š” ์ง„ํ–‰ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.
  • @ManyToOne(fetch = LAZY) ์ „ํ™˜๊ณผ LazyInitializationException, N+1, EntityGraph ์ ์šฉ ์—ฌ๋ถ€๋Š” ์•„์ง ์‹คํ—˜ํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.
  • /reservations-mine์€ ์‹ค์ œ ํšŒ์› ์ธ์ฆ ๊ธฐ๋ฐ˜์ด ์•„๋‹ˆ๋ผ ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ์กฐํšŒ๋ผ ๋™๋ช…์ด์ธ ๋ฌธ์ œ๊ฐ€ ๋‚จ์•„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • waitingNumber๋Š” /reservations-mine ์‘๋‹ต์—์„œ ์•„์ง ๊ณ„์‚ฐํ•˜์ง€ ์•Š๊ณ  null๋กœ ๋‚ด๋ ค์ค๋‹ˆ๋‹ค.

๋ฐœํ–‰ SQL ๋ฐœ์ทŒ

Hibernate DDL ๊ด€์ฐฐ

JPA Entity๋งŒ์œผ๋กœ ์ƒ์„ฑํ•œ DDL์€ ๋‹ค์Œ์ฒ˜๋Ÿผ Entity ํ•„๋“œ ์ค‘์‹ฌ์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.

create table reservation (
    date date not null,
    deleted_at timestamp(6),
    id bigint generated by default as identity,
    theme_id bigint not null,
    time_id bigint not null,
    version bigint not null,
    name varchar(255) not null,
    status enum ('ACTIVE','CANCELED','WAITING') not null,
    primary key (id)
)

๋ฐ˜๋ฉด ๊ธฐ์กด schema.sql์—๋Š” soft delete์™€ ์˜ˆ์•ฝ ์ค‘๋ณต ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ generated column, unique index๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

active_date DATE GENERATED ALWAYS AS (
    CASE WHEN status = 'ACTIVE' AND deleted_at IS NULL THEN date ELSE NULL END
);

CREATE UNIQUE INDEX uq_active_reservation
ON reservation (active_date, active_time_id, active_theme_id);

์ด ์ฐจ์ด๋ฅผ ๋ณด๊ณ  Entity ๋งคํ•‘๋งŒ์œผ๋กœ๋Š” ํ˜„์žฌ ๋„๋ฉ”์ธ์˜ DB ์ œ์•ฝ์„ ์ถฉ๋ถ„ํžˆ ํ‘œํ˜„ํ•˜๊ธฐ ์–ด๋ ต๋‹ค๋Š” ์ ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

INSERT SQL ๊ด€์ฐฐ

๊ธฐ์กด JDBC๋Š” ๋„ฃ์„ ์ปฌ๋Ÿผ์„ ์ง์ ‘ ์ง€์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

INSERT INTO reservation (name, date, time_id, theme_id, status)
VALUES (?, ?, ?, ?, ?)

JPA ์ „ํ™˜ ํ›„์—๋Š” Entity ์ƒํƒœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‹ค์Œ ํ˜•ํƒœ์˜ SQL์ด ๋‚˜๊ฐ”์Šต๋‹ˆ๋‹ค.

insert into reservation
    (date, deleted_at, name, status, theme_id, time_id, version, id)
values
    (?, ?, ?, ?, ?, ?, ?, default)

deleted_at, version์ฒ˜๋Ÿผ JDBC์—์„œ๋Š” DB default์— ๋งก๊ฒผ๋˜ ์ปฌ๋Ÿผ๋„ JPA insert์—๋Š” ํฌํ•จ๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

findById ํ›„ ์—ฐ๊ด€ ๊ฐ์ฒด ์ ‘๊ทผ SQL

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๊ด€์ฐฐํ–ˆ์Šต๋‹ˆ๋‹ค.

Reservation reservation = reservationRepository.findById(reservationId).orElseThrow();
reservation.getTime().getStartAt();

์ฒ˜์Œ์—๋Š” getTime() ์‹œ์ ์— ์ถ”๊ฐ€ select๊ฐ€ ๋‚˜๊ฐˆ ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒํ–ˆ์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” @ManyToOne ๊ธฐ๋ณธ๊ฐ’์ด EAGER๋ผ findById ์‹œ์ ์— join์ด ํ•จ๊ป˜ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

select
    r1_0.id,
    r1_0.date,
    r1_0.deleted_at,
    r1_0.name,
    r1_0.status,
    r1_0.theme_id,
    t1_0.id,
    t1_0.name,
    r1_0.time_id,
    t2_0.id,
    t2_0.start_at,
    r1_0.version
from reservation r1_0
join theme t1_0 on t1_0.id = r1_0.theme_id
join reservation_time t2_0 on t2_0.id = r1_0.time_id
where r1_0.id = ?

๋ง์„ค์ธ ๊ฒฐ์ •

1. schema.sql์„ ์ง€์šธ์ง€, Entity๋กœ ์ตœ๋Œ€ํ•œ ํ‘œํ˜„ํ• ์ง€

์ฒ˜์Œ์—๋Š” JPA๋ฅผ ์“ฐ๋ฉด schema.sql์„ ์—†์• ๋„ ๋  ๊ฒƒ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์˜ ํ•ต์‹ฌ ์ œ์•ฝ์€ soft delete๋ฅผ ๊ณ ๋ คํ•œ unique index์™€ generated column์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ Entity์— columnDefinition, @Index๋ฅผ ์ถ”๊ฐ€ํ•ด ์ตœ๋Œ€ํ•œ ๋งž์ท„์ง€๋งŒ, ์ด ๋ฐฉ์‹๋„ ๊ฒฐ๊ตญ DB๋ณ„ DDL ๋ฌธ์ž์—ด์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.
์šด์˜ ์ˆ˜์ค€์—์„œ๋Š” schema.sql์ด๋‚˜ migration ๋„๊ตฌ๊ฐ€ ์—ฌ์ „ํžˆ ํ•„์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ์Šต๋‹ˆ๋‹ค.

2. ์˜ˆ์•ฝ ์ˆ˜์ • ๋กœ์ง์„ JPQL update๋กœ ์œ ์ง€ํ• ์ง€, dirty checking์œผ๋กœ ๋ฐ”๊ฟ€์ง€

์ฒ˜์Œ์—๋Š” JDBC ์‹œ์ ˆ์˜ update() ํ๋ฆ„์„ JPA Repository ์•ˆ์— JPQL ๋ฒŒํฌ update๋กœ ์˜ฎ๊ฒผ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ์ด ๋ฐฉ์‹์€ JPA๋ฅผ ์“ฐ๋ฉด์„œ๋„ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ์žฅ์ ์„ ๊ฑฐ์˜ ์“ฐ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ตœ์ข…์ ์œผ๋กœ๋Š” ๋‹ค์Œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค.

Reservation reservation = reservationRepository.findByIdAndDeletedAtIsNull(id)
    .orElseThrow();

reservation.update(date, time, theme);

์ด์ œ save()๋‚˜ ์ปค์Šคํ…€ update() ์—†์ด flush/commit ์‹œ์ ์— dirty checking์œผ๋กœ UPDATE๊ฐ€ ๋ฐœํ–‰๋ฉ๋‹ˆ๋‹ค.

ํ”๋“ค๋ฆฐ ํ•œ ์žฅ๋ฉด

๊ฐ€์žฅ ํ—ท๊ฐˆ๋ ธ๋˜ ์žฅ๋ฉด์€ findById(reservationId).getTime().getStartAt()์„ ๊ด€์ฐฐํ•  ๋•Œ์˜€์Šต๋‹ˆ๋‹ค.

์ฒ˜์Œ ์˜ˆ์ƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์•˜์Šต๋‹ˆ๋‹ค.

  1. reservation๋งŒ select
  2. getTime() ์ ‘๊ทผ ์‹œ reservation_time ์ถ”๊ฐ€ select

ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” findById ํ•œ ๋ฒˆ์— theme, reservation_time์ด join์œผ๋กœ ๊ฐ™์ด ์กฐํšŒ๋์Šต๋‹ˆ๋‹ค.
๊ฐ์ฒด ๊ทธ๋ž˜ํ”„๋ฅผ ํƒ์ƒ‰ํ•˜๋Š” ์ฝ”๋“œ๋งŒ ๋ณด๊ณ  SQL์„ ์˜ˆ์ƒํ•˜๋ฉด ํ‹€๋ฆด ์ˆ˜ ์žˆ๊ณ , fetch ์ „๋žต์„ ๋ฐ˜๋“œ์‹œ ๊ฐ™์ด ๋ด์•ผ ํ•œ๋‹ค๋Š” ์ ์„ ๋ฐฐ์› ์Šต๋‹ˆ๋‹ค.

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