📢 요약
여러분이 위와 같은 에러를 만난다면 여러가지 경우의 수를 확인해보셔야합니다.
1️⃣ Gradle 설정확인
Gradle 설정 파일이 제대로 구성되었는지 확인해주셔야합니다.
2️⃣ Q 클래스 생성 확인
Q클래스가 올바르게 생성이 되었는지 확인하셔야합니다.
(build/generated/sources/annotationProcessor/java/main 디렉터리에 생성되어있어야 합니다.)
3️⃣ query를 올바르게 작성하였는지 확인해주세요.
📖 오류 회고
1️⃣ Gradle 설정확인
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
implementation 'com.querydsl:querydsl-core:5.1.0'
implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
implementation "jakarta.annotation:jakarta.annotation-api"
implementation "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
clean {
delete file('src/main/generated')
}
- implementation 'com.querydsl:querydsl-core:5.1.0'
- QueryDSL의 핵심(core) 라이브러리를 포함합니다. QueryDSL의 기본 기능을 제공합니다.
- implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
- QueryDSL의 JPA(Jakarta Persistence API) 모듈을 포함합니다. JPA를 사용할 때 QueryDSL을 사용할 수 있게 합니다.
- annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta"
- QueryDSL에서 제공하는 어노테이션 프로세서 라이브러리입니다. 이 라이브러리는 컴파일 시에 필요한 QueryDSL 타입을 생성합니다.
- annotationProcessor "jakarta.annotation:jakarta.annotation-api"
- Jakarta EE에서 제공하는 어노테이션 API를 포함합니다. 이는 어노테이션 프로세서가 Jakarta EE 어노테이션을 처리할 수 있도록 합니다.
- annotationProcessor "jakarta.persistence:jakarta.persistence-api"
- Jakarta Persistence API 라이브러리입니다. JPA를 사용하는 프로젝트에서 어노테이션 프로세서가 이 API를 참조할 수 있게 합니다.
- implementation 'jakarta.annotation:jakarta.annotation-api'
- implementation 'jakarta.persistence:jakarta.persistence-api'
- 런타임에 이 API들을 사용할 수 있도록 해줍니다. 이는 런타임에도 어노테이션 API가 필요할 경우 유용합니다.
- delete file('src/main/generated')
- clean 작업이 실행될 때 src/main/generated 디렉토리를 삭제하도록 설정합니다. 이는 일반적으로 QueryDSL의 어노테이션 프로세서가 생성한 소스 파일들이 저장되는 디렉토리입니다.
❗️ SemanticException
먼저 SemanticExcpetion 부터 알아봅시다.
SemanticException은 hibernate-orm 에서 찾아보실 수 있습니다.
갑자기 hibernate ? 라고 하실 수도 있는데 QueryDSL에는 꼭 필요한 라이브러리입니다.
❓ QueryDSL이 Hibernate ORM을 사용하는 이유
QueryDSL이 Hibernate ORM을 사용하는 이유
Hibernate ORM은 JPA 구현체 중 하나로, 자바 객체를 관계형 데이터베이스 테이블에 매핑하는 역할을 합니다.
1. JPA 구현체로서의 Hibernate
QueryDSL은 JPA와 함께 동작할 수 있도록 설계되었습니다. JPA는 Java 애플리케이션에서 데이터베이스와 상호작용하기 위한 표준 API를 제공하며, Hibernate는 가장 널리 사용되는 JPA 구현체 중 하나입니다. 따라서, QueryDSL은 Hibernate를 통해 JPA와 연동하여 ORM 기능을 활용합니다.
2. 광범위한 기능 지원
Hibernate는 강력하고 풍분한 기능을 제공하는 ORM 프레임워크로, 데이터베이스와의 복잡한 상호작용을 단순화하고 효율적으로 처리할 수 있습니다. QueryDSL은 이러한 기능을 활용하여 복잡한 쿼리를 간단하고 안전하게 작성할 수 있습니다.
특히, Hibernate의 캐싱, 트랜잭션 관리, 성능 최적화 기능을 사용할 수 있습니다.
3. 통합과 호환성
QueryDSL은 Hibernate와의 통합을 통해 JPA 표준으을 준수하면서도, Hibernate의 특정 기능을 활용할 수 있는 유연성을 제공합니다. 예를 들어, Hibernate의 고유한 기능이나 최적화 기법을 사용할 수 있으며, 이는 다른 JPA 구현체에서는 제공되지 않을 수 있습니다.
> QueryDSL과 Hibernate를 함께 사용하는 간단한 예제입니다.
public class QueryDSLExample {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager();
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
// 예제: 특정 조건에 맞는 사용자 조회
List<User> users = queryFactory.selectFrom(user)
.where(user.age.gt(18))
.fetch();
for (User u : users) {
System.out.println(u.getName());
}
em.close();
emf.close();
}
}
이 예제에서 JPAQueryFactory를 사용하여 QueryDSL 쿼리를 작성하고 Hibernate를 통해 데이터베이스와 상호작용합니다.
QueryDSL은 JPA 표준 API를 사용하면서도 타입 안전성과 동적 쿼리 생성의 장점을 제공합니다.
여기서 SemanticExcpetion 상속을 타고 끝까지 올라가보면 아래와 같이 'jakarta.persistence.PersistenceException'을 상속하는 것을 알 수 있습니다.
PersistenceException는 JPA 사용 중에 발생할 수 있는 여러 가지 예외의 상위 클래스입니다. 이 예외는 데이터베이스와의 상호작용에서 발생할 수 있는 다양한 문제를 포괄합니다.
📌 주요 하위 클래스와 예외 상황
- EntityExistsException
- 발생 상황: 데이터베이스에 이미 존재하는 엔티티를 저장하려고 할 때 발생합니다.
- 예제: 동일한 ID를 가진 엔티티를 persist() 메서드로 저장하려고 할 때.
- EntityNotFoundException
- 발생 상황: find() 메서드로 조회하려는 엔티티가 데이터베이스에 존재하지 않을 때 발생합니다.
- 예제: find 메서드를 호출하여 특정 ID의 엔티티를 찾으려고 했지만 데이터베이스에 존재하지 않는 경우.
- OptimisticLockException
- 발생 상황: 낙관적 잠금 충돌이 발생했을 때 발생합니다. 이는 동시에 여러 트랜잭션이 같은 엔티티를 수정하려고 할 때 발생할 수 있습니다.
- 예제: 두 개의 트랜잭션이 동일한 엔티티를 수정하고 각각의 변경사항을 커밋하려고 할 때.
- RollbackException
- 발생 상황: 트랜잭션을 커밋하려고 할 때 오류가 발생하여 롤백되었을 때 발생합니다.
- 예제: 데이터베이스 제약 조건 위반 등으로 인해 트랜잭션 커밋이 실패한 경우
- TransactionRequiredException
- 발생 상황: 트랜잭션이 필요한 작업을 트랜잭션 없이 수행하려고 할 때 발생합니다.
- 예제: 트랜잭션이 없는 상태에서 엔티티를 저장 또는 삭제하려고 할 때.
- NoResultException
- 발생 상황: 쿼리 실행 결과가 없을 때 발생합니다.
- 예제: 단일 결과를 기대하는 쿼리가 아무런 결과도 반환하지 않았을 때
- NonUniqueResultException
- 발생 상황: 단일 결과를 기대하는 쿼리가 여러 개의 결과를 반환했을 때 발생합니다.
- 예제: 단일 결과 쿼리가 두 개 이상의 결과를 반환한 경우.
- PersistenceUnitLoadingException
- 발생 상황: 영속성 유닛을 로드하는 동안 문제가 발생했을 때 발생합니다.
- 예제: 잘못된 설정이나 파일 누락으로 인해 영속성 유닛이 로드되지 않는 경우.
이렇게 다양한 경우가 발생할 수 있고 PersistenceException은 기본적으로 RuntimeException의 상속을 받기에 런타임 에러라서 코드를 작성하다 실수를 해도 컴파일 타임에는 알기 힘듭니다. 즉 직접 코드를 실행해보고 SemanticException가 발생하면 앞서 말했던 순서대로 확인을 해주셔야합니다.
저같은 경우 "could not interpret path expression"에러 메시지에 꽂혀가지고 8시간동안 삽질을 했습니다.
앞서 제가 제시드렸던 1. Gradle 설정확인, 2. Q 클래스 생성 확인까지만 했기에 8시간을 확인했습니다.
등잔밑이 어둡죠. 제 query가 잘 못 되어있다는 생각은 하지 못했습니다.
그냥 저 에러메시지만 보고 아 세팅 또는 경로가 잘못되어서 에러가 뜨는 구나 싶었습니다.
그래서 공식문서 가서 혹시 hibernate에 오류가 있는건 아닐까 찾아보기도 하고 실제로 6.4.x 버전에 문제가 있다고 하여 여러버전을 바꾸기도 하고 세팅을 다시 해보기도 하고 Q클래스 생성이 올바르게 되었는지 하나하나 확인도 하고 테스트 클래스를 여러개 만들어가면 경우의 수를 지우다가 결국에는 새벽 6시까지 해결 못하고 다음날 일어나자마자 또 해보고 하다고 점심 먹으면서 생각해보았습니다.
"에러메시지 생각해보지 말고 정리해보자 queryDSL 설정도 올바르게 했고 Q 클래스도 정상 경로에 생성되었으니 여기서 문제가 없다고 가정하면 내가 지금까지 확인하지 않았던 발밑인 쿼리가 잘못된게 아닐까? 하고 바로 쿼리전부 지우고 다시 작성했는데 정상적으로 동작했습니다.
가장 큰 원인은 QueryDSL에 대해 정확하게 동작 원리를 알지 못하고 사용해서 이고 너무 에러 메시지에만 신경을 섰기에 주변 상황을 보지 못한 저의 잘못입니다.
이번 에러를 계기로 동일한 에러가 나온다면 몇배는 더 빠르게 해결 가능할 것 같습니다.
📚 Reference
'정보' 카테고리의 다른 글
CI CD 세팅 (1) | 2024.06.02 |
---|---|
JIRA 이슈번호 자동으로 커밋메시지에 넣기 (0) | 2024.05.15 |
[에러 해결] cannot resolve class or package 'cj' (0) | 2024.05.03 |
[Effective java] 🚀 item 13. clone 재정의는 주의해서 진행해라 (0) | 2024.04.17 |
[Effective java] 🚀 item 12. 항상 toString을 재정의하라 Always override toString (0) | 2024.03.31 |