"Spring Boot 4.0.2 ๊ธฐ๋ฐ์ ์ธ์ ์ธ์ฆ๊ณผ Soft Delete๋ฅผ ๊ตฌํํ ๊ณ ์ฑ๋ฅ ์ผ์ ๊ด๋ฆฌ API"
Spring Boot 4.0.2 ํ๋ ์์ํฌ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ถ๋ ์ผ์ ๊ด๋ฆฌ ๋ฐฑ์๋ ์๋น์ค์ ๋๋ค. ๋จ์ํ CRUD๋ฅผ ๋์ด, ๋ฐ์ดํฐ ๋ณด์กด(Soft Delete) ์ ๋ต๊ณผ ์ธ์ (Session) ๊ธฐ๋ฐ์ ๋ณด์ ์ธ์ฆ ์์คํ ์ ๊ตฌ์ถํ์ฌ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ๊ณผ ๋ณด์์ฑ์ ๋์์ ํ๋ณดํ์ต๋๋ค.
- Spring Boot 4.0.2 ์ฌ์ฉ: ์ต์ ์์ ํ ๋ฒ์ ์ ์ ์ฉํ์ฌ ์ต์ ํ๋ ์ฑ๋ฅ๊ณผ ๋ณด์ ํจ์น ์ ์ฉ
- ๐ ์ธ์
๊ธฐ๋ฐ ์ธ์ฆ (Session Auth):
HttpSession์ ํ์ฉํ์ฌ ์๋ฒ ์ธก์์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌํ๊ณ ๊ถํ์ ์ ์ด (๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ง ๋ณธ์ธ์ ์ผ์ ์์ /์ญ์ ๊ฐ๋ฅ) - ๐๏ธ ์ํํธ ๋๋ฆฌํธ (Soft Delete):
@SoftDelete๋ฅผ ํ์ฉ, DB์์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ์ง ์๊ณdeleted = true์ํ๋ก ๋ณด์กดํ์ฌ ๋ฐ์ดํฐ ๋ณต๊ตฌ ๊ฐ๋ฅ์ฑ ํ๋ณด - ๐ก๏ธ ๊ฒฌ๊ณ ํ ์์ธ ์ฒ๋ฆฌ:
GlobalExceptionHandler๋ฅผ ํตํด ๊ฒ์ฆ(@Valid) ์คํจ์ ๋น์ฆ๋์ค ๋ก์ง ์์ธ๋ฅผ ์ผ๊ด๋ JSON ํฌ๋งท์ผ๋ก ์๋ต - ๐ ํ์ด์ง๋ค์ด์
: ๋์ฉ๋ ๋ฐ์ดํฐ ์กฐํ ์ฑ๋ฅ์ ๊ณ ๋ คํ Spring Data JPA ํ์ด์ง(
Pageable) ์ ์ฉ - โฐ JPA Auditing:
@EnableJpaAuditing๊ณผBaseEntity๋ฅผ ํ์ฉํ์ฌ ์์ฑ์ผ/์์ ์ผ์ ์๋์ผ๋ก ๊ด๋ฆฌ
์ฌ์ฉ์(User)๋ ์ฌ๋ฌ ์ผ์ (Schedule)๊ณผ ๋๊ธ(Comment)์ ๊ฐ์ง ์ ์์ผ๋ฉฐ, ๋ชจ๋ ๋ฐ์ดํฐ๋ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ญ์ ๋์ง ์๊ณ deleted ํ๋๊ทธ๋ก ๊ด๋ฆฌ๋ฉ๋๋ค.
๐๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ์์ธ ๋ณด๊ธฐ (Click)
| ์ปฌ๋ผ๋ช | ํ์ | ์ ์ฝ์กฐ๊ฑด | ์ค๋ช |
|---|---|---|---|
| id | BIGINT |
PK, AI (์๋ ์ฆ๊ฐ) | ์ฌ์ฉ์ ๊ณ ์ ID |
VARCHAR(255) |
Unique, Not Null | ๋ก๊ทธ์ธ ์ด๋ฉ์ผ | |
| password | VARCHAR(255) |
Not Null | ์ํธํ๋ ๋น๋ฐ๋ฒํธ (BCrypt) |
| name | VARCHAR(20) |
Not Null | ์ฌ์ฉ์ ์ด๋ฆ |
| created_at | DATETIME |
Not Null | ์์ฑ์ผ (BaseEntity) |
| modified_at | DATETIME |
Not Null | ์์ ์ผ (BaseEntity) |
| deleted | BOOLEAN |
Default false |
Soft Delete ์ฌ๋ถ |
| ์ปฌ๋ผ๋ช | ํ์ | ์ ์ฝ์กฐ๊ฑด | ์ค๋ช |
|---|---|---|---|
| id | BIGINT |
PK, AI (์๋ ์ฆ๊ฐ) | ์ผ์ ๊ณ ์ ID |
| user_id | BIGINT |
FK (User.id) | ์์ฑ์ ID |
| title | VARCHAR(50) |
Not Null | ์ผ์ ์ ๋ชฉ |
| content | VARCHAR(50) |
Not Null | ์ผ์ ๋ด์ฉ |
| author | VARCHAR(20) |
Not Null | ์์ฑ์๋ช |
| created_at | DATETIME |
Not Null | ์์ฑ์ผ |
| modified_at | DATETIME |
Not Null | ์์ ์ผ |
| deleted | BOOLEAN |
Default false |
Soft Delete ์ฌ๋ถ |
| ์ปฌ๋ผ๋ช | ํ์ | ์ ์ฝ์กฐ๊ฑด | ์ค๋ช |
|---|---|---|---|
| id | BIGINT |
PK, AI (์๋ ์ฆ๊ฐ) | ๋๊ธ ๊ณ ์ ID |
| schedule_id | BIGINT |
FK (Schedule.id) | ์ฐ๊ด๋ ์ผ์ ID |
| user_id | BIGINT |
FK (User.id) | ๋๊ธ ์์ฑ์ ID |
| content | VARCHAR(50) |
Not Null | ๋๊ธ ๋ด์ฉ |
| created_at | DATETIME |
Not Null | ์์ฑ์ผ |
| modified_at | DATETIME |
Not Null | ์์ ์ผ |
| deleted | BOOLEAN |
Default false |
Soft Delete ์ฌ๋ถ |
๋ณต์ก๋๋ฅผ ์ค์ด๊ณ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์ ๋ช
ํํ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์ฃผ์ ์ํฐํฐ(Entity)์ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ค์ ์ผ๋ก ์๊ฐํํ์ต๋๋ค.
BaseEntity๋ฅผ ํตํ ๊ณตํต ํ๋(์์ฑ์ผ/์์ ์ผ) ์์ ๊ตฌ์กฐ์ User, Schedule, Comment ๊ฐ์ ์ฐธ์กฐ ๊ด๊ณ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
์ถ๊ฐ๋ก, GlobalExceptionHandler๋ฅผ ์ ์ํ์ฌ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ ์ญ์ ์ผ๋ก ์ค์ ์ง์คํํ๊ณ ์์ง๋๋ฅผ ๋์์ต๋๋ค.
| ๊ตฌ๋ถ | ๊ธฐ์ | ๋ฒ์ ๋ฐ ์ค๋ช |
|---|---|---|
| Language | Java | JDK 17 (LTS) |
| Framework | Spring Boot | 4.0.2 |
| Database | MySQL | 8.0 / InnoDB |
| ORM | Spring Data JPA | Hibernate Core (SoftDelete ์ง์) |
| Build Tool | Gradle | 8.x |
| Dependencies | Lombok, Validation | ์ฝ๋ ๊ฐ์ํ ๋ฐ ๋ฐ์ดํฐ ๊ฒ์ฆ |
์ด ํ๋ก์ ํธ๋ฅผ ๋ก์ปฌ ํ๊ฒฝ์์ ์คํํ๊ธฐ ์ํ ๋จ๊ณ๋ณ ๊ฐ์ด๋์ ๋๋ค.
- JDK 17 ์ด์ ์ค์น ํ์ธ
- MySQL ์๋ฒ ์คํ ์ค (๊ธฐ๋ณธ ํฌํธ 3306)
ํฐ๋ฏธ๋(๋๋ MySQL Workbench)์์ ์๋ SQL์ ์คํํ์ฌ DB์ ๊ณ์ ์ ์์ฑํฉ๋๋ค.
-- root ๊ณ์ ์ผ๋ก ์ ์ ํ ์คํ
CREATE DATABASE advanced_schedule;
-- ๊ณ์ ์์ฑ (๊ธฐ์กด ๊ณ์ ์ด ์๋ค๋ฉด ์๋ต ๊ฐ๋ฅ)
CREATE USER '<your-username>'@'localhost' IDENTIFIED BY '<your-password>';
GRANT ALL PRIVILEGES ON advanced_schedule.* TO 'dev_user'@'localhost';
FLUSH PRIVILEGES;DB ์ฐ๊ฒฐ ์ ๋ณด๋ฅผ ๋ณธ์ธ์ ํ๊ฒฝ์ ๋ง๊ฒ ์์ ํฉ๋๋ค.
spring.application.name=advanced-schedule
spring.datasource.url=jdbc:mysql://localhost:3306/advanced_schedule?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
spring.datasource.username=<your-username>
spring.datasource.password=<your-password>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# Swagger/OpenAPI ์ค์
springdoc.swagger-ui.path=/swagger-ui.html
springdoc.api-docs.path=/v3/api-docs๐ก Tip:
ddl-auto๋ ์ต์ด ์คํ ํcreateโupdate๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.create๋ ๋งค๋ฒ ํ ์ด๋ธ์ ์ญ์ ํ๊ณ ์ฌ์์ฑํ๋ฏ๋ก ๋ฐ์ดํฐ๊ฐ ์์ค๋ฉ๋๋ค.
ํ๋ก์ ํธ ๋ฃจํธ ๊ฒฝ๋ก์์ ๋ค์ ๋ช ๋ น์ด๋ก ์คํํฉ๋๋ค.
# Mac/Linux
./gradlew bootRun
# Windows
gradlew.bootRun์์ธํ API ์์ฒญ ๋ฐ ์๋ต ๋ช ์ธ๋ ์๋ Postman Documentation์์ ํ์ธํ์ค ์ ์์ต๋๋ค.
- ํ์๊ฐ์
:
POST /api/users(์ด๋ฉ์ผ, ๋น๋ฐ๋ฒํธ ์ํธํ ์ ์ฅ) - ๋ก๊ทธ์ธ:
POST /api/users/login(์ธ์ ID ๋ฐ๊ธ) - ์ ์ฒด ์กฐํ:
GET /api/users - ๋จ์ผ ์กฐํ:
GET /api/users/{userId} - ์ ๋ณด ์์ :
PATCH /api/users/{userId}(๋ก๊ทธ์ธ ํ์, ๋ณธ์ธ๋ง ๊ฐ๋ฅ) - ํ์ ํํด:
DELETE /api/users/{userId}(๋ก๊ทธ์ธ ํ์, Soft Delete, ์ธ์ ๋ฌดํจํ)
- ์ผ์ ์์ฑ:
POST /api/schedules/{userId}(๋ก๊ทธ์ธ ํ์) - ์ ์ฒด ์กฐํ:
GET /api/schedules(ํ์ด์ง:?page=0&size=10, ์์ ์ผ ๊ธฐ์ค ๋ด๋ฆผ์ฐจ์) - ๋จ๊ฑด ์กฐํ:
GET /api/schedules/{scheduleId} - ์ผ์ ์์ :
PATCH /api/schedules/{scheduleId}(์์ฑ์ ๋ณธ์ธ๋ง ๊ฐ๋ฅ) - ์ผ์ ์ญ์ :
DELETE /api/schedules/{scheduleId}(์์ฑ์ ๋ณธ์ธ๋ง ๊ฐ๋ฅ, Soft Delete)
- ๋๊ธ ์์ฑ:
POST /api/schedules/{scheduleId}/comments/(๋ก๊ทธ์ธ ํ์) - ๋๊ธ ์กฐํ:
GET /api/schedules/{scheduleId}/comments/ - ๋จ์ผ ๋๊ธ ์กฐํ:
GET /api/schedules/{scheduleId}/comments/{commentId} - ๋๊ธ ์์ :
PATCH /api/schedules/{scheduleId}/comments/{commentId}(์์ฑ์ ๋ณธ์ธ๋ง ๊ฐ๋ฅ) - ๋๊ธ ์ญ์ :
DELETE /api/schedules/{scheduleId}/comments/{commentId}(์์ฑ์ ๋ณธ์ธ๋ง ๊ฐ๋ฅ, Soft Delete)
"๋จ์ผ ์๋ฒ ํ๊ฒฝ์์์ ํจ์จ์ฑ๊ณผ ๋ณด์"
ํ์ฌ ์งํ๋ ํ๋ก์ ํธ๋ ๋จ์ผ ์๋ฒ ๊ตฌ์กฐ์ ๋๋ค. JWT๋ ํ์ฅ์ฑ์ด ์ข์ง๋ง, ๊ตฌํ ๋ณต์ก๋๊ฐ ๋๊ณ ํ ํฐ ๊ด๋ฆฌ๊ฐ ๊น๋ค๋กญ์ต๋๋ค.
๋ฐ๋ฉด Session ๋ฐฉ์์ ์๋ฒ๊ฐ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ฏ๋ก ๋ณด์์ ์ผ๋ก ์์ ํ๊ณ (HttpOnly Cookie ์ฌ์ฉ), ๋ก๊ทธ์์ ์ ์ฆ๊ฐ์ ์ธ ์ ๊ทผ ์ฐจ๋จ(session.invalidate())์ด ๊ฐ๋ฅํ์ฌ ์ ํํ์ต๋๋ค.
"๋ฐ์ดํฐ์ ๊ฐ์น ๋ณด์กด๊ณผ ๋ณต๊ตฌ ๊ฐ๋ฅ์ฑ"
์ค๋ฌด์์๋ ์ค์๋ก ์ธํ ์ญ์ ๋ ๋ฐ์ดํฐ ๋ถ์์ ์ํด ๋ฐ์ดํฐ๋ฅผ ์ค์ ๋ก ์ง์ฐ์ง ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
@SoftDelete์ด๋ ธํ ์ด์ ์ ํ์ฉํ์ฌ ๋น์ฆ๋์ค ๋ก์ง ์์ ์์ดdeleted=trueํ๋๊ทธ๋ง ๋ณ๊ฒฝ๋๋๋ก ๊ตฌํํจ์ผ๋ก์จ, ๋ฐ์ดํฐ ์์ ์ฑ๊ณผ ๊ฐ๋ฐ ์์ฐ์ฑ์ ๋ชจ๋ ํ๋ณดํ์ต๋๋ค.
"๋ฐ๋ณต ์ฝ๋ ์ ๊ฑฐ์ ์ผ๊ด์ฑ ํ๋ณด"
@EnableJpaAuditing๊ณผBaseEntity๋ฅผ ํ์ฉํ์ฌ ๋ชจ๋ ์ํฐํฐ์ ์์ฑ์ผ(createdAt)๊ณผ ์์ ์ผ(modifiedAt)์ ์๋์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค.
์ด๋ฅผ ํตํด ๊ฐ๋ฐ์๊ฐ ์ง์ ์๊ฐ ์ ๋ณด๋ฅผ ์ค์ ํ๋ ๋ฐ๋ณต ์์ ์ ์ ๊ฑฐํ๊ณ , ๋ชจ๋ ํ ์ด๋ธ์์ ์ผ๊ด๋ ์๊ฐ ๊ด๋ฆฌ ์ ์ฑ ์ ์ ์งํ ์ ์์ต๋๋ค.
"๊ฒฝ๋ํ์ ํ์ํ ๊ธฐ๋ฅ๋ง ๋์ "
์ด ํ๋ก์ ํธ๋ ๋ณต์กํ ๊ถํ ๊ด๋ฆฌ๋ OAuth2 ๊ฐ์ ๊ณ ๊ธ ๋ณด์ ๊ธฐ๋ฅ์ด ํ์ํ์ง ์์ต๋๋ค.
Spring Security๋ ๊ฐ๋ ฅํ์ง๋ง ์ค์ ์ด ๋ณต์กํ๋ฏ๋ก, ๋น๋ฐ๋ฒํธ ์ํธํ ๊ธฐ๋ฅ๋ง ํ์ํ ๊ฒฝ์ฐ ๋ ๋ฆฝ์ ์ธ BCrypt ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ก์ ํธ ๋ณต์ก๋๋ฅผ ๋ฎ์ท์ต๋๋ค.

