[TIL] πŸ› οΈμΌμ •κ΄€λ¦¬ ν”„λ‘œμ νŠΈ(Develop) νŠΈλŸ¬λΈ”μŠˆνŒ…

YJinΒ·2025λ…„ 4μ›” 3일

[λ‚΄λ°°μΊ  Spring 6κΈ°_TIL]

λͺ©λ‘ 보기
19/56

πŸ› οΈ νŠΈλŸ¬λΈ” μŠˆνŒ…

πŸ“Œ JPA Auditing λ™μž‘ 였λ₯˜

β›” 문제

  • νƒ€μž„μŠ€νƒ¬ν”„λ₯Ό 좜λ ₯ν•˜κΈ° μœ„ν•΄, @Builderλ₯Ό μ μš©ν•œ μ—”ν‹°ν‹°μ—μ„œ @EntityListeners(AuditingEntityListener.class) 적용 ν›„ @CreatedDate둜 생성일을 μžλ™ μž…λ ₯ν•˜λ € ν–ˆμœΌλ‚˜ 값이 μ •μƒμ μœΌλ‘œ μ €μž₯λ˜μ§€ μ•ŠμŒ.
@Getter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@EntityListeners(AuditingEntityListener.class)
public class ErrorResponse {
  @CreatedDate
  private final LocalDateTime timestamp;
  private final int status;
  ...


  public static ErrorResponse of(ErrorCode errorCode) {
    return ErrorResponse.builder()
        .status(errorCode.getStatus())
        ...
        .build();
  }
  
  ...
  
}

❓ 원인

  • JPA Auditing은 JPA 라이프사이클 이벀트λ₯Ό 기반으둜 함.
  • persist, merge, remove λ“±μ˜ μž‘μ—…μ— μ˜ν•΄ 이벀트 νŠΈλ¦¬κ±°κ°€ 됨.
  • μœ„μ˜ μ˜ˆμ‹œ μ½”λ“œμ˜ ErrorResponseλŠ” μ—”ν‹°ν‹°κ°€ μ•„λ‹ˆλ―€λ‘œ(@Entity μ–΄λ…Έν…Œμ΄μ…˜βŒ) JPA 라이프라이클에 λ”°λ₯Έ 이벀트 νŠΈλ¦¬κ±°κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€.
  • λ”°λΌμ„œ AuditingEntityListener.class 이 λ™μž‘ν•˜μ§€ μ•Šμ•˜μŒ.

❗ ν•΄κ²°

  • JPA μ—”ν‹°ν‹°κ°€ μ•„λ‹ˆλ―€λ‘œ μΈμŠ€ν„΄μŠ€μ— λ””ν΄νŠΈ 값을 μ„€μ •ν•˜λŠ” κ²ƒμœΌλ‘œ ν•΄κ²°.

  • μ˜ˆμ‹œ μ½”λ“œμ—μ„œλŠ” @Builer νŒ¨ν„΄μ„ μ‚¬μš©ν•˜κ³  μžˆμœΌλ―€λ‘œ, @Builder.Defaultλ₯Ό μ‚¬μš©ν•˜μ—¬ @Builer 둜 생성 μ‹œμ— λ””ν΄νŠΈ κ°’μœΌλ‘œ ν˜„μž¬ μ‹œκ°„μ„ μ €μž₯ν•˜λ„λ‘ λ³€κ²½ν•˜μ˜€λ‹€.

@Builder.Default
 private final LocalDateTime timestamp = LocalDateTime.now();

κ·Έ κ²°κ³Ό νƒ€μž„ μŠ€νƒ¬ν”„κ°€ μ •μƒμ μœΌλ‘œ κΈ°λ‘λ˜λŠ” 것을 확인할 수 μžˆλ‹€.

{
    "timestamp": "2025-04-04T02:35:55.5728417",
    "status": 400,
    "error": "Bad Request",
    "code": "U003",
    "message": "Email is Duplicated"
}



πŸ“Œ @Valid 였λ₯˜ λ©”μ„Έμ§€ 좜λ ₯

