์ด์ง€๋ฏผ์˜ ๊ฐœ๋ฐœ ๋…ธํŠธ

๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์„ ๊ณต๋ถ€ํ•˜๋ฉฐ Java์™€ Spring์„ ์ค‘์‹ฌ์œผ๋กœ ํ•™์Šต ๊ธฐ๋ก์„ ์ •๋ฆฌํ•˜๋Š” ๊ณต๊ฐ„์ž…๋‹ˆ๋‹ค.

๐Ÿ”ฅ ๋‚ด์ผ๋ฐฐ์›€์บ ํ”„/๐Ÿ“Œ ๊ณผ์ œ

[๋‚ด์ผ๋ฐฐ์›€์บ ํ”„ Spring 3๊ธฐ] CH3 ์ˆ™๋ จ Spring ์ผ์ • ๊ด€๋ฆฌ ์•ฑ Develop ํ•„์ˆ˜ ๊ธฐ๋Šฅ

jiiimni 2026. 3. 10. 12:27

์ด๋ฒˆ ๊ณผ์ œ๋Š” Spring Boot + JPA + MySQL๋กœ ์ผ์ •(Schedule) CRUD๋ฅผ ๋งŒ๋“ค๊ณ ,
Lv2๋ถ€ํ„ฐ๋Š” User ์—ฐ๊ด€๊ด€๊ณ„, Lv4์—์„œ๋Š” Cookie/Session ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ ์ธ์ฆ๊นŒ์ง€ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์˜€๋‹ค.


๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • Java 17
  • Spring Boot
  • Spring Data JPA
  • MySQL
  • Validation
  • Lombok
  • Postman

ํ”„๋กœ์ ํŠธ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ

kr.spartaclub.develop_scheduleapp
 โ”ฃ config
 โ”ฃ controller
 โ”ฃ dto
 โ”ฃ entity
 โ”ฃ repository
 โ”— service

๊ณตํ†ต ์„ค์ •: JPA Auditing (createdAt / updatedAt ์ž๋™)

Lv1๋ถ€ํ„ฐ ์ผ์ • ์ƒ์„ฑ/์ˆ˜์ • ์‹œ๊ฐ„์„ ์‘๋‹ต์— ํฌํ•จํ•ด์•ผ ํ•ด์„œ Auditing์„ ๋จผ์ € ์žก์•˜๋‹ค.

1) JPA Auditing ํ™œ์„ฑํ™”

JpaAuditingConfig.java

  • @EnableJpaAuditing ๋ถ™์ด๋ฉด JPA๊ฐ€ ์—”ํ‹ฐํ‹ฐ ์ €์žฅ/์ˆ˜์ • ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•ด์„œ ์‹œ๊ฐ„์„ ์ž๋™์œผ๋กœ ์ฑ„์›Œ์ค€๋‹ค.
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
}

2) BaseTimeEntity

