#17 상영일정 엔티티 CRUD 구현

seojin's 개발블로그·2023년 11월 1일
0

영화 사이트 제작

목록 보기
17/19

이전글에서 작성한 엔티티 설계대로 엔티티를 작성하고
API 명세대로 기능 구현을 하였다.

📌 상영일정 엔티티 구현

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "schedules")
public class Schedule {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "schedule_id", nullable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "hall_id", referencedColumnName = "hall_id")
    private Hall hall;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "movie_id", referencedColumnName = "movie_id")//, insertable = false, updatable = false
    private Movie movie;

    @Column(name = "start_time", nullable = false)
    private LocalDateTime startTime;

    @Column(name = "end_time", nullable = false)
    private LocalDateTime endTime;

    @CreatedDate
    @Column(name = "created_at")
    private LocalDateTime createAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @Builder
    public Schedule(Hall hall, Movie movie, LocalDateTime startTime, LocalDateTime endTime) {
        this.hall = hall;
        this.movie = movie;
        this.startTime = startTime;
        this.endTime = endTime;
    }
}

여러 상영일정이 하나의 상영관, 영화를 참조하므로 @ManyToOne으로 관계를
정의하였다.
그리고 최적화를 위해 FetchType.LAZY 옵션으로 지연로딩 설정을 걸어주었다.

@OneToMany(mappedBy = "hall", cascade = CascadeType.PERSIST)
private List<Schedule> schedules;

그리고 상영관별 상영일정의 조회가 필요할 것 같아 팀원분께 부탁하여 상영관에도 관계를 정의하여 양방향 맵핑을 해주었다.
상영관의 삭제등 연산이 일어났을때 상영일정에도 일괄 적용을 하기 위해
cascade 옵션도 설정을 해주었다.

📌 상영일정 생성 구현

Controller

