발단
지금까지 공부했던 자바, 스프링, 데이터베이스 등을 온전히 내 것으로 소화하고자 개인 프로젝트를 하나 진행하고 있다. 상품을 게시하고, 그 상품을 구매할 수 있는 간단한 웹사이트를 만들고 있는데 상품을 구현하는 부분에서 서브쿼리를 써야 하는 상황이 발생했다. 그와 관련된 자세한 내용은 다음과 같다. 참고로 데이터베이스는 MariaDB를, jdbc 라이브러리(맞는 표현인가..?)는 JPA 하이버네이트를 사용하고 있었다. ( + 스프링 데이터 JPA를 사용하지 않고 EntityManager를 직접 사용하여 데이터베이스 매핑 로직을 구현하고 있었다 )
상황
- 상품 테이블과 리뷰 테이블이 존재했다.
- 상품 테이블과 리뷰 테이블은 1:N으로 묶여 있었다.
- Foreign Key는 리뷰 테이블에 ITEM_ID라는 이름으로 존재했다.
구현 요구사항
- 뷰 : 상품의 목록을 보여주는 페이지에는 페이지당 12개의 상품이 노출된다.
- 뷰 : 이때 각각의 상품은 상품의 이름, 가격을 포함해 '상품의 리뷰 수', '상품의 평점'을 보여주어야 한다.
구현을 위한 아이디어
- "페이징 기능은 JPA가 제공하는 막강한 기능이 있지. 그 기능을 활용해서 구현해야겠다." (무리 없이 수행)
- "목록에 상품, 리뷰를 같이 띄워야 하네.. join을 사용하면 될거 같은데." (1차 아이디어)
- "그런데 단순하게 join을 이용하면 리뷰 수 만큼 상품이 중복되는 현상이 발생하겠구나.. 그건 안되지." (inner join을 하던, left outer join을 하던, right outer join을 하던 아이템 * 리뷰 수 만큼 조인 결과가 발생 [inner join인 경우에는 리뷰 수가 0이면 아이템이 출력되지 않는다는 것도 문제였음])
- "서브 쿼리.. 를 써야 하나. fk를 공유하는 리뷰끼리 집계를 내서 논리적인 테이블을 새로 만들고, 그 테이블과 상품 테이블을 outer join으로 연결하면 상품의 수가 중복되거나 누락되지 않을거 같은데." (2차 아이디어) (fk를 공유하는 리뷰를 집계 내어 새로운 테이블을 만들면 새로운 테이블은 fk당 하나의 행만을 가지게 되므로 상품과의 join 시 중복되는 행이 발생하지 않는다. 또한 outer join을 사용하기 때문에 리뷰가 없어 상품이 누락되는 일이 발생하지 않는다.)
- "정리해보면, 서브쿼리로 리뷰 테이블을 집계 낸 다음, 그 결과를 상품 테이블과 outer join 한다! 괜찮은데?" (괜찮지 않았음)
구현을 시작하다
바로 jpql을 작성하는 것은 겁이 나, 우선 데이터베이스에서 위의 아이디어를 검증해보기로 했다. mariaDB 클라이언트 프로그램을 열고 아이템 하나당 리뷰가 10개씩 연결되는 더미 데이터를 만든 후 SQL 쿼리문을 작성했다.
SELECT [필요한 데이터 (아이템 id, 이름, 가격, 리뷰 평점, 리뷰 수)]
FROM 상품 i
LEFT OUTER JOIN (
SELECT r.아이템_id, r.리뷰_평점, r.리뷰_수
FROM 리뷰 r
GROUP BY r.아이템_id
) x
ON i.아이템_id = x.아이템_id
결과는? 대만족. 생각했던대로 제대로 동작하는 쿼리였다. 자 이제, 이걸 jpql로 옮기기만 하면 되는데..
jpql을 작성하고, 작성된 jpql을 점검하니 뭔가 구현의 80%는 달성한 것 같다는 기대감이 들기 시작했다. 꽤 많이 까다로운 쿼리를 작성했기 때문에 처음에는 동작하지 않을 확률이 절반 이상은 될 것 같다는 생각을 했는데 mariaDB에서 나온 쿼리 결과가 생각보다 좋았기 때문이었다. 거의 다 왔다, 하는 생각으로 jpql을 테스트할 수 있는 테스트 로직을 작성하고 테스트를 실행시켰다. 어 그런데 이게 웬걸..?
QuerySyntaxException...? jpql 쿼리가 잘못되었나? unexpected token이라는 표현이 약간 불안한데.. 하는 생각으로 예외가 발생한 지점을 찾아봤다. (캡처 첫 번째 줄 오른쪽 끝을 보면 첫 번째 줄, 182번 열에서 발생했다는 표현이 있다.)
어 그런데 이게 웬걸..?
... 뭐지?
문제의 원인
혹시나 JPA가 서브쿼리를 지원하지 않는 것은 아닐까 해서 관련 정보를 구글에서 찾아보기 시작했다. 그리고 뜻밖의 사실을 알게 됐다. 바로 JPA의 서브쿼리는 where, having절에서만 가능하다는 것.
2022. 06. 18 보강) select절, where절, having절 가능. from절 불가능 (JPA 표준 스펙에서는 select절도 서브 쿼리가 불가능하다. select절은 하이버네이트 구현체를 사용할 때, 하이버네이트가 지원해준다.)
https://eocoding.tistory.com/41
헐퀴....
이후
그대로 IDE를 종료하고 노트북을 덮었다. 2시간동안 재미있게 삽질을 했구나.. 하는 생각이 들었다. 나름 괜찮게 쿼리를 짠 것 같았는데, 서버 로직에 그 쿼리를 녹이지 못했다는 사실이 못내 아쉬웠다.
그럼 구현을 못하는건가? 잠시 생각했다. 네이티브 쿼리로 직접 SQL문을 날려볼까..? 아냐, 괜히 또 내가 모르는 부분이 있을 수 있어...
그냥 쿼리를 두 번 날리자. 상품 가져오고, 집계된 리뷰 가져와서 비지니스 로직으로 직접 합치지 뭐. 그렇게 어려운 것도 아닌데 뭐.. 그런데 일이 더 늘었네.. ^_^... 하하하 하고 새로운 아이디어만 만든 후 자러 갔다. (새벽 2시인가 그랬다)
끝.
'메모 & 삽질기록보관소' 카테고리의 다른 글
[jqeury] ajax에서의 parse error | 스프링과 자바스크립트의 부조화 (0) | 2022.03.13 |
---|---|
[메모] 배포 관련 리눅스 명령어 (0) | 2022.01.19 |
[EC2 - 서버 배포] TemplateInputException (2) | 2022.01.19 |
[JPA] 연관관계 주인과 CASCADE 옵션 (0) | 2021.12.24 |
MOOC를 통한 프로그래밍 공부 커리큘럼 가이드 (0) | 2021.09.28 |