BaseTimeEntity.java

  • @MappedSuperclass : ์ƒ์†ํ•œ ์—”ํ‹ฐํ‹ฐ ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ์œผ๋กœ ํฌํ•จ๋จ
  • @EntityListeners(AuditingEntityListener.class) : auditing ์ด๋ฒคํŠธ ๊ฐ์ง€
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity {

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

Lv1. ์ผ์ • CRUD (username ๊ธฐ๋ฐ˜)

Lv1์€ ์œ ์ € ํ…Œ์ด๋ธ”์ด ์—†์–ด์„œ, ์ผ์ •์— username์„ ๋ฌธ์ž์—ด๋กœ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‹œ์ž‘ํ–ˆ๋‹ค.

Lv1 ์š”๊ตฌ์‚ฌํ•ญ(์š”์•ฝ)

  • ์ผ์ • CRUD ๊ตฌํ˜„
    • ์ƒ์„ฑ/์ „์ฒด์กฐํšŒ/๋‹จ๊ฑด์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ
  • createdAt / updatedAt ์‘๋‹ต์— ํฌํ•จ

Lv1 ์„ค๊ณ„ ํฌ์ธํŠธ

  • ์•„์ง User ํ…Œ์ด๋ธ”์ด ์—†์œผ๋‹ˆ๊นŒ ์ž‘์„ฑ์ž(username)๋Š” ๋ฌธ์ž์—ด๋กœ ๋ฐ›๋Š”๋‹ค
  • Controller -> Service -> Repository ํ๋ฆ„์œผ๋กœ ๋ถ„๋ฆฌ
  • ์—”ํ‹ฐํ‹ฐ๋Š” DB ๊ตฌ์กฐ, DTO๋Š” ์š”์ฒญ/์‘๋‹ต ์ „์šฉ์œผ๋กœ ๋ถ„๋ฆฌ

Lv1์—์„œ ํ•ต์‹ฌ์œผ๋กœ ๋งŒ๋“  ํŒŒ์ผ๋“ค

  • controller/ScheduleController
  • service/ScheduleService
  • repository/ScheduleRepository
  • entity/Schedule, entity/BaseTimeEntity
  • dto/ScheduleRequest, dto/ScheduleResponse, dto/ScheduleUpdateRequest
  • config/JpaAuditingConfig

์ฝ”๋“œ ํ๋ฆ„(์š”์•ฝ)

  1. Controller์—์„œ ์š”์ฒญ ๋ฐ›์Œ
  2. Service์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ(์ €์žฅ/์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ)
  3. Repository(JPA) ๋กœ DB ์ ‘๊ทผ
  4. ๊ฒฐ๊ณผ๋ฅผ Response DTO๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜

Lv1 DTO/Entity ์—ญํ•  ๋ถ„๋ฆฌ

  • ScheduleRequest: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ด๋Š” ๊ฐ’๋งŒ ๋ฐ›์Œ(username/title/content)
  • ScheduleUpdateRequest: ์ˆ˜์ •์— ํ•„์š”ํ•œ ๊ฐ’๋งŒ ๋ฐ›์Œ(title/content)
  • ScheduleResponse: ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋‚ด๋ ค์ค„ ๊ฐ’๋งŒ ๋‹ด์Œ(id/username/title/content/createdAt/updatedAt)

์ด ๋ถ„๋ฆฌ๋ฅผ ํ•ด๋‘๋ฉด, ๋‚˜์ค‘์— Lv2์—์„œ username์„ ์—†์• ๊ณ  userId๋กœ ๋ฐ”๊พธ๋Š” ์ž‘์—…์ด ํ›จ์”ฌ ํŽธํ•ด์ง(์‹ค์ œ๋กœ ๊ทธ๋žฌ์Œ).


API ๋ชฉ๋ก

  • POST /api/schedules : ์ผ์ • ์ƒ์„ฑ
  • GET /api/schedules : ์ „์ฒด ์กฐํšŒ
  • GET /api/schedules/{id} : ๋‹จ๊ฑด ์กฐํšŒ
  • PATCH /api/schedules/{id} : ์ˆ˜์ •
  • DELETE /api/schedules/{id} : ์‚ญ์ œ

Postman ๊ฒ€์ฆ ์ˆœ์„œ (Lv1)

1) ์ผ์ • ์ƒ์„ฑ

POST /api/schedules

{
  "username": "jimin",
  "title": "์ฒซ ์ผ์ •",
  "content": "CRUD ์‹œ์ž‘!"
}

2) ์ „์ฒด ์กฐํšŒ

GET /api/schedules

3) ๋‹จ๊ฑด ์กฐํšŒ

GET /api/schedules/{id}

4) ์ˆ˜์ •

PATCH /api/schedules/{id}

{
  "title": "์ˆ˜์ •๋œ ์ œ๋ชฉ",
  "content": "์ˆ˜์ •๋œ ๋‚ด์šฉ"
}

5) ์‚ญ์ œ

DELETE /api/schedules/{id}


Lv2. User CRUD + Schedule ์—ฐ๊ด€๊ด€๊ณ„ (userId ๊ธฐ๋ฐ˜)

Lv2๋ถ€ํ„ฐ๋Š” ์ผ์ •์ด username ๋ฌธ์ž์—ด์„ ์ €์žฅํ•˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ,
User๋ฅผ ์ฐธ์กฐ(FK)ํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

  • ๊ธฐ์กด: Schedule.username (String)
  • ๋ณ€๊ฒฝ: Schedule.user (ManyToOne)

ํ•ต์‹ฌ ๊ฐœ๋…: ์—ฐ๊ด€๊ด€๊ณ„(ManyToOne)

Schedule์€ ์ž‘์„ฑ์ž(User)๋ฅผ ๊ฐ€์ง„๋‹ค.
ํ•œ ๋ช…์˜ ์œ ์ €๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ผ์ •์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ ๊ด€๊ณ„๋Š” ์ด๋ ‡๊ฒŒ ๋œ๋‹ค.

  • User 1 : Schedule N
    -> Schedule ์ž…์žฅ์—์„œ๋Š” ManyToOne

