๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

Hibernate 5.x โ†’ 6.x QueryDSL ๊ด€๋ จ ์ด์Šˆ

by dkswnkk 2025. 3. 28.

๊ฐœ์š”

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์—์„œ๋Š” ์—๋Ÿฌ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ์ผ๊ด€๋œ ๋™์ž‘์ด๋ผ๊ณ  ํŒ๋‹จํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.(๊ทธ๋ž˜์„œ ๋”ฐ๋กœ ์–ธ๊ธ‰ ์•ˆ ํ•œ ๊ฒƒ์ผ ์ˆ˜๋„..?)

https://discourse.hibernate.org/t/duplicate-alias-when-using-hibernate-6-and-querydsl/7705

 

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๋ถ€ํ„ฐ๋Š” ๋ณด์ด์ง€ ์•Š๊ณ , ์ด ์™ธ์—๋„ ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๋“ค๋„ ์‚ฌ๋ผ์ง„ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  1. .transform(groupBy().as())๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , List<Tuple>๋กœ ์กฐํšŒํ•œ ๋’ค ์ง์ ‘ Map์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋ฐฉ์‹
  2. JPQLTemplates๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‚ด๋ถ€ ์‹คํ–‰ ๋ฐฉ์‹ ๋ณ€๊ฒฝ
  3. 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)

JPAProvider.java

๊ทธ๋ฆฌ๊ณ  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+ ํ™•์žฅ ์ฟผ๋ฆฌ ๋ฐฉ์‹ ์‚ฌ์šฉ

HQLTemplates.java
JPQLTemplates.java

๊ฒฐ๊ตญ ๋ฌธ์ œ์˜ ํ•ต์‹ฌ์€, Hibernate5Templates๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” HibernateHandler๊ฐ€ ์ƒ์„ฑํ•œ HibernateQuery๊ฐ€ transform() ์‹คํ–‰ ์‹œ scroll()์„ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ ๋‚ด๋ถ€์—์„œ Hibernate6์—์„œ ์ œ๊ฑฐ๋œ ScrollableResults.get(int)๋ฅผ ์ฐธ์กฐํ•œ๋‹ค๋Š” ์ ์ด๋‹ค.

๋”ฐ๋ผ์„œ DefaultQueryHandler๋ฅผ ์‚ฌ์šฉํ•˜๋Š” JPQLTemplates๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ Hibernate5Templates๋ฅผ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ QueryHandler๋ฅผ DefaultQueryHandler๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด DefaultQueryHandler๋Š” scroll()์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  .list()๋‚˜ .getResultList() ๊ฐ™์€ JPA ํ‘œ์ค€ API๋ฅผ ํ†ตํ•ด ๊ฒฐ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.


GitHub

LinkedIn

GitHub

LinkedIn