Spring Data JPA Projections의 생성자 타입 매칭 오류 해결하기
2025.04.03
JPA Projections 사용 시 발생하는 "Missing constructor" 에러의 원인과 해결 방법을 알아봅니다.
Spring Data JPA Projections의 생성자 타입 매칭 오류 해결하기
자주 발생하는 에러 메시지
org.hibernate.query.SemanticException: Missing constructor for type 'com.example.SimilarBrandDto' [
SELECT new com.example.SimilarBrandDto(
b.id,
b.name,
SIMILARITY(b.name, :brandName)
)
FROM Brand b
]
이 에러는 크게 두 가지 상황에서 발생합니다:
- 생성자가 없는 경우
- 생성자의 파라미터 타입이 쿼리 결과와 일치하지 않는 경우
1. 타입 불일치로 인한 Constructor 에러
1.1. 흔한 실수 사례
// ❌ 실패하는 케이스
public record SimilarityDto(
UUID id,
String name,
Double similarity // PostgreSQL의 similarity()는 numeric 타입 반환
) {}
// 이 쿼리는 다음 에러를 발생시킵니다
@Query("""
SELECT new com.example.SimilarityDto(
b.id,
b.name,
SIMILARITY(b.name, :keyword)
) FROM Brand b
""")
List<SimilarityDto> findSimilar(@Param("keyword") String keyword);
/*
org.hibernate.query.SemanticException: Missing constructor for type 'com.example.SimilarityDto'
Caused by: java.lang.IllegalArgumentException: Could not find matching constructor
*/
1.2. 해결 방법
// ✅ 성공하는 케이스
public record SimilarityDto(
UUID id,
String name,
Object similarity // Object로 받아서 변환
) {
public Double getSimilarity() {
return ((Number) similarity).doubleValue();
}
}
2. 데이터베이스 함수별 반환 타입과 자주 발생하는 에러
2.1. COUNT 함수
// ❌ 실패 케이스
public record CountDto(Integer count) {} // Integer 대신 Long 사용해야 함
/*
org.hibernate.query.SemanticException: Missing constructor for type 'CountDto'
Caused by: java.lang.IllegalArgumentException: No matching constructor
*/
// ✅ 성공 케이스
public record CountDto(Long count) {}
2.2. AVG 함수
// ❌ 실패 케이스
public record AvgDto(Float average) {} // Float 대신 Double 사용해야 함
/*
org.hibernate.query.SemanticException: Missing constructor for type 'AvgDto'
Caused by: java.lang.IllegalArgumentException: No suitable constructor found
*/
// ✅ 성공 케이스
public record AvgDto(Double average) {}
3. 계산된 필드의 타입 매칭
// ❌ 실패 케이스
public record PriceDto(
Long id,
Integer calculatedPrice // 계산 결과는 보통 Double
) {}
@Query("""
SELECT new com.example.PriceDto(
p.id,
p.price * (1 - p.discount)
) FROM Product p
""")
/*
org.hibernate.query.SemanticException: Missing constructor for type 'PriceDto'
Caused by: java.lang.IllegalArgumentException: Parameter type mismatch
*/
// ✅ 성공 케이스 1: 명시적 캐스팅
@Query("""
SELECT new com.example.PriceDto(
p.id,
CAST(p.price * (1 - p.discount) AS int)
) FROM Product p
""")
// ✅ 성공 케이스 2: Object로 받기
public record PriceDto(
Long id,
Object calculatedPrice
) {
public Integer getCalculatedPrice() {
return ((Number) calculatedPrice).intValue();
}
}
4. 해결 전략
4.1. 디버깅을 위한 SQL 로그 활성화
# application.properties
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
4.2. 안전한 타입 변환 유틸리티
public class ProjectionUtils {
public static Double toDouble(Object value) {
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
throw new IllegalArgumentException(
String.format("Cannot convert %s to Double: %s",
value.getClass(), value)
);
}
}
4.3. Native Query 사용
@Query(
value = """
SELECT
id,
name,
CAST(SIMILARITY(name, :keyword) AS float8) as similarity
FROM brands
""",
nativeQuery = true
)
List<SimilarityProjection> findSimilar(@Param("keyword") String keyword);
결론
Spring Data JPA Projections에서 생성자 타입 매칭 오류를 해결하기 위한 핵심 포인트:
- 데이터베이스 함수의 실제 반환 타입 확인
- 에러 메시지를 통한 정확한 원인 파악
Object
또는Number
를 사용한 유연한 타입 처리- 필요한 경우 명시적 타입 캐스팅 사용
이러한 가이드라인을 따르면 “Missing constructor” 에러를 효과적으로 해결하고 안정적인 Projection을 구현할 수 있습니다.
#SpringBoot #JPA #Hibernate #DatabaseProjection #ErrorHandling #JavaTips