๊ฐ์
Spring Boot 2.7.x์์ Spring Boot 3.3.x๋ก ๋ฒ์ ์ ๊ทธ๋ ์ด๋๋ฅผ ์งํํ๋ฉด์ ๋ฐ์ํ ์ฌ๋ฌ ๋ฌธ์ ์ค์์ QueryDSL๊ณผ ๊ด๋ จ๋ ์ด์๋ฅผ ๊ณต์ ํ๊ณ ์ ํ๋ค. (Hibernate ๋ฒ์ ์ ์งํํ ๋ Migration Guides๋ฅผ ์ฐธ๊ณ ํ๋ฉด ๋์ ๋๋ค.)
๋จผ์ Spring Boot๊ฐ 3.x ๋ฒ์ ๋๋ก ์ฌ๋ผ์ค๋ฉด์ Hibernate๋ ๊ธฐ๋ณธ์ ์ผ๋ก 6.1 ๋ฒ์ ์ด ์ ์ฉ๋๋ค. ์ด์ ๋ง์ถฐ Hibernate๋ฅผ v6.5.3.Final๋ก ์ ๊ทธ๋ ์ด๋ํ์ผ๋ ์ํฅ์ผ๋ก ์ด์ ๊น์ง ๋ฌธ์ ์์ด ๋์ํ๋ ์ผ๋ถ QueryDSL ๋ก์ง์์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ธฐ ์์ํ๋ค.
1. InvalidDataAccessApiUsageException: org.hibernate.query.sqm.AliasCollisionException
private final JPAQueryFactory queryFactory; @Override public List<Product> fetchAll() { return queryFactory .select(product) .from(product) .join(product.category, code).fetchJoin() .join(product.brand, code).fetchJoin() .fetch(); }
Hibernate 5.x ํ๊ฒฝ์์๋ ์ ์ฝ๋๊ฐ ์ ์์ ์ผ๋ก ์คํ๋์์ง๋ง, Hibernate 6.x๋ก ์ ๊ทธ๋ ์ด๋ํ ์ดํ์๋ ๋ฐํ์ ์ ๋ค์๊ณผ ๊ฐ์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
InvalidDataAccessApiUsageException: org.hibernate.query.sqm.AliasCollisionException
Hibernate6์์ JPQL/HQL ์ฟผ๋ฆฌ์ alias(๋ณ์นญ) ๊ด๋ จ ์ ์ฝ์ด ๊ฐํ๋์๊ธฐ ๋๋ฌธ์ธ๋ฐ, Hibernate5์์๋ ๋์ผํ Qํ์ (code)์ ๋ ๋ฒ ์ฌ์ฌ์ฉํด๋ ์๋์ผ๋ก ํํผํ์ง๋ง, ์ด์ ๋ ์ค๋ณต๋ ๋ณ์นญ์ ์๊ฒฉํ๊ฒ ๊ธ์งํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ค. ๊ทธ๋์ Hibernate6์์๋ SQL๋ก ๋ณํํ๋ ์์ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ
private final JPAQueryFactory queryFactory; @Override public List<Product> fetchAll() { QCode categoryCode = new QCode("categoryCode"); QCode brandCode = new QCode("brandCode"); return queryFactory .select(product) .from(product) .join(product.category, categoryCode).fetchJoin() .join(product.brand, brandCode).fetchJoin() .fetch(); }
์ค๋ณต alias๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์๋ ์์ ๊ฐ์ด ๊ฐ๊ฐ์ join๋ง๋ค ๋ณ๋์ Qํ์ ์ ์ ์ธํ๊ณ ์๋ก ๋ค๋ฅธ alias๋ฅผ ๋ถ์ฌํด ์ฃผ๋ฉด ์ ์์ ์ผ๋ก ์ ๋์ํ๋ค.
์ด์ ์๋ ์ ๋์ํ๋๊ฐ?
QueryDSL์ ์ฌ์ฉํ ๋ ๊ฐ์ Qํ์ ์ธ์คํด์ค๋ฅผ ์ฌ๋ฌ ๋ฒ ์ฌ์ฌ์ฉํ๋ฉด ๋์ผํ alias๋ก ์ธ์๋์ง๋ง Hibernate 5์์๋ alias ์ถฉ๋์ ๋ํด ์๊ฒฉํ ๊ฒ์ฌ๋ฅผ ํ์ง ์์๊ณ , QueryDSL์ด SQL ์์ฑ ์ ๋ด๋ถ์ ์ผ๋ก ๋ฌผ๋ฆฌ alias๋ฅผ ์๋์ผ๋ก ๋ค๋ฅด๊ฒ ์ง์ ํ๋ฉด์ ์ ์์ ์ผ๋ก ๋์ํ๋ค.
select ... from product product0_ join system_code systemcode1_ on product0_.category_id = systemcode1_.id join system_code systemcode2_ on product0_.brand_id = systemcode2_.id
๊ทธ๋ฌ๋ Hibernate6๋ถํฐ๋ ํ์๊ฐ ์๊ฒฉํ๊ฒ ๋ณ์นญ ์ค๋ณต์ ๊ธ์งํ๋๋ก ๋ณ๊ฒฝ๋์๊ธฐ ๋๋ฌธ์ ๋ ์ด์ ํตํ์ง ์๋๋ค. (Hibernate6์์ "ํ์๊ฐ ์๊ฒฉํ๊ฒ ๋ณ์นญ ์ค๋ณต์ ๊ธ์ง" ํ๋ค๊ณ ์ ํํ ๋ช ์ํ ๋ฌธ์๋ ์ฐพ์ง ๋ชปํ์ผ๋ Hibernate 6์์๋ ์๋ก์ด sqmSQM(parser)๊ณผ ๊ฒฐ๊ณผ ๋งคํ ๋ฐฉ์ ๋์ ์ผ๋ก SQL๋ ๋ ๊ฐ๊ฒฐํ๊ฒ ์์ฑ๋๋๋ฐ, ์ค๋ณต๋ JPQL ๋ณ์นญ์ด ์์ผ๋ฉด Hibernate๋ SQL์์ ๋ณ์นญ์ ๊ฒน์น๊ฒ ๋ด๋ณด๋ผ ๊ฐ๋ฅ์ฑ์ด ์๋ค๋ ์ ์์ ์ ์ถํ๋ค.)
์ฐธ๊ณ ๋ก ์ด ์ด์์ ๋ํด Hibernate ํ์์๋ ์ ์ด์ ์ค๋ณต alias ์ฌ์ฉ์ด ๋ช ์์ ์ผ๋ก ํ์ฉ๋ ๋ฌธ๋ฒ์ด ์๋๊ธฐ ๋๋ฌธ์ Hibernate6์์๋ ์๋ฌ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๋ ์ผ๊ด๋ ๋์์ด๋ผ๊ณ ํ๋จํ ๊ฒ์ผ๋ก ๋ณด์ธ๋ค.(๊ทธ๋์ ๋ฐ๋ก ์ธ๊ธ ์ ํ ๊ฒ์ผ ์๋..?)

