세팅이랑, 레포지토리 일부 수정이나 만들기만 하면 끝남. todo쪽에.
그 전에 앞서 했던 포스팅과 유사하게 필요한 개념을 먼저 정리하고 시작하도록 하겠습니다.
@Query("SELECT t FROM Todo t LEFT JOIN t.user WHERE t.id = :todoId")
Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);
QTodo todo = QTodo.todo;
QUser user = QUser.user;
return queryFactory.selectFrom(todo)
.leftJoin(todo.user, user).fetchJoin()
.where(todo.id.eq(todoId))
.fetchOne();
LEFT JOIN FETCH 사용 시 한 번의 쿼리로 조회해야 함.
QueryDSL의 .fetchJoin()을 사용하면 EAGER FETCH와 같은 효과로 N+1 문제 해결 가능.
Spring Boot 3.x 버전과 호환되는 QueryDSL 5.x 버전을 명시적으로 추가하는 것이 좋습니다
dependencies {
// QueryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0' // QueryDSL JPA 의존성 추가
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa' // Q클래스 자동 생성
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0' // JPA 메타 모델을 위해 필요
}
sourceSets {
main {
java {
srcDirs = ['src/main/java', 'build/generated/querydsl']
}
}
}
tasks.withType(JavaCompile) {
options.annotationProcessorGeneratedSourcesDirectory = file('build/generated/querydsl')
}
Q클래스가 생성될 경로를 지정하고, 해당 경로를 소스 디렉토리로 인식시키기 위해 설정.
IntelliJ IDEA를 사용하신다면, build/generated/querydsl 디렉토리를 'Generated Sources Root'로 설정하여 IDE가 해당 디렉토리를 소스 루트로 인식하도록 해야 합니다.
이러한 설정을 통해 QueryDSL의 Q클래스가 자동으로 생성되고, QTodo, QUser 등의 클래스를 인식할 수 있게 됩니다.
기존 TodoRepository에서 JPQL을 제거하고 QueryDSL을 적용할 새로운 Custom Repository를 추가
TodoRepositoryImpl에서 QueryDSL을 활용한 findByIdWithUser 구현
QueryDSL을 적용하도록 TodoRepository 인터페이스를 변경
TodoService에서 findByIdWithUser 호출 방식 변경
QTodo와 QUser에 빨간 밑줄이 그어지는 건 QueryDSL의 Q 클래스가 아직 생성되지 않았기 때문이라서 괜찮습니다.
이건 빌드 과정에서 자동으로 생성되는 파일이라서, 직접 만들 필요는 없습니다.
그래도 실행때 안된다면, 확인해줘야할 것들이 있습니다.
(위에 설정이 제대로 되어있는지 확인하면 됩니다. 위에 설정 잘못 되어있는 부분들 다 수정했습니다.)
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
에서
//queryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0' // QueryDSL JPA 의존성 추가
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa' // JPA 기반으로 Q 클래스 자동 생성
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0' // JPA 메타 모델을 위해 필요
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
등 수정사항들을 작성하려 했으나, 수정본만 올리도록 하겠습니다!
plugin 추가
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' //QueryDSL 플러그인 추가
}
build 파일 삭제하고 다시 실행.
Execution failed for task ':compileQuerydsl'.
> Annotation processor 'com.querydsl.apt.jpa.JPAAnnotationProcessor' not found
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.12.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 1s
4 actionable tasks: 4 executed
에러 발생.
Execution failed for task ':compileQuerydsl'.
> Annotation processor 'com.querydsl.apt.jpa.JPAAnnotationProcessor' not found
JPAAnnotationProcessor를 찾지 못하는 이유는 QueryDSL의 annotationProcessor 의존성이 올바르게 설정되지 않았거나, Gradle이 이를 제대로 인식하지 못하는 경우 발생합니다.
일단 build.gradle를 분석하는게 제일 좋다고 판단했습니다.
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
이 플러그인은 QueryDSL 관련 설정을 쉽게 관리할 수 있도록 도와줍니다.
QueryDSL을 사용할 때 수동으로 QClass(예: QTodo, QUser)를 생성하지 않아도 되도록 해줍니다.
// QueryDSL JPA 의존성 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0'
// JPA 기반으로 Q 클래스 자동 생성
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
// JPA 메타 모델을 위해 필요
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
// Jakarta Annotation 처리
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
def querydslDir = "$buildDir/generated/querydsl"
QueryDSL의 Q클래스가 생성될 디렉토리를 설정합니다.
build/generated/querydsl/ 경로에 QTodo, QUser 같은 QClass 파일이 생성됩니다.
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
QueryDSL의 JPA 모드를 활성화하고, QClass 생성 디렉토리를 설정합니다.
sourceSets {
main {
java {
srcDirs += querydslDir
}
}
}
QClass가 생성된 querydslDir을 소스 디렉토리에 추가합니다.
why ? -> 그래야 QClass(QTodo, QUser)를 import할 때 Gradle이 인식할 수 있습니다.
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
Java 컴파일 시, annotationProcessor가 querydslDir에 QClass를 생성하도록 설정합니다.
tasks.named('compileJava') {
dependsOn 'clean'
}
매 빌드 시 clean을 실행하여 QClass를 강제로 재생성합니다.
why ? -> QClass가 생성되지 않는 문제를 방지할 수 있음.
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// JPA 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok (컴파일 시 필요)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 데이터베이스 (H2, MySQL)
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// JWT 관련 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// QueryDSL 의존성 (JPA)
implementation 'com.querydsl:querydsl-jpa:5.0.0'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
// Jakarta Persistence API (javax.persistence.Entity 해결)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
}
def querydslDir = "$buildDir/generated/sources/annotationProcessor/java/main"
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
sourceSets {
main {
java {
srcDirs += querydslDir
}
}
}
tasks.named('compileJava') {
dependsOn 'clean'
}
https://developer-jinnie.tistory.com/21 를 참고하여,
순서대로 해봤음에도 실패.
./gradlew clean build --stacktrace
➜ spring-plus git:(main) ✗ ./gradlew clean build --stacktrace
Starting a Gradle Daemon, 1 incompatible and 4 stopped Daemons could not be reused, use --status for details
> Task :compileJava FAILED
/Users/mun/Desktop/2025/spring-plus/src/main/java/org/example/expert/domain/todo/repository/TodoRepositoryQueryDslImpl.java:6: error: cannot find symbol
import org.example.expert.domain.todo.entity.QTodo;
^
symbol: class QTodo
location: package org.example.expert.domain.todo.entity
/Users/mun/Desktop/2025/spring-plus/src/main/java/org/example/expert/domain/todo/repository/TodoRepositoryQueryDslImpl.java:8: error: cannot find symbol
import org.example.expert.domain.user.entity.QUser;
^
symbol: class QUser
location: package org.example.expert.domain.user.entity
[Incubating] Problems report is available at: file:///Users/mun/Desktop/2025/spring-plus/build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileJava'.
> java.lang.NoClassDefFoundError: javax/persistence/Entity
* Try:
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':compileJava'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:130)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:293)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:128)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:116)
at org.gradle.api.internal.tasks.execution.ProblemsTaskPathTrackingTaskExecuter.execute(ProblemsTaskPathTrackingTaskExecuter.java:40)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:74)
at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:42)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:331)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:318)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.lambda$execute$0(DefaultTaskExecutionGraph.java:314)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:314)
at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:303)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:459)
at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:376)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
Caused by: java.lang.RuntimeException: java.lang.NoClassDefFoundError: javax/persistence/Entity
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:168)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.doCall(JavacTaskImpl.java:100)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:94)
at org.gradle.internal.compiler.java.IncrementalCompileTask.call(IncrementalCompileTask.java:92)
at org.gradle.api.internal.tasks.compile.AnnotationProcessingCompileTask.call(AnnotationProcessingCompileTask.java:94)
at org.gradle.api.internal.tasks.compile.ResourceCleaningCompilationTask.call(ResourceCleaningCompilationTask.java:57)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:78)
at org.gradle.api.internal.tasks.compile.JdkJavaCompiler.execute(JdkJavaCompiler.java:46)
at org.gradle.api.internal.tasks.compile.daemon.AbstractIsolatedCompilerWorkerExecutor$CompilerWorkAction.execute(AbstractIsolatedCompilerWorkerExecutor.java:78)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:54)
at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:48)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
at org.gradle.workers.internal.AbstractClassLoaderWorker.executeInClassLoader(AbstractClassLoaderWorker.java:48)
at org.gradle.workers.internal.FlatClassLoaderWorker.run(FlatClassLoaderWorker.java:32)
at org.gradle.workers.internal.FlatClassLoaderWorker.run(FlatClassLoaderWorker.java:22)
at org.gradle.workers.internal.WorkerDaemonServer.run(WorkerDaemonServer.java:108)
at org.gradle.workers.internal.WorkerDaemonServer.run(WorkerDaemonServer.java:77)
at org.gradle.process.internal.worker.request.WorkerAction$1.call(WorkerAction.java:159)
at org.gradle.process.internal.worker.child.WorkerLogEventListener.withWorkerLoggingProtocol(WorkerLogEventListener.java:41)
at org.gradle.process.internal.worker.request.WorkerAction.lambda$run$1(WorkerAction.java:156)
at org.gradle.internal.operations.CurrentBuildOperationRef.with(CurrentBuildOperationRef.java:85)
at org.gradle.process.internal.worker.request.WorkerAction.run(WorkerAction.java:148)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
Caused by: java.lang.NoClassDefFoundError: javax/persistence/Entity
at com.querydsl.apt.jpa.JPAAnnotationProcessor.createConfiguration(JPAAnnotationProcessor.java:37)
at com.querydsl.apt.AbstractQuerydslProcessor.process(AbstractQuerydslProcessor.java:82)
at org.gradle.api.internal.tasks.compile.processing.DelegatingProcessor.process(DelegatingProcessor.java:62)
at org.gradle.api.internal.tasks.compile.processing.IsolatingProcessor.process(IsolatingProcessor.java:50)
at org.gradle.api.internal.tasks.compile.processing.DelegatingProcessor.process(DelegatingProcessor.java:62)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor.access$401(TimeTrackingProcessor.java:37)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor$5.create(TimeTrackingProcessor.java:99)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor$5.create(TimeTrackingProcessor.java:96)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor.track(TimeTrackingProcessor.java:117)
at org.gradle.api.internal.tasks.compile.processing.TimeTrackingProcessor.process(TimeTrackingProcessor.java:96)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:1023)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:939)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1267)
at jdk.compiler/com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1382)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1234)
at jdk.compiler/com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:916)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.lambda$doCall$0(JavacTaskImpl.java:104)
at jdk.compiler/com.sun.tools.javac.api.JavacTaskImpl.invocationHelper(JavacTaskImpl.java:152)
... 32 more
Caused by: java.lang.ClassNotFoundException: javax.persistence.Entity
... 50 more
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.12.1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD FAILED in 6s
2 actionable tasks: 2 executed
문제를 보면 자꾸
javax.persistence.Entity 클래스를 찾을 수 없음
라고 뜨는데, javax.persistence.Entity를 찾을 수 없다는 것을 의미합니다.
QueryDSL이 jakarta.persistence를 사용할 수 있도록 설정하면 될 것 같습니다.. ㅎㅎ...
그리고 에러 중간에 보면 Q클래스가 생성되지 않는데, 이건 쿼리dsl 어노테이션 프로세서가 실행되지 않았거나, 쿼리dsl이 javax.persistence를 요구하는데 jakarta.persistence를 사용하면서 충돌이 발생하는 것입니다.
이것 또한 앞서 말햇듯이 QueryDSL의 어노테이션 프로세서가 jakarta.persistence를 지원하도록 수정.
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// JPA 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok (컴파일 시 필요)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 데이터베이스 (H2, MySQL)
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// JWT 관련 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// bcrypt
implementation 'at.favre.lib:bcrypt:0.10.2'
// QueryDSL 의존성 (JPA)
implementation 'com.querydsl:querydsl-jpa:5.0.0'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
// Jakarta Persistence API (javax.persistence.Entity 해결)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
//test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
def querydslDir = "$buildDir/generated/sources/annotationProcessor/java/main"
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
sourceSets {
main {
java {
srcDirs += querydslDir
}
}
}
tasks.named('compileJava') {
dependsOn 'clean'
}
이것을 수정해야함.
보면,QueryDSL과 Jakarta Persistence 설정 관련 문제 때문에 여전히 javax.persistence.Entity 관련 에러가 발생할 가능성이 있기 때문에 수정이 필요함.
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
에서 수정이 필요함.
위 설정이 QueryDSL의 어노테이션 프로세서(querydsl-apt) 와 충돌을 일으킬 가능성이 있음. 현재 QueryDSL이 jakarta.persistence 기반으로 동작할 수 있도록 명확하게 설정되지 않았기 떄문에 수정해주면 됩니다.
// Jakarta Persistence API (javax.persistence.Entity 해결)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api' // QueryDSL이 jakarta.persistence를 사용하도록 보장
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
이 부분은 필요 없음. annotationProcessor에만 추가하면 해결되기 떄문에 위와같이 수정해주면 됩니다.
def querydslDir = "$buildDir/generated/sources/annotationProcessor/java/main"
이렇게 해주면 제대로 적용되지 않을 가능성이 있음.
QueryDsl이 생성한 Qclass가 프로젝트의 소스 경로로 제대로 인식되지 않으면,
빌드시 QTodo, QUser를 찾을 수 없는 오류가 발생됨.
그래서,
def querydslDir = "$buildDir/generated/querydsl"
위와 같이 설정하고 소스 세트(sourceSets.main.java.srcDirs += querydslDir) 를 올바르게 추가해야 함.
tasks.named('compileJava') {
dependsOn 'clean'
}
이렇게 설정하면 매번 빌드할 때마다 clean을 수행하므로 빌드 속도가 느려짐.
따라서, 쿼리dsl 관련 파일이 삭제되는 부작용도 있을 수 있으니까, 수정.
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
이와 같이 설정하면 clean 없이도 QueryDsl 의 Q클래스를 자동 생성하고, 올바른 디렉토리로 저장되게 됨.
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// JPA 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok (컴파일 시 필요)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 데이터베이스 (H2, MySQL)
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// JWT 관련 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// bcrypt
implementation 'at.favre.lib:bcrypt:0.10.2'
// QueryDSL 의존성 (JPA)
implementation 'com.querydsl:querydsl-jpa:5.0.0'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
// Jakarta Persistence API (javax.persistence.Entity 해결)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api' // QueryDSL이 jakarta.persistence를 사용하도록 보장
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
//test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
def querydslDir = "$buildDir/generated/querydsl"
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
sourceSets {
main {
java {
srcDirs += querydslDir
}
}
}
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
코드에 중복 발견. 수정
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// JPA 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok (컴파일 시 필요)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 데이터베이스 (H2, MySQL)
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// JWT 관련 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// bcrypt
implementation 'at.favre.lib:bcrypt:0.10.2'
// QueryDSL (JPA 지원)
implementation 'com.querydsl:querydsl-jpa:5.0.0' // QueryDSL JPA 버전
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa' // JPA 기반으로 Q 클래스 생성
// Jakarta Persistence API (javax.persistence.Entity 문제 해결)
annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
// 테스트 의존성
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
// QueryDSL Q 클래스 생성 경로
def querydslDir = "$buildDir/generated/querydsl"
tasks.withType(JavaCompile).configureEach {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}
sourceSets {
main {
java {
srcDirs += querydslDir
}
}
}
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.3'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// JPA 관련 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Lombok (컴파일 시 필요)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// 데이터베이스 (H2, MySQL)
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
// JWT 관련 라이브러리
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// bcrypt
implementation 'at.favre.lib:bcrypt:0.10.2'
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
// 테스트 의존성
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
로 튜터님께 문의해서 수정했는데, 이건 QEntitY는 생성되는데, 콘솔창에 에러가 발생했습니다.
이에 설정 파일이 저랑 무엇이 다른지 확인해보고, 이를 분석해보고 콘솔창 에러를 해결해보겠습니다.
쿼리 Dsl 버전 및 자카르타 설정 차이
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
이렇게 수정했는데, 직접 자카르타 버전을 명시
(이전 코드)
implementation 'com.querydsl:querydsl-jpa:5.0.0
+ :jpa
별도로 자카르타.persistence 추가
Q 클래스 자동 생성 관련 설정
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
=> 자카르타 버전 지정annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jpa'
2025-03-14T20:47:44.961+09:00 INFO 21544 --- [foodduck] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-03-14T20:47:45.244+09:00 WARN 21544 --- [foodduck] [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'commentController' defined in file [/Users/mun/Desktop/2025/spring-plus/out/production/classes/org/example/expert/domain/comment/controller/CommentController.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'commentService' defined in file [/Users/mun/Desktop/2025/spring-plus/out/production/classes/org/example/expert/domain/comment/service/CommentService.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'todoRepository' defined in org.example.expert.domain.todo.repository.TodoRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot resolve reference to bean 'jpa.TodoRepository.fragments#0' while setting bean property 'repositoryFragments'
2025-03-14T20:47:45.244+09:00 INFO 21544 --- [foodduck] [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2025-03-14T20:47:45.247+09:00 INFO 21544 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2025-03-14T20:47:45.251+09:00 INFO 21544 --- [foodduck] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2025-03-14T20:47:45.252+09:00 INFO 21544 --- [foodduck] [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2025-03-14T20:47:45.262+09:00 INFO 21544 --- [foodduck] [ main] .s.b.a.l.ConditionEvaluationReportLogger :
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-03-14T20:47:45.275+09:00 ERROR 21544 --- [foodduck] [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in org.example.expert.domain.todo.repository.TodoRepositoryQueryDslImpl required a bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' that could not be found.
Action:
Consider defining a bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' in your configuration.
Process finished with exit code 1
콘솔을 보면
Description:
Parameter 0 of constructor in org.example.expert.domain.todo.repository.TodoRepositoryQueryDslImpl required a bean of type 'com.querydsl.jpa.impl.JPAQueryFactory' that could not be found.
JpaQueryFactory를 찾을 수 없다는 것을 확인할 수 있습니다.
즉, TodoRepostiroyQueryDslImpl 클래스에서 의존성 주입을 바등려는 JPAQueryFactory가 빈으로 등록되지 않아서 발생한 것입니다.
(bean of type 'com.querydsl.jpa.impl.JPAQueryFactory
)
쿼리 dsl을 사용할 때, 직접 빈으로 등록해주면 해결되는 것입니다.
@Configuration
public class QueryDslFactory {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
저는 이렇게 추가해줬습니다.
빈으로 관리하도록 설정했고, 엔티티 매니저를 persistenceContext로 주입해서 jpa쿼리팩토리에 연결했습니다. 그 후, jpa쿼리 팩토리를 빈으로 등ㄹ고하면, 쿼리 dsl을 사용하는 리포에선 자동으로 주입 가능해지는 것입니다.
(사실 이렇게만 하면 해결된거임)