Lv2 ์š”๊ตฌ์‚ฌํ•ญ

  • User CRUD ์ถ”๊ฐ€
  • ์ผ์ • ์ƒ์„ฑ ์‹œ ์œ ์ €์™€ ์—ฐ๊ด€๊ด€๊ณ„๋กœ ์ €์žฅ
  • ์ผ์ • ์‘๋‹ต์—์„œ username์€ User์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์ด์–ด์•ผ ํ•จ

Lv2์—์„œ ๋ฐ”๋€ ์ 

Schedule์ด ๋” ์ด์ƒ username(String)์„ ์ €์žฅํ•˜์ง€ ์•Š๊ณ , user(User)๋ฅผ ์ฐธ์กฐํ•˜๋„๋ก ๋ณ€๊ฒฝ


Lv2 ํ•ต์‹ฌ ๋ณ€๊ฒฝ 1) User ์—”ํ‹ฐํ‹ฐ ์ถ”๊ฐ€

  • entity/User ์ƒ์„ฑ
  • ์ด๋ฉ”์ผ์€ unique ์ฒ˜๋ฆฌ(์ค‘๋ณต ๊ฐ€์ž… ๋ฐฉ์ง€)
@Entity
@Table(name="users")
public class User extends BaseTimeEntity {
  @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(nullable=false, length=50)
  private String username;

  @Column(nullable=false, length=100, unique=true)
  private String email;
}

Lv2 ํ•ต์‹ฌ ๋ณ€๊ฒฝ 2) Schedule ์—”ํ‹ฐํ‹ฐ์— ์—ฐ๊ด€๊ด€๊ณ„ ์ถ”๊ฐ€

  • Schedule.user ํ•„๋“œ ์ถ”๊ฐ€
  • @ManyToOne + @JoinColumn(name="user_id") ๋กœ FK ์ƒ์„ฑ
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

์—ฌ๊ธฐ์„œ ์‹ค์ œ DB์— user_id ์ปฌ๋Ÿผ์ด ์ƒ๊ธฐ๊ณ , schedules ํ…Œ์ด๋ธ”์ด users๋ฅผ ์ฐธ์กฐํ•˜๊ฒŒ ๋จ.


Lv2 ํ•ต์‹ฌ ๋ณ€๊ฒฝ 3) ScheduleRequest๊ฐ€ userId๋ฅผ ๋ฐ›๋„๋ก ๋ณ€๊ฒฝ

๊ธฐ์กด Lv1:

  • username์„ ๋ฐ›์Œ

Lv2:

  • userId๋ฅผ ๋ฐ›์Œ
@NotNull(message="userId๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private Long userId;

Lv2 ํ•ต์‹ฌ ๋ณ€๊ฒฝ 4) Service์—์„œ userId๋กœ User๋ฅผ ์กฐํšŒํ•ด์„œ Schedule ์ €์žฅ

ScheduleService.create()์—์„œ:

  1. userRepository.findById(userId) ๋กœ ์œ ์ € ์กด์žฌ ํ™•์ธ
  2. ๊ทธ User๋กœ Schedule ์ƒ์„ฑ ํ›„ save
User user = userRepository.findById(request.getUserId())
    .orElseThrow(() -> new IllegalArgumentException("ํ•ด๋‹น ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. id=" + request.getUserId()));

Schedule saved = scheduleRepository.save(new Schedule(user, request.getTitle(), request.getContent()));
return new ScheduleResponse(saved);

User ์ƒ์„ฑ API (Lv2)

POST /api/users

{
  "username": "jimin",
  "email": "jimin@test.com"
}


Postman ๊ฒ€์ฆ ์ˆœ์„œ (Lv2)

1) ์œ ์ € ์ƒ์„ฑ ํ›„ id ํ™•์ธ

  • ์‘๋‹ต์—์„œ id=1 ๊ฐ™์€ ๊ฐ’ ํ™•์ธ

2) ์ผ์ • ์ƒ์„ฑ (userId๋กœ)

POST /api/schedules

{
  "userId": 1,
  "title": "Lv2 ์ผ์ •",
  "content": "์œ ์ € ์—ฐ๊ด€๊ด€๊ณ„๋กœ ์ƒ์„ฑ"
}

