[워밍업 BE 프로젝트 3기] 3주차 발자국

 

학습

 

3주차는 섹션 5에 대해 학습합니다.

 

공통 개발 - Exception/Advice

  • 컨트롤러에서 @ExceptionHandler로 선언된 메서드가 있으면 해당 컨트롤러에서 발생한 에러를 처리함

  • 하지만 @GetMapping 같은 실제 API랑 핸들러랑 같이 있으면 보기에도 좋지 않으며, 중복 코드 발생

  • 예외 핸들러를 @RestControllerAdvice가 달린 클래스에 모아두는 것

     

 

공통 개발 - DTO

  • Kotlin에는 static이 없어서 companion object를 이용해서 정적 메서드 구현

  • vararg 키워드: Python에서 나머지 파라미터 긁어오는 것과 비슷한 기능

     

 

조회 개발

  • Kotlin에서 삼항연산자는 없지만, 비슷한 문법으로 if (condition) A else B 로 사용 가능 -> Python이랑 비슷하다고 느낌

     

 

삽입, 수정, 삭제 개발

  • 1:N 관계에서 cascade 옵션 때문에 experience만 저장해도 detail까지 저장됨

  • Map 을 생성할 때 x to y 로 Pair를 표현할 수 있음

    • experience.details.map { it.id to it }.toMap()

  • JPA의 자동 변경분 적용 기능

    • NestJS+TypeORM을 사용할때면 무조건 코드 마지막줄에 save()를 작성해서 그런지 익숙하지 않았다.

    • Spring, JPA를 사용한 경험이 많지 않다 보니 자꾸 까먹는다. .

       

 

뷰 개발, 대시보드 개발

  • 템플릿이 유료로 전환되어 사용할 수 없었음

  • 더이상 무료 템플릿이 아닌데 강사님의 코드를 붙여넣기 하는 건 괜찮을까..? 라는 생각이 들어서 우선 실습을 건너뜀

 


 

추가 탐구

 

bindingResult.fieldErrors vs. bindingResult.fieldError

image

  • e.bindingResult. 까지 쳤을 때 자동완성으로 fieldErrors와 fieldError가 존재했다.

  • 두 개의 차이가 무엇일지 궁금해졌다.

     

image

  • getFieldErrors()는 전체 에러를 리스트로 반환하고, getFieldError()는 첫 번째 에러를 반환한다.

  • 실습 때 진행한 것처럼 [0] 으로 첫 번째에 접근하는 경우에는 리스트가 비어서 에러가 날 수도 있으니 안전하게 getFieldError()로 사용하면 되겠다고 생각한다.

 

equals() vs. ==

image

  • IDE에서 equals() 대신 == 로 바꿔 쓸 것을 추천했다.

  • 이유는 잘 모르겠습니다. .

 

Optional

experienceRepository.findById(id)
      .orElseThrow { throw AdminBadRequestException("ID ${id}에 해당하는 데이터를 찾을 수 없습니다.") }
      .details
  • Optional 덕분에 orElseThrow()에서 예외가 던져지지 않았다면 데이터가 존재한다는 걸 의미한다.

  • 그래서 details에 바로 접근할 수 있었다.

 

Validate 옵션 확인

image

  • 코드 내 import문에서 control+원하는 부분을 선택하면 사용할 수 있는 데코레이터, 클래스 등을 확인할 수 있었다.

     

  • 사실 조금만 IDE를 써봐도 당연했던 건데 코드에서만 따라가봤지 import문에서 선택한다는 생각을 못했어서 신기했다.

  • 그 전에는 어떤 옵션이 있을지 IDE 자동완성이나 코파일럿 추천을 기다리고 있었다. 🥲

 



미션

 

과제에 대한 마지막 미션으로 <삽입, 수정, 삭제 REST API 만들기>를 진행했습니다.

 

삽입, 수정 시에는 @Transactional을 꼭 빼먹지 않도록 유의했습니다.

 

그리고 추가로 필요한 조회 요청이 있어 구현했습니다.

GET 요청 할 때 쿼리 파라미터를 여러 개 전달하기를 원했는데 이를 컨트롤러에서 여러 개의 @RequestParam을 쓰기보다는 클래스로 지정해서 객체 형태로 받고 싶었습니다.

이렇게 하니 400 에러가 발생했고, @ModelAttribute로 해결하게 되었습니다.

디테일하게 해당 어노테이션을 탐구해본건 아니지만, 주로 폼데이터에서 여러 파라미터를 객체에 바인딩할 때 사용됩니다. 제약사항으로 Setter나 모든 필드를 받는 생성자가 존재해야 했는데, 후자로 해결하였습니다.

@GetMapping("/v1/transactions")
fun getTransactions(@ModelAttribute request: FindTransactionRecordsRequest): List<TransactionRecordDTO> {
    return presentationService.getTransactions(request)
}
data class FindTransactionRecordsRequest(
    @field:NotEmpty
    val userId: Long,

    val categoryId: Long?,

    @field:NotEmpty
    val year: Int,

    @field:NotEmpty
    val month: Int,
)

 

삭제 로직의 경우, Soft delete로 구현하면 좋을 것 같아서 해당 방법을 찾아보았습니다.

@SQLRestriction("deleted_date_time is null")
@SQLDelete(sql = "UPDATE transaction SET deleted_date_time = NOW() WHERE id = ?")

저는 엔티티마다 deleted_date_time을 두어서 삭제 날짜를 저장했습니다. 따라서 해당 값이 null이면 삭제되지 않은 데이터입니다.

그래서 @SQLDelete로 deleted_date_time=NOW() 를 명시하여 삭제 시 해당 쿼리가 나가도록 작성했습니다.

 

그리고 남은 기간 동안 개선해보면 좋을 점을 몇 개 작성해보았습니다.

  • 필드 유효성 검사 에러 핸들링 로직 - 현재 에러 메시지가 제대로 응답에 나가지 않음

  • 로그인 기반으로 헤더에서 정보 추출 - 로그인 기능이 없어 유저 아이디를 바디로 함께 보내고 있음

  • 컨트롤러 분리 - PresentationApiController에서 여러 엔티티에 대한 CRUD를 하고 있어 적절하게 분리 필요

 

바쁜 한 주여서 강의도 몰아듣느라 과제 제출에 필요한 최소한의 기능만 구현했습니다.

시작할 땐 욕심이 많았는데 점점 타협하게 되는것 같아서 아쉽지만, 마지막 주는 커리큘럼 상 강의가 이틀만 있어서 남은 기간은 디테일에 집중해보려고 합니다.

마지막까지 화이팅 !! 💪🏻

 

채널톡 아이콘