QA 게시판 프로젝트 진행 중에, 검색 기능을 추가하고 싶었다.
질문의 제목, 내용 그리고 작성자 중에 검색한 keyword가 포함되어 있으면 해당되는 질문 글들을 찾아주는 기능이다.
처음에는, 간단한 쿼리 작업이기 때문에 JPA가 제공하는 Specification 인터페이스를 사용할까 싶었다. 하지만 나중에는 복잡한 쿼리도 다뤄야 할 때가 생길거고, 또 성능 최적화를 위해 나중에 배우고 싶었던 QueryDsl 을 사용하기로 했다.
queryDsl 설정에는 여러가지 방법이 존재하는 것처럼 보였다.
검색하고 찾아본 결과, queryDsl plugin을 이용하는 방법도 있었고 또 dependencies에 추가할 implementation, annotationProcessor 를 설정하는 방법도 있었다.
하지만 plugin의 경우에는 2018년 7월 이후로 업데이트가 안 된 프로젝트라서 사용하기 좀 찝찝했고, gradle 버전은 고도화 되는데 이 변화에 맞추어 업데이트 되지 못해 생기는 오류들이 생길까 걱정됐다. 따라서 후자의 방법을 택했다.
설정은 간단했다.
dependencies {
implementation 'com.querydsl:querydsl-core'
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
}
궁합이 잘 맞는 버전은 스프링이 알아서 가지고 오도록, 이렇게만 명시해 주면 된다.
또한 QueryDsl 설정을 하기 위해 QueryDslConfig java 파일을 하나 생성해준다.
@Configuration
public class QueryDslConfig {
@PersistenceContext
public EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
이것이 끝이었다. 이제 domain @Entity 로 선언된 객체들은 Q가 붙은 클래스 파일들이 build 폴더 안에 생길 것이다. 그러면 Repository에서 사용할 QAnswer, QUser 등을 임포트해서 그대로 사용하면 된다.
위 설정과 같이 쉽게 끝나면 좋았겠지만.. 항상 개발은 한 번에 잘 되는 경우는 드문 것 같다. 하지만 또 생긴 문제를 이렇게 해결하고 Trouble-shooting 글을 작성하는 것도 재밌는 일이긴 하다.
gradle 파일을 위와 같이 추가해주고 나서 QuerydslConfig 파일을 작성했더니, JPAQueryFactory의 인자에 들어갈 Entitymanager 가 타입이 맞지 않는다는 compileError 가 발생했다.
package com.qa.board.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.PersistenceContext;
@Configuration
public class QueryDslConfig {
@PersistenceContext
public EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em); // Error!
}
}
IDE가 발생시킨 이유를 읽어보니 jakarta에서 임포트된 EntityManager를 인수를 받지 않는다고 한다. 그럼 어떤 걸 인수로 받아야 하는지 보니 javax에서 임포트 해야 한다고 한다.
스프링 부트 버전이 3.xx 로 업그레이드 되면서 javax -> jakarta 라이브러리로 이름이 변했다고 알고 있었는데, 이렇게 dependency 까지 신경써야 할 줄은 몰랐었다.
그래서 시키는 대로 javax 를 임포트해 넣어주었더니, 이제는 아까 gradle 파일에 넣은 implementation, annotationProcessor 에서 문제가 발생해 build 가 진행되질 않았다. 아무래도 jakarta 를 사용하는 라이브러리와 javax 를 사용하는 라이브러리 간의 충돌이 있는 것처럼 보였다.
해결법을 오랜 시간동안 찾아 해맸다. 그 결과, 크게 두 가지 정도 시도해 볼 만한 방법이 있었다.
첫번째 방법은 build 시, jakarta와 javax 간의 충돌이 일어나지 않는 라이브러리 버전을 찾아 명시해 주는 것이다. 지금은 따로 버전을 명시해주지 않아 스프링이 최적의 라이브러리 궁합을 찾아 가져오는 방법이었다면, 직접 명시해주는 방법인 것이다.
예를 들면,
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
5.0.0 과 같이 라이브러리의 버전을 직접 써주는 방법이다.
두번째 방법은 javax, jakarta 두 개의 라이브러리를 모두 컴파일할 수 있도록 dependencies 에 모두 추가하는 방법이다.
예를 들면,
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
implementation 'jakarta.persistence:jakarta.persistence-api'
implementation 'javax.persistence:javax.persistence-api:2.2'
이런 식으로 javax, jakarta 모두에게 관련한 depedency를 추가해주었다.
그 결과, gradle 도 빌드가 잘 진행되었고 QuerydslConfig 파일도 제대로 컴파일 되었다.
그래서 도대체
jakarta와javax는 뭐가 다른건가?
ChatGPT 가 자세하게 설명해준다.
요약하자면,
javax 라는 패키지 네임스페이스로 관리되어 왔다.jakarta 라는 새로운 패키지 네임스페이스로 바꾸어 transfer 과정이 진행됐다.rebranding 또는 Java EE 기술을 이어받았다 고 이해할 수 있겠다.패키지 이름과 dependency 만 업데이트 해주면 된다.
그렇다면 이번에 생긴 문제는 두 패키지 간의 QueryDsl 라이브러리에 대한 dependency 를 맞춰줘야 했던 문제라고 볼 수 있겠다.
QueryDsl의 설정이 그렇게 까다롭지 않다고 알고 있었어서 그런지, 이번 문제는 조금 당황스러웠다.
앞으로 QueryDsl을 스프링 버전 3.xx 로 사용할 일이 많을 것 같으니 해결 방법을 글로 남겨 둔다.
참고 사이트: https://wikidocs.net/162814
https://stackoverflow.com/questions/74756871/spring-boot-3-with-querydsl