개인 프로젝트/예약 시스템 개발하기

예약 시스템 개발하기 06. EmbeddedId와 ConverterNotFoundException

going.yoon 2022. 3. 12. 15:18

현재 예약 시스템은 화면에서 신청에 필요한 AccountId, 멤버십Id, 수업Id를 받아와 예약 히스토리에 해당 값을 insert하는 방식으로 진행한다. 이를 저장하기 위한 객체는 CourseReservation 객체였는데, 처음에는 AccountId만 PK로 지정했다가 수업Id와 AccountId를 같이 중복키로 등록해야 할 필요성이 생겼다.

 

이를 위해서 CourseReservationId라는 객체를 만들어주고 @Embeddable이라는 애노테이션을 붙여준다. 이렇게 PK만 따로 도메인을 만들어주면, 기존의 CourseReservation객체에서는 이를 주입받아서 사용하기만 하면 된다.

@Data
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
public class CourseReservationId implements Serializable {

    @Column(name="crs_id")
    private Long crsId; // 수업아이디

    @Column(name = "account_id", nullable = false)
    private Integer accountId;

    public CourseReservationId(Integer accountId, Long crsId) {
        this.crsId = crsId;
        this.accountId= accountId;
    }
}

 

CourseReservation 객체에서는 중복키 객체를 등록하기 위해 @EmbeddedId 애노테이션을 붙여주면 된다.

@Data
@Entity
@Table(name = "crs_rsv")
public class CourseReservation {

    @EmbeddedId
    private CourseReservationId courseReservationId;

    @Column(name = "mmbrshp_id", nullable = false)
    private Integer mmbrshpId; // 멤버십 아이디

}

리포지토리 인터페이스도 아래와 같이 PK값에 Integer > CourseReservationId객체로 변환해주면 된다.

@Repository
public interface CourseReservationRepository extends JpaRepository<CourseReservation, CourseReservationId>

 

서비스 부분에서는 Repository의 매개변수를 생성할 때 CourseReservation과 CourseReservationId를 각각 셋팅하여 생성한다.

@Transactional
public CourseReservation saveCrsSrv(CourseReservationDto vo){
    CourseReservation bean = new CourseReservation();
    CourseReservationId id = new CourseReservationId(vo.getAccountId(), vo.getCrsId());

    bean.setMmbrshpId(vo.getMmbrshpId());
    bean.setCourseReservationId(id);

    bean = courseReservationRepository.save(bean);
  }

 

테스트 다시 성공!

 

그리고 사용자가 예약한 수업리스트를 모두 가져오기 위해서 테스트 코드에 아래와 같은 로직을 추가해주었다.

List<CourseDTO> vo = courseReservationController.getReservedCourses(1);
System.out.println("reserved course: " + vo.toString());

 

테스트 결과는 ConverterNotFoundException 발생

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.core.euljiro.dto.CourseReservationDto]

 

 

CourseReservationDto는 

@Data
@Builder
@NoArgsConstructor
public class CourseReservationDto {

    @NotNull
    private Long crsId; // 수업아이디

    @NotNull
    private Integer mmbrshpId; // 멤버십 아이디

    @NotNull
    private Integer accountId;

    @Builder
    public CourseReservationDto(Long crsId, Integer mmbrshpId , Integer accountId){
        this.crsId = crsId;
        this.mmbrshpId = mmbrshpId;
        this.accountId = accountId;
    }
}

 

Repository에서 JPQL이 이렇게 작성되어 있어 문제가 발생하였다.

@Repository
public interface CourseReservationRepository extends JpaRepository<CourseReservation, CourseReservationId> {
    @Query(value="select p.crs_id as crsId, p.mmbrshp_id as mmbrshpId , p.account_id as accountId from crs_rsv p " +
            "where p.account_id = :userId", nativeQuery = true)
    public List<CourseReservationDto> findByUserId(@Param("userId") int userId);
}

 

native 쿼리 사용시 도메인으로 리턴을 받지 않고 Dto로 받아야 할 때 Converter를 작성해주어야 하는데 명시된 Converter가 없어서 나는 오류다(https://www.popit.kr/jpa-native-query-%EC%82%AC%EC%9A%A9-%EC%8B%9C-dto%EB%A1%9C-%EB%A7%A4%ED%95%91%ED%95%98%EA%B8%B0/

 

그렇다... 리턴 타입에 Dto가 들어가있었다. 나는 도메인으로 리턴을 받을 생각이었는데....

 

 

 

그런데 알아보던 중, 중복키를 주입받았을 때 중복키 객체를 기준으로 쿼리 메소드를 작성하는 방법이 스프링 JPA 문서에 나와있었다.

https://www.baeldung.com/spring-jpa-embedded-method-parameters

// 1. Book 도메인

@Entity
public class Book {

    @EmbeddedId
    private BookId id; // id라는 이름으로 중복키 객체 생성
    private String genre;
    private Integer price;

    //standard getters and setters
}


// 2. findById + 중복키 객체 내 키 name 으로 쿼리 메소드 생성
@Repository
public interface BookRepository extends JpaRepository<Book, BookId> {

    List<Book> findByIdName(String name);

    List<Book> findByIdAuthor(String author);
}

// 3. Jpa에서 자동으로 conversion 해줌

findByIdName -> directive "findBy" field "id.name"
findByIdAuthor -> directive "findBy" field "id.author"

 

 

나는 중복키 객체를 

@EmbeddedId
private CourseReservationId courseReservationId;

이렇게 선언했으므로 쿼리메소드는 courseReservationIdAccountId로 작성하면 directive "findBy" field courseReservation.AccoutId 로 변환해주겠지! 아래와 같이 리포지토리 소스를 변경해주었다.

@Repository
public interface CourseReservationRepository extends JpaRepository<CourseReservation, CourseReservationId> {
//    @Query(value="SELECT crs_id as crsId, mmbrshp_id as mmbrshpId , account_id as accountId from crs_rsv " +
//            "where account_id = :userId", nativeQuery = true)
    public List<CourseReservation> findByCourseReservationIdAccountId(@Param("userId") int userId);
}

 

 

드디어콘솔에 찍힌 예약 결과!
test passed!

 

하.. 뿌듯한 마음으로 회먹으러가야징