3) ์ผ์ • ์ „์ฒด ์กฐํšŒ

GET /api/schedules

4) ์œ ์ €๋ช… ์ˆ˜์ • ํ›„ ์ผ์ • ๋‹ค์‹œ ์กฐํšŒ

PATCH /api/users/1

{
  "username": "jimin2",
  "email": "jimin2@test.com"
}

๊ทธ ๋‹ค์Œ GET /api/schedules ๋‹ค์‹œ ์‹คํ–‰

  • ์ผ์ • ์‘๋‹ต์—์„œ username์ด jimin2๋กœ ๋ฐ”๋€Œ์–ด ๋ณด์ด๋ฉด Schedule์ด username์„ ์ €์žฅํ•œ๊ฒŒ ์•„๋‹ˆ๋ผ User๋ฅผ ์ฐธ์กฐํ•œ๋‹ค๋Š” ์ฆ๊ฑฐ


Lv3. ์œ ์ €์— password ํฌํ•จ / ํšŒ์›๊ฐ€์ž… ํ˜•ํƒœ ๊ฐ•ํ™”

Lv3์—์„œ ๋‚˜๋Š” ํšŒ์›๊ฐ€์ž… ์ž…๋ ฅ์— password๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.
(์ดํ›„ Lv4 ๋กœ๊ทธ์ธ ๊ตฌํ˜„์„ ์œ„ํ•ด ํ•„์š”)

  • User์— password ์ปฌ๋Ÿผ ์ถ”๊ฐ€
  • UserRequest DTO์— password ์ถ”๊ฐ€

Lv3 ์š”๊ตฌ์‚ฌํ•ญ

  • ํšŒ์›๊ฐ€์ž… ํ˜•ํƒœ ๊ฐ•ํ™”(๋กœ๊ทธ์ธ ์ค€๋น„ ๋‹จ๊ณ„)

Lv3์—์„œ ์ถ”๊ฐ€๋œ ๊ฒƒ

  • User์— password ์ปฌ๋Ÿผ ์ถ”๊ฐ€
  • DTO(UserRequest ๋“ฑ)์— password ํ•„๋“œ ์ถ”๊ฐ€ + Validation
@Column(nullable=false)
private String password;

์ •์ƒ ํšŒ์›๊ฐ€์ž… ํ…Œ์ŠคํŠธ

๋น„๋ฐ€๋ฒˆํ˜ธ 4์ž๋ฆฌ๋งŒ ์ž…๋ ฅํ–ˆ์„ ๊ฒฝ์šฐ: ์˜ค๋ฅ˜

-> ์˜ค๋ฅ˜๋ฉ”์‹œ์ง€๋Š” ๋„์ „ ๊ธฐ๋Šฅ์—์„œ ๊ตฌํ˜„ ์˜ˆ์ •

๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ: ์˜ค๋ฅ˜

-> ์˜ค๋ฅ˜๋ฉ”์‹œ์ง€๋Š” ๋„์ „ ๊ธฐ๋Šฅ์—์„œ ๊ตฌํ˜„ ์˜ˆ์ •

์ด ๋‹จ๊ณ„์—์„œ 400์ด ๋œฐ ์ˆ˜๋„ ์žˆ๋Š”๋ฐ,
์˜ˆ์™ธ์ฒ˜๋ฆฌ๋Š” Lv5์—์„œ ํ•˜๋ฏ€๋กœ “์ง€๊ธˆ์€ 400๋งŒ ํ™•์ธ๋˜๋ฉด OK”๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.


Lv4. ๋กœ๊ทธ์ธ(์ธ์ฆ) - Cookie/Session ๊ธฐ๋ฐ˜

Lv4 ํ•ต์‹ฌ์€ ์„ธ์…˜(HttpSession)์„ ํ™œ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ.

๊ฐœ๋… ์ •๋ฆฌ (Session / Cookie)

  • ๋กœ๊ทธ์ธ ์„ฑ๊ณตํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ์„ธ์…˜์„ ๋งŒ๋“ค๊ณ  ํด๋ผ์ด์–ธํŠธ(Postman)์—๊ฒŒ JSESSIONID ์ฟ ํ‚ค๋ฅผ ๋‚ด๋ ค์ค€๋‹ค
  • ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฟ ํ‚ค๋ฅผ ๊ฐ™์ด ๋ณด๋‚ด๋ฉด ์„œ๋ฒ„๋Š” ์•„, ์ด ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ ๋˜์–ด ์žˆ๊ตฌ๋‚˜”๋ฅผ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค

Lv4 ์š”๊ตฌ์‚ฌํ•ญ

  • ์ด๋ฉ”์ผ + ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ
  • Cookie/Session์œผ๋กœ ๋กœ๊ทธ์ธ ์ƒํƒœ ์œ ์ง€
  • ํ•„์š”ํ•œ API์—์„œ ์„ธ์…˜์„ ํ™œ์šฉํ•ด์„œ ์ธ์ฆ ์ฒดํฌ

Lv4์—์„œ ์ถ”๊ฐ€๋œ ํŒŒ์ผ๋“ค

  • controller/AuthController
  • dto/LoginRequest
  • service/AuthService ๋˜๋Š” UserService์— ๋กœ๊ทธ์ธ ๋กœ์ง ์ถ”๊ฐ€

Lv4 ํ•ต์‹ฌ ๊ตฌํ˜„ 1) ๋กœ๊ทธ์ธ API

  • POST /api/auth/login
  • email/password ๊ฒ€์ฆ ์„ฑ๊ณต ์‹œ
    • HttpSession์— ๋กœ๊ทธ์ธ ์œ ์ € id ์ €์žฅ

์˜ˆ์‹œ ํ๋ฆ„:

session.setAttribute(LOGIN_USER_ID, user.getId());

Lv4 ํ•ต์‹ฌ ๊ตฌํ˜„ 2) ๋กœ๊ทธ์•„์›ƒ API

  • POST /api/auth/logout
  • ์„ธ์…˜ ๋ฌดํšจํ™”
session.invalidate();

Lv4 ํ•ต์‹ฌ ๊ตฌํ˜„ 3) ์ผ์ • ์ƒ์„ฑ์— ์„ธ์…˜ ์ธ์ฆ ์ ์šฉ

Lv2๊นŒ์ง€ ์ผ์ • ์ƒ์„ฑ์€ userId๋ฅผ body๋กœ ๋ฐ›์•˜๋Š”๋ฐ
Lv4๋ถ€ํ„ฐ๋Š” ๋กœ๊ทธ์ธ ์„ธ์…˜์—์„œ userId๋ฅผ ๊บผ๋‚ด์„œ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝ.

  • ํด๋ผ์ด์–ธํŠธ๋Š” userId๋ฅผ ๋ณด๋‚ด์ง€ ์•Š๋Š”๋‹ค
  • ์„œ๋ฒ„๊ฐ€ ์„ธ์…˜์—์„œ ๊บผ๋‚ธ id๋กœ๋งŒ ์ƒ์„ฑํ•œ๋‹ค
@PostMapping
public ResponseEntity<ScheduleResponse> create(@RequestBody ScheduleRequest request, HttpSession session) {

    Long loginUserId = (Long) session.getAttribute(AuthController.LOGIN_USER_ID);
    if (loginUserId == null) {
        throw new IllegalArgumentException("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
    }

    return ResponseEntity.ok(scheduleService.create(loginUserId, request));
}

Postman ๊ฒ€์ฆ ์ˆœ์„œ (Lv4)

1) ํšŒ์›๊ฐ€์ž… (password ํฌํ•จ)

POST /api/users

{
  "username": "jimin3",
  "email": "jimin3@test.com",
  "password": "11223344"
}

2) ๋กœ๊ทธ์ธ (์„ธ์…˜ ์ฟ ํ‚ค ๋ฐœ๊ธ‰)

POST /api/auth/login

{
  "email": "jimin3@test.com",
  "password": "11223344"
}

์‘๋‹ต ํ—ค๋”์—์„œ Set-Cookie: JSESSIONID=... ํ™•์ธ

3) ์ผ์ • ์ƒ์„ฑ (์ด์ œ userId ๋ณด๋‚ด์ง€ ์•Š์Œ)

POST /api/schedules

{
  "title": "์„ธ์…˜ ์ผ์ •",
  "content": "๋กœ๊ทธ์ธ ์ƒํƒœ๋กœ ์ƒ์„ฑ"
}

4) ๋กœ๊ทธ์•„์›ƒ ํ›„ ์ผ์ • ์ƒ์„ฑ ์žฌ์‹œ๋„

POST /api/auth/logout

๊ทธ ๋‹ค์Œ ๋‹ค์‹œ POST /api/schedules ์š”์ฒญ

-> ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ


ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ์ •๋ฆฌ