β›” 문제

  • Validationμ—μ„œ message νŒŒλΌλ―Έν„°λ‘œ μ„€μ •ν•œ 였λ₯˜ λ©”μ„Έμ§€κ°€ μ œλŒ€λ‘œ 좜λ ₯λ˜μ§€ μ•ŠμŒ
ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getMessage());
{
    "status": 400,
    "error": "Bad Request",
    "code": "C001",
    "message": "Validation failed for argument [0] in public java.lang.String org.example.scheduleprojectv2.controller.UserController.login(org.example.scheduleprojectv2.dto.LoginRequestDTO,jakarta.servlet.http.HttpServletRequest): [Field error in object 'loginRequestDTO' on field 'email': rejected value [yejin1234]; codes [Email.loginRequestDTO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [loginRequestDTO.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@3da63570,.*]; default message [μ˜¬λ°”λ₯Έ ν˜•μ‹μ˜ 이메일 μ£Όμ†Œμ—¬μ•Ό ν•©λ‹ˆλ‹€]] "
}

❓ 원인

  • μ—λŸ¬ λ©”μ„Έμ§€ νŒŒλΌλ―Έν„° 값을 μ œλŒ€λ‘œ κΊΌλ‚΄μ˜€μ§€ μ•ŠμŒ.

❗ ν•΄κ²°

  • ν•„λ“œ μ—λŸ¬λ₯Ό μ •ν™•νžˆ μ§€μ •ν•˜μ—¬ defaultMessageλ₯Ό κ°€μ§€κ³ μ˜€κ²Œ 함.
ErrorResponse errorResponse = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, bindingResult.getFieldErrors().get(0).getDefaultMessage());
{
    "status": 400,
    "error": "Bad Request",
    "code": "C001",
    "message": "μ˜¬λ°”λ₯Έ ν˜•μ‹μ˜ 이메일 μ£Όμ†Œμ—¬μ•Ό ν•©λ‹ˆλ‹€"
}



πŸ•΅οΈβ€β™€οΈ κ³ λ―Όν•œ λΆ€λΆ„/μ–΄λ €μ› λ˜ λΆ€λΆ„

DTO ↔️ Entity λ³€ν™˜

방법1️⃣ 엔티티에 λ©”μ†Œλ“œ κ΅¬ν˜„

  • DTO μ‚­μ œλ‚˜ λ³€κ²½ μ‹œ μ—”ν‹°ν‹°μ—μ„œ 직접 μ‚­μ œ ν•„μš”

방법2️⃣ μ„œλΉ„μŠ€ λ ˆμ΄μ–΄μ— κ΅¬ν˜„

  • ν•΄λ‹Ή μ—”ν‹°ν‹°μ˜ μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ μ™Έμ—μ„œλ„ λ³€ν™˜ λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•  수 있음
  • μ„œλΉ„μŠ€ λ ˆμ΄μ–΄μ΄μ˜ μ±…μž„μ„ 벗어남

방법3️⃣ DTO에 λ©”μ†Œλ“œ κ΅¬ν˜„

  • 데이터 전솑 μ΄μƒμ˜ μ±…μž„μ„ κ°€μ§€κ²Œ λ˜λŠ”κ±ΈκΉŒ?
  • ν˜Ήμ€ 쀑볡 μ½”λ“œ 문제
  • 이 λ°©μ‹λŒ€λ‘œ ν•˜λ €κ³  ν–ˆμœΌλ‚˜ DTO에 κ°’ λ³€κ²½μœΌλ‘œ μΈν•œ λ³΄μ•ˆ 문제둜 @Setterλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šμ•„ μ›ν•˜λŠ” λŒ€λ‘œ 생성이 μ•ˆλ¨.
    ➑️ 1번 λ°©μ‹μœΌλ‘œ μ‚¬μš©