2. NoSuchMethodError: org.hibernate.ScrollableResults.get(int)
@Override public Map<Long, ProductDetail> fetchProductDetailsByProductId() { QProductDetail detail = QProductDetail.productDetail; QProduct product = QProduct.product; return queryFactory .select(product.id) .from(detail) .leftJoin(detail.product, product) .transform( groupBy(product.id).as(detail) ); }
java.lang.NoSuchMethodError: 'java.lang.Object org.hibernate.ScrollableResults.get(int)' at com.querydsl.jpa.ScrollableResultsIterator.next(ScrollableResultsIterator.java:70) at com.querydsl.core.group.GroupByMap.transform(GroupByMap.java:57)
.transform(groupBy().as())๋ฅผ ์ฌ์ฉํ๋ QueryDSL ์ฝ๋์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ์์ธ์ Hibernate6 ๋ถํฐ ScrollableResults.get(int) ๋ฉ์๋๊ฐ ์ ๊ฑฐ๋์๋๋ฐ, QueryDSL์ .transform(groupBy().as()) ๋ด๋ถ ๋ก์ง์์๋ ์ฌ์ ํ ํด๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ณ ์๊ธฐ ๋๋ฌธ์ NoSuchMethodError๊ฐ ๋ฐ์ํ๋ค.
์ค์ ์ฝ๋ ๋ถ๋ถ์ ํ์ธํ๋ฉด v5.6.5์๋ ์์๋ Object get(int i); ๋ฉ์๋๊ฐ v6.0๋ถํฐ๋ ๋ณด์ด์ง ์๊ณ , ์ด ์ธ์๋ ๋ค๋ฅธ ๋ฉ์๋๋ค๋ ์ฌ๋ผ์ง ๊ฒ์ ํ์ธํ ์ ์๋ค.
ํด๊ฒฐ๋ฐฉ๋ฒ
ํด๊ฒฐ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ธ ๊ฐ์ง๊ฐ ์๋ค.
- .transform(groupBy().as())๋ฅผ ์ฌ์ฉํ์ง ์๊ณ , List<Tuple>๋ก ์กฐํํ ๋ค ์ง์ Map์ผ๋ก ๋ณํํ๋ ๋ฐฉ์
- JPQLTemplates๋ฅผ ์ฌ์ฉํ์ฌ ๋ด๋ถ ์คํ ๋ฐฉ์ ๋ณ๊ฒฝ
- Hibernate5Templates์ ๊ณ์ ์ฌ์ฉํ๋ฉด์ QueryHandler๋ง ๊ต์ฒด
์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๋ง ๊ทธ๋๋ก .transform()์ ์์ ์ฌ์ฉํ์ง ์๊ณ , ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ List<Tuple> ํํ๋ก ๋ฐ์์์ ์ง์ ๊ฐ๊ณตํ์ฌ Map์ผ๋ก ๋ณํํ๋ ๋ฐฉ์์ด๋ค.
List<Tuple> results = queryFactory .select(product.id, detail) .from(detail) .leftJoin(detail.product, product) .fetch(); // ์ดํ ์์์
์ผ๋ก Map<Long, ProductDetail> ํํ๋ก ๋ณํ
๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ QueryDSL์ ๊ธฐ๋ณธ ์คํ ๋ฐฉ์์ JPA ํ์ค์ผ๋ก ๋ณ๊ฒฝํ์ฌ Hibernate ๋ด๋ถ API ํธ์ถ์ ํผํ๋ ๋ฐฉ์์ด๋ค.(๋ค๋ง index(), class(), function(), cast() ๋ฑ Hibernate5Templates์์๋ง ์ง์ํ๋ ์ผ๋ถ DSL ๊ธฐ๋ฅ์ด ์ ํ๋ ์ ์๋ค.)
public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager); }
์ธ ๋ฒ์งธ ๋ฐฉ๋ฒ์ Hibernate ์คํ์ผ์ ์ฟผ๋ฆฌ ๋ฌธ๋ฒ์ ๊ทธ๋๋ก ์ ์งํ๋ฉด์ ์คํ ์์๋ง JPA ํ์ค ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋๋๋ก QueryHandler๋ง ๊ต์ฒดํ๋ ๋ฐฉ๋ฒ์ด๋ค.
public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(new CustomHibernate5Templates(), entityManager); } /** * transform()์ ์ฌ์ฉํ๊ธฐ ์ํด HibernateQueryHandler ๋์ * DefaultQueryHandler๋ฅผ ์ฌ์ฉํ๋๋ก ๊ฐ์ ์ง์ */ public static class CustomHibernate5Templates extends Hibernate5Templates { @Override public QueryHandler getQueryHandler() { return DefaultQueryHandler.DEFAULT; } }
๋ค๋ง ์ด ๋ฐฉ์์ ์ผ์์ ์ธ ํํผ์ฑ ์ผ๋ก ์ธ ์ ์์ง๋ง ๋ด๋ถ์ ์ผ๋ก ์ฌ์ ํ Hibernate ์คํ์ผ์ ๋ฌธ๋ฒ ๋ฐ ๊ตฌ๋ฌธ ํด์๊ธฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํฅํ ์ ๋ฐ์ดํธ์์ ๋ฌธ์ ๊ฐ ๋ค์ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์์์ ์ธ์งํ์.
๋ ๋ฒ์งธ์ ์ธ ๋ฒ์งธ ๋ฐฉ๋ฒ ๋ชจ๋ ๊ฐ๋ณ ํด๋์ค์์๋ง ์ฌ์ฉํ ์๋ ์๊ณ , ์ ์ญ ์ค์ ์ผ๋ก Bean ๋ฑ๋กํ์ฌ ์ ์ฒด์ ์ ์ฉํ ์๋ ์๋ค. ์ด์ ๊ด๋ จํด์๋ QueryDSL issues #3428๋ฅผ ์ฐธ๊ณ ํ๋ฉด ๋์ ๋ ๊ฒ์ด๋ค.
์ Template์ด๋ Handler๋ฅผ ๋ฐ๊พธ๋ฉด ํด๊ฒฐ๋๋๊ฐ?
๊ธฐ๋ณธ์ ์ผ๋ก Hibernate5 ์ด์ ํ๊ฒฝ์์๋ ๋ณ๋์ ์ค์ ์ ํ์ง ์์ผ๋ฉด Hibernate5Templates๊ฐ ์ฌ์ฉ๋๋ค.(JPAProvider.java#L59)

๊ทธ๋ฆฌ๊ณ Hibernate5Templates๋ ๋ด๋ถ์ ์ผ๋ก HibernateHandler๋ฅผ ์ฌ์ฉํ์ฌ HibernateQuery ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ scroll() ๋ฉ์๋๋ฅผ ํตํด ScrollableResults.get(int)๋ฅผ ํธ์ถํ๋ค. ๋ค๋ง ์ด ๋ฉ์๋๋ Hibernate6์์ ์ ๊ฑฐ๋์๊ธฐ ๋๋ฌธ์ ํด๋น ํธ๋ค๋ฌ๊ฐ ๊ทธ๋๋ก ์ฌ์ฉ๋ ๊ฒฝ์ฐ ๋ฐํ์ ์์ธ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.(Hibernate6๋ฒ์ ์ด ๋์จ ์ง ํ์ฐธ ๋์์๋ ์์ง๋ ์ ๋ฐ์ดํธ๊ฐ ๋๋ ค Hibernate5Templates๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ ์๋๊น..?)
QueryDSL์์ ์ฌ์ฉํ๋ ํ ํ๋ฆฟ ํด๋์ค๋ ๋ค์๊ณผ ๊ฐ์ ์์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
Templates โโโ JPQLTemplates โ JPA ํ์ค์ฉ ํ
ํ๋ฆฟ โโโ HQLTemplates โ Hibernate 4 ํ์ฅ์ฉ โโโ Hibernate5Templates โ Hibernate 5+ ํ์ฅ์ฉ
๊ทธ๋ฆฌ๊ณ ๊ฐ ํ ํ๋ฆฟ์ ๋ค์๊ณผ ๊ฐ์ QueryHandler๋ฅผ ์ฌ์ฉํ๋ค.
JPQLTemplates | DefaultQueryHandler | JPA ํ์ค ์ฟผ๋ฆฌ ์์ฑ ๋ฐ ์คํ ๋ฐฉ์ ์ฌ์ฉ |
HQLTemplates | HibernateHandler | Hibernate ๊ณ ์ ์ฟผ๋ฆฌ ๋ฐฉ์ ์ฌ์ฉ |
Hibernate5Templates | HibernateHandler | Hibernate 5+ ํ์ฅ ์ฟผ๋ฆฌ ๋ฐฉ์ ์ฌ์ฉ |


๊ฒฐ๊ตญ ๋ฌธ์ ์ ํต์ฌ์, Hibernate5Templates๊ฐ ์ฌ์ฉํ๋ HibernateHandler๊ฐ ์์ฑํ HibernateQuery๊ฐ transform() ์คํ ์ scroll()์ ํธ์ถํ๊ณ , ๊ทธ ๋ด๋ถ์์ Hibernate6์์ ์ ๊ฑฐ๋ ScrollableResults.get(int)๋ฅผ ์ฐธ์กฐํ๋ค๋ ์ ์ด๋ค.
๋ฐ๋ผ์ DefaultQueryHandler๋ฅผ ์ฌ์ฉํ๋ JPQLTemplates๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ Hibernate5Templates๋ฅผ ์ฌ์ฉํ๋๋ผ๋ QueryHandler๋ฅผ DefaultQueryHandler๋ก ๋ณ๊ฒฝํ๋ฉด DefaultQueryHandler๋ scroll()์ ์ฌ์ฉํ์ง ์๊ณ .list()๋ .getResultList() ๊ฐ์ JPA ํ์ค API๋ฅผ ํตํด ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
'BackEnd๐ฑ > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring ํธ๋์ญ์ ์ ์ธ์ ์ด๋ป๊ฒ ๋กค๋ฐฑ ๋ ๊น? -2ํธ(feat. rollback-only) (0) | 2024.12.22 |
---|---|
Spring ํธ๋์ญ์ ์ ์ธ์ ์ด๋ป๊ฒ ๋กค๋ฐฑ ๋ ๊น? -1ํธ (2) | 2024.10.04 |
[gradle] implementation, api ์ฐจ์ด (0) | 2024.09.07 |
[JPA] deleteAll(), deleteAllInBatch(), deleteInBatch() ์ ๋ฆฌ (0) | 2024.08.12 |
๋จ๋ฐฉํฅ @OneToMany์ ๋ฌธ์ ์ (2) | 2024.07.26 |
@TransactionalEventListener ์ฌ์ฉ ์ ์ ๋ฐ์ดํธ ์๋ ๋ (0) | 2024.07.03 |
๋๊ธ