1) Auditing์ธ๋ฐ updatedAt์ด ์•ˆ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ž„

ํ˜„์ƒ

PATCH ํ›„ ์‘๋‹ต์„ ๋ดค๋Š”๋ฐ updatedAt์ด createdAt์ด๋ž‘ ๋˜‘๊ฐ™์ด ๋ณด์˜€๋‹ค.

์›์ธ(๊ฐ€๋Šฅ์„ฑ)

  • ์š”์ฒญ์„ ๋„ˆ๋ฌด ๋นจ๋ฆฌ ์—ฐ์†์œผ๋กœ ๋ณด๋‚ด์„œ ์‹œ๊ฐ„์ด ๊ฐ™์€ ์ดˆ๋กœ ์ฐํž˜
  • ํ˜น์€ update๊ฐ€ ์‹ค์ œ๋กœ flush/dirty checking ๋˜๊ธฐ ์ „์— ์‘๋‹ต์„ ๋งŒ๋“  ๊ฒฝ์šฐ

ํ•ด๊ฒฐ/ํ™•์ธ ๋ฐฉ๋ฒ•

  • Postman์—์„œ ์ˆ˜์ • ์š”์ฒญ์„ ๋ช‡ ์ดˆ ํ…€์„ ๋‘๊ณ  ๋‹ค์‹œ ์‹คํ–‰
  • DB์—์„œ ์ง์ ‘ ํ™•์ธ (select) ํ˜น์€ GET ์กฐํšŒ๋กœ updatedAt ๋ณ€ํ™” ํ™•์ธ

2) @NotBlank๋ฅผ Long(userId)์— ๊ฑธ์–ด์„œ ํ„ฐ์ง (HV000030)

ํ˜„์ƒ

No validator could be found for constraint 'NotBlank' validating type 'Long'

์›์ธ

  • @NotBlank๋Š” ๋ฌธ์ž์—ด(String) ์ „์šฉ์ด๋‹ค.
  • ์ˆซ์ž(Long)์— ์“ฐ๋ฉด ๊ฒ€์ฆ๊ธฐ๊ฐ€ ์—†์–ด์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ.

ํ•ด๊ฒฐ

  • Long์—๋Š” @NotNull์„ ์จ์•ผ ํ•œ๋‹ค.
@NotNull(message="userId๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
private Long userId;

3) 500 Internal Server Error๋กœ ์ผ์ • ์ƒ์„ฑ์ด ์‹คํŒจ

ํ˜„์ƒ

Postman์—์„œ ์ผ์ • ์ƒ์„ฑ ์‹œ 500์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

์ฒดํฌํ–ˆ๋˜ ๊ฒƒ

  • userId๊ฐ€ ์‹ค์ œ ์กด์žฌํ•˜๋Š”์ง€ (GET /api/users)
  • DB ํ…Œ์ด๋ธ” ๊ตฌ์กฐ ๋ณ€๊ฒฝ์ด ๋ฐ˜์˜๋˜์—ˆ๋Š”์ง€ (user_id ์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€)
  • Schedule ์ƒ์„ฑ์ž๊ฐ€ (User, title, content)๋กœ ๋งž๊ฒŒ ๋˜์–ด ์žˆ๋Š”์ง€

๊ฒฐ๋ก 

๋Œ€๋ถ€๋ถ„ ์ด๋Ÿฐ 500์€

  • ์—”ํ‹ฐํ‹ฐ/DTO ํ•„๋“œ ํƒ€์ž… ๋ถˆ์ผ์น˜
  • FK ์ปฌ๋Ÿผ ๋ฏธ์ƒ์„ฑ/์Šคํ‚ค๋งˆ ๋ฏธ๋ฐ˜์˜
  • ์กด์žฌํ•˜์ง€ ์•Š๋Š” userId๋กœ ์ƒ์„ฑ ์‹œ๋„

์ด ์…‹ ์ค‘ ํ•˜๋‚˜์˜€๋‹ค.


4) ํšŒ์›๊ฐ€์ž… ์‹œ Duplicate entry (email unique)

ํ˜„์ƒ

Duplicate entry 'jimin2@test.com' for key 'users...'

์›์ธ

  • User.email์— unique=true๊ฐ€ ๊ฑธ๋ ค์žˆ์–ด์„œ
  • ๊ฐ™์€ ์ด๋ฉ”์ผ๋กœ ๋‹ค์‹œ ํšŒ์›๊ฐ€์ž… ์‹œ DB์—์„œ ๋ง‰ํž˜

