기억력이 나쁜 미래의 나를 위해, 백엔드 프로젝트를 진행하는 과정에서 마주친 문제들과 그 해결책을 찾는 과정을 남겨두고자 한다
아래의 코드는 HTTP Request를 받아서 @RequestBody를 통해 Dto에 자동 매핑하여 등록하는 기존의 Controller이다.
@PostMapping("/api/v1/show")
public ResponseEntity<String> save(@RequestBody ShowInfoSaveRequestDto requestDto) {
return ResponseEntity.status(HttpStatus.CREATED).body(showAndSeatService.saveShowAndSeat(requestDto));
}
클라이언트에서 보내는 요청 본문에는 ShowInfoSaveRequestDto
에 대한 정보만 들어있기 때문에 @RequestBody 어노테이션으로 요청 본문을 받아와서 Dto에 자동으로 매핑시키면 문제없이 잘 작동한다.
하지만 공연정보를 등록할 때 포스터 이미지도 같이 등록할 필요가 있었고, 이를 위해 클라이언트의 요청에 기존의 JSON 형식의 요청과 함께 MultipartFile 형식의 포스터 이미지도 같이 보내도록 구현해야했다.
인터넷을 찾아보니 클라이언트 쪽에서는 FormData를 사용하여 데이터를 보내고 Springboot에서는 @ModelAttribute나 @RequestPart, @RequestParam을 사용하는 모양이다.
// 공연 정보를 등록
// 공연 포스터와 공연 정보를 동시에 받아오면 ShowInfoSaveRequestDto에 @RequestBody로 자동 매핑이 불가능해짐
// @RequestPart로 MultipartFile과 json string을 받도록 변경
@PostMapping("/api/v1/show")
public ResponseEntity<String> save(@RequestPart("files") List<MultipartFile> files, @RequestParam("requestDto") String request) throws Exception {
// ObjectMapper로 json string을 deserialize
ObjectMapper mapper = new ObjectMapper();
// JSR310 모듈이 deprecated라서 교체 필요
mapper.registerModule(new JSR310Module());
ShowInfoSaveRequestDto requestDto = mapper.readValue(request, new TypeReference<ShowInfoSaveRequestDto>() {
});
requestDto.setPosterURI(uploadFileHandler(files));
return ResponseEntity.status(HttpStatus.CREATED).body(showAndSeatService.saveShowAndSeat(requestDto));
}
@RequestPart를 사용하여 FormData 형식으로 보내진 요청 중 MultipartFile을 찾아내고 @RequestParam으로 Stringify된 JSON을 받고 ObjectMapper를 사용해 다시 Dto로 매핑하여 Deserialize 해주었다.
프로젝트 중에는 이렇게 구현하여 사용하였지만
프로젝트 후기를 정리하며 다시 찾아보니 @RequestParam 대신 @RequestPart를 사용해도 되는 모양이다.
RequestParam is likely to be used with name-value form fields while RequestPart is likely to be used with parts containing more complex content e.g. JSON, XML).
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestPart.html
삭제될 엔티티(여기에 cascade옵션 달아줌) -> cascade하게 삭제될 엔티티
때문에 컨트롤러에서 스케쥴링하고 서비스에서만 트랜잭션하는게 타당해보인다.
하지만 당시에는 시간관계상 그렇게 구현하지 못했다.
JPQL 쿼리
@Query("SELECT s FROM Seat s WHERE show_date > current_timestamp")
List<Seat> findRefundTarget();
native 쿼리
@Query(nativeQuery = true, value = "SELECT * FROM seat WHERE show_date between current_timestamp AND date_add(current_timestamp, interval 1 day)")
List<Seat> findLotteryTarget();
Jpa Repository에 쿼리를 등록할 때
JPQL로 등록해두면 어떤 DB에 연결하더라도 JPA에서 JPQL을 해당 DB에 맞게 번역하여 사용하게된다.
하지만 date_add(current_timestamp, interval 1 day)
이 부분을 JPQL로 구현하기가 어려워서 native query의 사용법을 찾아서 적용했다.