
Spring과 Hibernate를 사용하면서 평범한 JPQL 쿼리를 실행하다가 아래와 같은 황당한 오류를 만났습니다.
org.hibernate.query.QueryTypeMismatchException:
Specified result type [com.mycom.myapp.entity.Phone]
did not match Query selection type [com.mycom.myapp.entity.Phone]
– multiple selections: use Tuple or array
분명히 조회하는 클래스와 쿼리의 결과 타입이 동일한데, Hibernate가 왜 타입 불일치를 주장하는지 이해할 수 없었습니다. 처음엔 JPQL의 문법이나 엔티티 매핑을 의심했지만, 실제 원인은 예상치 못한 것이었습니다.
자바에서는 클래스가 단순히 같은 패키지와 클래스 이름을 가졌다고 해서 같은 클래스로 인식되지 않습니다. 클래스는 클래스 이름뿐만 아니라 "어떤 ClassLoader가 클래스를 로드했는지" 에 따라 정체성이 결정됩니다. 즉, 같은 클래스라도 서로 다른 ClassLoader에서 로드되었다면 JVM은 이를 완전히 별개의 클래스라고 판단합니다.
제가 겪은 문제는 바로 이 ClassLoader 의 정체성 충돌이었습니다.
com.mycom.myapp.entity.Phone 클래스를 로드했습니다.em.createQuery("SELECT p FROM Phone p", Phone.class)에서는 Spring이나 웹 애플리케이션의 ClassLoader를 통해 로드된 다른 Phone 클래스가 사용되었습니다.Phone 클래스가 존재하게 되어 Hibernate는 이를 서로 다른 타입으로 인식하고 QueryTypeMismatchException 예외를 던진 것입니다.Hibernate가 사용하는 ClassLoader와 웹 애플리케이션의 ClassLoader를 일치시키면 됩니다.
@Override
public ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
@Override
public ClassLoader getNewTempClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
이 설정으로 Hibernate가 애플리케이션 코드와 같은 ClassLoader를 사용하게 되어, 클래스 타입 비교가 올바르게 이루어졌습니다.
더 간단한 해결책은 Spring Boot의 자동설정 기능을 사용하는 것입니다. 직접 PersistenceUnitInfo를 구현하지 않고 spring-boot-starter-data-jpa를 활용하면 별도의 ClassLoader 충돌 문제 없이 안정적으로 작동합니다.
Phone 엔티티가 제대로 로딩되고 쿼리 결과가 정상적으로 반환되었습니다.이번 문제의 핵심은 "클래스 이름이 같다고 반드시 동일 클래스가 아니다"라는 자바의 ClassLoader 개념이었습니다. 서로 다른 ClassLoader가 같은 클래스를 로드하면 JVM은 완전히 별개의 클래스로 인식하므로, 예상치 못한 타입 충돌을 유발할 수 있습니다.
Spring과 Hibernate 환경에서 이런 문제가 발생할 경우, ClassLoader 설정을 명시적으로 통일하거나, Spring Boot의 자동설정을 사용하는 것을 권장합니다.