JPQL 로 조회 쿼리를 작성하고 이를 PostMan 으로 반환하는 테스트를 하던중
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [@org.springframework.data.jpa.repository.Query jpabook.dashdine.domain.restaurant.Restaurant]
해당 에러를 직면했다.
내가 작성한 쿼리는 String 타입을 반환하는데 현재 Restaurant 타입으로 반환하고 있어서 에러가 발생한 거 같다.
@Query("select r.name, r.tel, r.info, r.openingTime, r.closingTime, r.isOperating from Restaurant r where r.user.id = :userId")
List<Restaurant> findRestaurantListByUserId(@Param("userId") Long userId);
이게 그 문제의 코드인데, 저렇게 작성하면 엔티티에서 지정한 컬럼만 추출하여 엔티티로 변환하는 줄 알았다...
@Query("select r.name, r.tel, r.info, r.openingTime, r.closingTime, r.isOperating from Restaurant r where r.user.id = :userId")
List<String> findRestaurantListByUserId(@Param("userId") Long userId);
반환 타입이 잘못됐다고 했으니, String 으로 바꿔주었다.
근데 생각해보니 위 JPQL 쿼리에서 r.isOperating 은 boolean 타입인데 ?!
갑자기 머리가 뜨거워지기 시작했다.
이에 관련 내용들을 검색해보니, 이러한 경우를 "스칼라 타입 프로젝션" 이라고 한다.
SELECT 절의 조회 대상이 데이터 타입과 상관없이 여러 데이터를 조회하는 경우를 말한다.
이에 이를 조회하기 위해서는 여러 방법이 있는데 그 중 첫 번째가 Object 타입으로 반환하는 것이다.
@Query("select r.name, r.tel, r.info, r.openingTime, r.closingTime, r.isOperating from Restaurant r where r.user.id = :userId")
List<Object> findRestaurantListByUserId(@Param("userId") Long userId);
이와 같이 반환타입을 수정하였다.
이에 Object 타입을 Dto 로 변환하기 위해
for (Object result : restaurants) {
if (result instanceof Object[]) {
Object[] row = (Object[]) result;
String name = (String) row[0];
String tel = (String) row[1];
String info = (String) row[2];
String openingTime = (String) row[3];
String closingTime = (String) row[4];
boolean isOperating = (Boolean) row[5];
RestaurantListResponseDto dto = new RestaurantListResponseDto(name, tel, info, openingTime, closingTime, isOperating);
responseDtos.add(dto);
}
}
이와 같이 반환 로직을 작성하였다.
근데,, 음,,, 뭔가 너무 지저분하다고 느꼈다.
물론 이렇게 작성하고 테스트를 하니 결과는 정상적으로 출력됐다.
아무래도 위와 같이 작성하는 건 뭐랄까 너무 지저분한 거 같기도 하고, 가독성도 떨어지는 느낌으 들어서 다른 방법을 알아봤다.
이번에는 JPQL 에 new 명령어를 통해 바로 DTO 로 반환하는 것이다.
@Query("select new jpabook.dashdine.dto.response.restaurant.RestaurantListResponseDto(r.name, r.tel, r.info, r.openingTime, r.closingTime, r.isOperating) from Restaurant r where r.user.id = :userId")
List<RestaurantListResponseDto> findRestaurantListByUserId(@Param("userId") Long userId);
이와 같이 말이다.
이때 주의할 점은 반환하고자 하는 Dto 의 패키지 경로를 정확하게 작성해야 한다는 것이다.
이와 같이 작성하니 조회하면서 바로 Dto 로 반환해주기 때문에
@GetMapping("/read-restaurant")
public List<RestaurantListResponseDto> readAllRestaurant(@AuthenticationPrincipal UserDetailsImpl userDetails) {
return restaurantManagementService.readAllRestaurant(userDetails.getUser());
}
Controller 쪽에서도 코드가 확 줄었다.
시도 (3) 의 방법을 통해 원하는 데이터를 반환할 수 있었다.
// ========== 가게 조회 ========== //
Hibernate:
select
r1_0.name,
r1_0.tel,
r1_0.info,
r1_0.opening_time,
r1_0.closing_time,
r1_0.is_operating
from
restaurant r1_0
where
r1_0.user_id=?
이와 같이 select 이 잘 작동하였다.
이번 기회를 통해 쿼리의 반환타입의 접근법을 알게되었다.
select 를 통해 컬럼을 지정해주면 해당 컬럼의 데이터로 반환된다는 것을 이 기회에 확실히 알게된 것이다.
근데 생각이 드는 게 과연 이렇게 select 절을 통해 컬럼을 지정하여 반환하는 게 무조건 옳은가 생각이 든다.
물론 select * 보다야, 조회할 컬럼만 지정해서 조회하는 게 성능적으로 뛰어나다는 것은 인지하고 있다.
다만, 이럴 경우 재사용성이 떨어딘다는 단점이 발생하는 거 같다.
이에 범용성 및 재사용성를 생각해서 select * 을 할지, 그럼에도 성능을 위해서 매 다른 상황마다 맞춰서 쿼리를 따로 작성할지, 이에 대해서는 요구사항을 잘 판단하여 결정해야겠단 생각을 하였다.