μ„Έμ…˜ μ €μž₯ 정보

  • 둜그인 μ„Έμ…˜ μ €μž₯ μ‹œ, μ–΄λ–€ 값듀을 μ €μž₯ν•˜λ©΄ 쒋을지?
    • ν˜„μž¬λŠ” 이메일, 이름 μ •λ„λ§Œ μ €μž₯ν•˜λ„λ‘ ν•˜μ˜€μŒ.
  • 맀번 μš”μ²­ λ•Œλ§ˆλ‹€ DB에 μ ‘κ·Όν•˜μ—¬ 이름 같은 정보λ₯Ό λΆˆλŸ¬μ˜€λŠ” 것이 λΆˆνŽΈν•œλ°, μ„Έμ…˜ 정보에 μ €μž₯된 값을 μ»¨νŠΈλ‘€λŸ¬μ—μ„œ μ•Œμ•„μ„œ λΆˆλŸ¬μ˜€λ„λ‘ ν•  μˆ˜λŠ” μ—†μ„κΉŒ?
    • μΆ”κ°€ 쑰사 ν•„μš”


Postman ν…ŒμŠ€νŒ… μžλ™ν™”

  • λŒ“κΈ€ CRUD ν…ŒμŠ€νŠΈ μ‹œ, νšŒμ›κ°€μž…-둜그인-일정 μƒμ„±μ˜ 과정을 맀번 λ°˜λ³΅ν•΄μ•Ό ν–ˆμŒ.
  • μ΄λŸ¬ν•œ 반볡적인 ν•„μˆ˜ 과정을 ν•˜λ‚˜λ‘œ λ¬Άμ–΄ μžλ™ μ‹€ν–‰ν•  수 μžˆλŠ” 방법이 μžˆμ„κΉŒ?
    • Postman κΈ°λŠ₯ μΆ”κ°€ 쑰사 ν•„μš”


μ˜μ†μ„± 전이(Cascade)

  • 일정-μ‚¬μš©μž N:1 μ—°κ΄€ κ΄€κ³„μ—μ„œ, μ‚¬μš©μžκ°€ μ‚­μ œλ˜λ©΄(νšŒμ› νƒˆν‡΄) ν•΄λ‹Ή μ‚¬μš©μžμ™€ μ—°κ΄€λœ 일정도 ν•¨κ»˜ μ‚­μ œλ  ν•„μš”κ°€ 있음.

  • JPA CASCADEλ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„  @OneToMany둜 μ–‘λ°©ν–₯ 섀정을 ν•΄μ£Όμ–΄μ•Ό 함.

    • μ–‘λ°©ν–₯ μ„€μ • μ‹œ N+1 문제, μˆœν™˜ μ°Έμ‘°, NullPointer μ°Έμ‘° λ“±κ³Ό 같은 λ¬Έμ œκ°€ λ°œμƒν•  κ°€λŠ₯성이 있음.
    • λ”°λΌμ„œ μ–‘λ°©ν–₯ 섀정은 λ˜λ„λ‘ ν”Όν•˜κ³  μ‹ΆμŒ.
  • μ‘°μ‚¬ν•΄λ³΄λ‹ˆ @OnDelete(action= OnDeleteAction.CASCADE)λ₯Ό μ‚¬μš©ν•˜μ—¬ μ‚¬μš©μžκ°€ μ‚­μ œλ  λ•Œ κ΄€λ ¨λœ 일정도 같이 μ‚­μ œλ˜λ„λ‘ μ„€μ •ν•  수 있음.

  • 이 λ™μž‘μ€ JPAκ°€ μ•„λ‹Œ DB μΈ‘μ—μ„œ μ΄λ£¨μ–΄μ§€λŠ” μž‘μ—…μ΄λ―€λ‘œ ν•˜λ‚˜μ˜ 쿼리둜 μ²˜λ¦¬κ°€ κ°€λŠ₯함.

  • ν˜„μž¬ ν”„λ‘œμ νŠΈμ—μ„œλŠ” @OnDelete μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ²˜λ¦¬ν•˜μ˜€μœΌλ‚˜, λ‘˜ 쀑 μ–΄λŠ 방식이 더 μ μ ˆν• μ§€λŠ” μΆ”κ°€ 쑰사 ν•„μš”.

μ°Έκ³ 

profile
λ°±μ—”λ“œ κ°œλ°œλ„ 락이닀

0개의 λŒ“κΈ€