@PostMapping("/api/schedule/createSchedule")
public ResponseEntity<ApiResponse<?>> createSchedule(@RequestBody CreateScheduleRequestDto requestDto){

    try {
        scheduleService.saveSchedule(requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(new ApiResponse<>(1, "스케줄 등록 성공", null));
    }catch (IllegalStateException E) {
    	return ResponseEntity.badRequest().body(new ApiResponse<>(0, "스케줄 등록 실패", null));
    }

}

Service

public void saveSchedule(CreateScheduleRequestDto requestDto) {

    Movie movie = movieService.findMovieById(requestDto.getMovieId());
    Hall hall = hallService.findHallById(requestDto.getHallId());

    scheduleRepository.save(requestDto.toEntity(movie, hall));
}

저장의 경우 처음엔 json에 movie와 hall의 모든 데이터를 담아서 요청을 보내려 했으나
hall의 경우 상영일정 이외에도 참조관계를 가지고 있기에 코드의 복잡성과 통일성을 위해서 각 엔티티의 id값만 요청에 실어서 보내고 서비스 계층에서 조회후 등록을 하게 구현을 하였다.

📌 상영일정 조회 구현

Controller

@GetMapping("/api/schedule/findScheduleByScreen")
public ResponseEntity<ApiResponse<?>> searchSchedule(SearchScheduleRequestDto requestDto){

    try {
        List<SearchScheduleResponseDto> scheduleInfo = scheduleService.searchSchedules(requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(new ApiResponse<>(1, "상영일정 검색 성공", scheduleInfo));
    }catch (IllegalStateException E){
        return ResponseEntity.badRequest().body(new ApiResponse<>(0, "상영일정 검색 실패", null));
    }

}

Service

public List<SearchScheduleResponseDto> searchSchedules(SearchScheduleRequestDto requestDto) {
    List<Schedule> schedules = scheduleRepository.findScheduleByHallIdAndScreenDate(requestDto.getHallId(), requestDto.getScreenDate());

    List<SearchScheduleResponseDto> responseDtos = new ArrayList<>();
    for (Schedule schedule : schedules) {
        SearchScheduleResponseDto responseDto = new SearchScheduleResponseDto();
        responseDto.setStartTime(schedule.getStartTime());
        responseDtos.add(responseDto);
    }

    return responseDtos;
}

Repository

@Query("SELECT e FROM Schedule e WHERE e.hall.id = :hallId AND DATE(e.startTime) = DATE(:screenDate)")
List<Schedule> findScheduleByHallIdAndScreenDate(@Param("hallId") Long hallId, @Param("screenDate") LocalDate screenDate);

상영일정 조회는 상영관의 id와 선택한 상영일을 요청으로 받아 처리하게 하였다.
그리고 해당 상영일정의 시작시간만을 응답하게 구현을 했다
후에 작성할 프론트 파트를 편하게 구현하려고 최소로 필요한 데이터만 백단에서 필터링을 하는식으로 구현을 하고 있는데
만약 내가 아닌 다른 사람이 프론트를 구현하면 오류 검출이 힘들지 않을까? 라는 생각이 들어서 팀원분과 논의후 메서드를 수정해볼 예정이다.
그리고 Repository는 jpa 문법을 사용해서 작성을 할까 했지만
오히려 가독성이 떨어지고 남이 봤을때 이해하기가 힘들것 같았고 성능상 어떤 방식을 이용해도 유의미한 차이가 없다는 레퍼런스가 있어 쿼리문을 작성해서 붙여주었다.

📌 발생한 문제

1. 상영일정 데이터 저장시 데이터 직렬화 에러

Error : InvalidDefinitionException: No serializer found for class

발생 원인:

 @PostMapping("/api/schedule/createSchedule")
public ResponseEntity<ApiResponse<?>> createSchedule(@RequestBody CreateScheduleRequestDto requestDto){

    try {
        Schedule savedScheduleInfo = scheduleService.saveSchedule(requestDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(new ApiResponse<>(1, "스케줄 등록 성공", savedScheduleInfo));
    } catch (IllegalStateException E) {
        return ResponseEntity.badRequest().body(new ApiResponse<>(0, "스케줄 등록 실패", null));
    }

}

외래키의 지연로딩으로 인한 에러로 컨트롤러에서 저장된 상영일정 정보를 응답값으로 넣을때 hall과 movie 애트리뷰트가 아직 proxy객체에서 실제 객체로 초기화 되기전에 값을 반환하기 때문에 발생한 에러였다.

해결 방법:

I. 반환 타입의 변경
저장 메서드의 반환 타입을 void로 변경하고 컨트롤러의 응답값의 데이터도 null로 변경 저장의 경우 성공, 실패의 여부만 알 수 있으면 될 것이라 생각해서 이 방식으로 수정을 하여서 해결했다.

II. 반환에 dto를 이용
응답값을 실제 엔티티가 아닌 dto를 이용해서 반환하는 방법도 생각했으나
우선은 위의 1번 방법으로 해결을 하였다.

2. 데이터 조회시 상영일의 직렬화 문제

Error : Parameter value [2023-10-21] did not match expected type

발생 원인:

#조회 요청 dto의 일부
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd", timezone="Asia/Seoul")
private LocalDate screenDate;
@Query("SELECT e FROM Schedule e WHERE e.hall.id = :hallId AND DATE(e.startTime) = :screenDate")
List<Schedule> findScheduleByHallIdAndScreenDate(@Param("hallId") Long hallId, @Param("screenDate") LocalDate screenDate);

I. dto의 역직렬화 에러
우선 Get 메서드로 json이 아닌 url의 파라미터 형식으로 데이터 요청이 들어오는데 JsonFormat으로 역직렬화를 시도함

II. repository
repository의 경우 실제 데이터의 시작 시간 애트리뷰트는 년월일시분초까지 저장이 되는 DateTime 형식이라 Jpa의 Date 함수를 이용해서 연월일만 비교하게 하려는 생각이었으나 mariaDb의 경우 저장 형식이 다른것인지 직렬화에 에러가 발생했다.

해결 방법:

I. dto 수정

@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate screenDate;

어노테이션을 요청값에 맞게 DateTimeFormat으로 변경해주었다.

II. repository 쿼리문 수정

@Query("SELECT e FROM Schedule e WHERE e.hall.id = :hallId AND DATE(e.startTime) = DATE(:screenDate)")

상영일에도 DATE() 함수를 적용시켜서 비교값의 데이터 타입을 완전히 같게 해주었다.

📌 후기

-상영일정 데이터 저장시 데이터 직렬화 에러
를 해결하는데 시간이 오래 걸렸는데 팀원분이 같은 에러를 겪으신적이 있어서 피드백을 받아 바로 해결하였다.

사실 데이터는 정상적으로 저장이 되고 있었고 컨트롤러에서 응답값을 호출하지 못해 발생한 문제라 데이터베이스만 미리 확인했으면 더 빨리 해결을 했을텐데 에러메세지를 보고 너무나 큰 에러로 착각을 해서 구조적인 부분을 고치려다 보니 많은 시간이 소비되었다.

좀 더 주의해야겠다 그리고 역시 개발은 같이해야 어떤 부분이던 더 빨리 좋은 방식으로 해결을 할 수 있는것 같다. 팀원분이 없었으면 더 오랜시간을 소비 했을거다.

이번 회차 회고를 다시 읽으며 느낀건데
요즘 여러 사정으로 공부에 집중을 못하다보니 디테일만 부분들이 부족해졌다는 느낌이 들었다.
ex) 내가 아닌 다른 사람이 프론트를 맡는다면 불편할 것 같은 부분들

팀원분도 바쁜 시기다 보니 서로 논의를 많이 못해서 보완을 바로바로 못해서 발생하는 문제 같기도 하다.

곧 프론트 부분 제작도 해야하니 프론트 파트의 에로사항도 고려를 해서 개발을 해야겠다.

profile
개발 공부하는 블로그

0개의 댓글