ํ•ด๊ฒฐ

  • ์ด๋ฉ”์ผ์„ ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ํ…Œ์ŠคํŠธ
  • ํ˜น์€ ๊ธฐ์กด ์œ ์ € ๋ ˆ์ฝ”๋“œ ์‚ญ์ œ ํ›„ ์žฌ์‹œ๋„

ํšŒ๊ณ 

์‚ฌ์‹ค ์ง€๋‚œ ๊ณผ์ œ ๋•Œ๋Š” ๋…๊ฐ ๋•Œ๋ฌธ์— ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•ด์„œ ์•„์‰ฌ์›€์ด ๋งŽ์ด ๋‚จ์•˜์—ˆ๋‹ค.
๊ตฌ์กฐ๋ฅผ ๊นŠ๊ฒŒ ๊ณ ๋ฏผํ•˜๊ธฐ๋ณด๋‹ค๋Š” ์ผ๋‹จ ๋Œ์•„๊ฐ€๊ฒŒ ๋งŒ๋“œ๋Š” ๊ฒƒ์— ์ง‘์ค‘ํ•  ์ˆ˜๋ฐ–์— ์—†์—ˆ๊ณ , ์™„์„ฑ๋„์— ๋Œ€ํ•œ ์•„์‰ฌ์›€์ด ์ปธ๋‹ค.

์ด๋ฒˆ ๊ณผ์ œ๋„ ์‹œ๊ฐ„์ด ๋„‰๋„‰ํ•˜์ง„ ์•Š์•˜์ง€๋งŒ,
๊ทธ๋ž˜๋„ ์ €๋ฒˆ๋ณด๋‹ค๋Š” ํ›จ์”ฌ ์‹ ๊ฒฝ ์จ์„œ ๊ตฌ์กฐ๋ฅผ ์žก๊ณ , ๋ ˆ๋ฒจ๋ณ„ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ , ๋‹จ๊ณ„์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์—ˆ๋˜ ์ ์ด ์Šค์Šค๋กœ ๋งŒ์กฑ์Šค๋Ÿฝ๋‹ค.

ํŠนํžˆ ์ด๋ฒˆ ๊ณผ์ œ๋ฅผ ํ†ตํ•ด ๊นจ๋‹ฌ์€ ์ ์€

  • ์ฒ˜์Œ ์„ค๊ณ„๋ฅผ ์–ด๋–ป๊ฒŒ ํ•˜๋А๋ƒ์— ๋”ฐ๋ผ ์ดํ›„ ์ˆ˜์ • ๋‚œ์ด๋„๊ฐ€ ์™„์ „ํžˆ ๋‹ฌ๋ผ์ง„๋‹ค
  • DTO์™€ Entity๋ฅผ ๋ถ„๋ฆฌํ•ด๋‘๋Š” ๊ฒƒ์ด ์–ผ๋งˆ๋‚˜ ์ค‘์š”ํ•œ์ง€ ์ฒด๊ฐํ–ˆ๋‹ค
  • ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์ ์šฉํ•˜๋ฉด ๋‹จ์ˆœ ๋ฌธ์ž์—ด ์ €์žฅ๊ณผ๋Š” ์ฐจ์›์ด ๋‹ค๋ฅด๋‹ค
  • ์„ธ์…˜ ์ธ์ฆ์€ ๊ฐœ๋…์œผ๋กœ ์•Œ๋˜ ๊ฒƒ๊ณผ ์‹ค์ œ ๊ตฌํ˜„์€ ์™„์ „ํžˆ ๋‹ค๋ฅด๋‹ค
  • ์—๋Ÿฌ๋ฅผ ๋งˆ์ฃผ์ณค์„ ๋•Œ ๋‹นํ™ฉํ•˜์ง€ ์•Š๊ณ  ํ•˜๋‚˜์”ฉ ์›์ธ์„ ์ขํ˜€๊ฐ€๋Š” ๊ณผ์ •์ด ์ค‘์š”ํ•˜๋‹ค

์ด์ œ ๋„์ „ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ์™€ Validation๊นŒ์ง€ ์ •๋ฆฌํ•˜๋ฉด์„œ
ํ”„๋กœ์ ํŠธ๋ฅผ ๋” ๋‹ค๋“ฌ์–ด๋ณด๊ณ  ์‹ถ๋‹ค.