Backend 프로젝트 후기 - (1)

Slow·2022년 2월 15일
0

서두

기억력이 나쁜 미래의 나를 위해, 백엔드 프로젝트를 진행하는 과정에서 마주친 문제들과 그 해결책을 찾는 과정을 남겨두고자 한다

(1) MultipartFile과 @RequestBody

아래의 코드는 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


(2) JPA 일대일, 일대다 관계에서의 cascade remove

삭제될 엔티티(여기에 cascade옵션 달아줌) -> cascade하게 삭제될 엔티티


(3) 스케쥴링과 트랜잭션 어노테이션은 동시에 사용할 수 없다

때문에 컨트롤러에서 스케쥴링하고 서비스에서만 트랜잭션하는게 타당해보인다.
하지만 당시에는 시간관계상 그렇게 구현하지 못했다.


(4) native query를 사용하면 DB의 펑션을 사용가능

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의 사용법을 찾아서 적용했다.

profile
터벅터벅 전진

0개의 댓글