Skip to content

Commit 3af3ed8

Browse files
committed
Updated data files and added new post
1 parent e7be1ee commit 3af3ed8

File tree

4 files changed

+231
-0
lines changed

4 files changed

+231
-0
lines changed

_data/pageMap.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2025-04-03-jpa-projects-error:
2+
type: blog
3+
title: 'Spring Data JPA Projections의 생성자 타입 매칭 오류 해결하기'
4+
summary: 'JPA Projections 사용 시 발생하는 "Missing constructor" 에러의 원인과 해결 방법을 알아봅니다.'
5+
parent: null
6+
url: /blog/2024/03/21/jpa-projects-error
7+
updated: '2024-03-21 15:30:00 +0900'
8+
children: []
19
2018년회고:
210
type: wiki
311
title: '2018년 회고'

_data/tagList.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- '[SpringBoot,'
12
- action
23
- agile
34
- 'airflow,'
@@ -13,6 +14,7 @@
1314
- book
1415
- 'book,'
1516
- codereview
17+
- 'Database,'
1618
- datapipline
1719
- datastructure
1820
- 'db,'
@@ -22,12 +24,14 @@
2224
- 'devops,'
2325
- document
2426
- 'enum,'
27+
- 'ErrorHandling,'
2528
- 'flutter,'
2629
- 'gc,'
2730
- git
2831
- gradle
2932
- haproxy
3033
- 'hbase,nosql'
34+
- 'Hibernate,'
3135
- http
3236
- in
3337
- infra
@@ -36,7 +40,9 @@
3640
- java
3741
- 'java,'
3842
- 'Java,'
43+
- 'Java]'
3944
- jpa
45+
- 'JPA,'
4046
- 'jpa,'
4147
- 'json,'
4248
- junit

_data/tagMap.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
회고:
22
- {fileName: 2018년회고}
3+
'[SpringBoot,':
4+
- {fileName: 2025-04-03-jpa-projects-error}
5+
'JPA,':
6+
- {fileName: 2025-04-03-jpa-projects-error}
7+
'Hibernate,':
8+
- {fileName: 2025-04-03-jpa-projects-error}
9+
'Database,':
10+
- {fileName: 2025-04-03-jpa-projects-error}
11+
'ErrorHandling,':
12+
- {fileName: 2025-04-03-jpa-projects-error}
13+
'Java]':
14+
- {fileName: 2025-04-03-jpa-projects-error}
315
'airflow,':
416
- {fileName: Airflow}
517
datapipline:
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
layout : post
3+
title : Spring Data JPA Projections의 생성자 타입 매칭 오류 해결하기
4+
summary : JPA Projections 사용 시 발생하는 "Missing constructor" 에러의 원인과 해결 방법을 알아봅니다.
5+
date : 2024-03-21 15:30:00 +0900
6+
tags : [SpringBoot, JPA, Hibernate, Database, ErrorHandling, Java]
7+
toc : true
8+
comment : true
9+
public : true
10+
---
11+
12+
* TOC
13+
{:toc}
14+
15+
# Spring Data JPA Projections의 생성자 타입 매칭 오류 해결하기
16+
17+
## 자주 발생하는 에러 메시지
18+
19+
```
20+
org.hibernate.query.SemanticException: Missing constructor for type 'com.example.SimilarBrandDto' [
21+
SELECT new com.example.SimilarBrandDto(
22+
b.id,
23+
b.name,
24+
SIMILARITY(b.name, :brandName)
25+
)
26+
FROM Brand b
27+
]
28+
```
29+
30+
이 에러는 크게 두 가지 상황에서 발생합니다:
31+
1. 생성자가 없는 경우
32+
2. **생성자의 파라미터 타입이 쿼리 결과와 일치하지 않는 경우**
33+
34+
## 1. 타입 불일치로 인한 Constructor 에러
35+
36+
### 1.1. 흔한 실수 사례
37+
```java
38+
// ❌ 실패하는 케이스
39+
public record SimilarityDto(
40+
UUID id,
41+
String name,
42+
Double similarity // PostgreSQL의 similarity()는 numeric 타입 반환
43+
) {}
44+
```
45+
46+
```java
47+
// 이 쿼리는 다음 에러를 발생시킵니다
48+
@Query("""
49+
SELECT new com.example.SimilarityDto(
50+
b.id,
51+
b.name,
52+
SIMILARITY(b.name, :keyword)
53+
) FROM Brand b
54+
""")
55+
List<SimilarityDto> findSimilar(@Param("keyword") String keyword);
56+
57+
/*
58+
org.hibernate.query.SemanticException: Missing constructor for type 'com.example.SimilarityDto'
59+
Caused by: java.lang.IllegalArgumentException: Could not find matching constructor
60+
*/
61+
```
62+
63+
### 1.2. 해결 방법
64+
```java
65+
// ✅ 성공하는 케이스
66+
public record SimilarityDto(
67+
UUID id,
68+
String name,
69+
Object similarity // Object로 받아서 변환
70+
) {
71+
public Double getSimilarity() {
72+
return ((Number) similarity).doubleValue();
73+
}
74+
}
75+
```
76+
77+
## 2. 데이터베이스 함수별 반환 타입과 자주 발생하는 에러
78+
79+
### 2.1. COUNT 함수
80+
```java
81+
// ❌ 실패 케이스
82+
public record CountDto(Integer count) {} // Integer 대신 Long 사용해야 함
83+
84+
/*
85+
org.hibernate.query.SemanticException: Missing constructor for type 'CountDto'
86+
Caused by: java.lang.IllegalArgumentException: No matching constructor
87+
*/
88+
```
89+
90+
```java
91+
// ✅ 성공 케이스
92+
public record CountDto(Long count) {}
93+
```
94+
95+
### 2.2. AVG 함수
96+
```java
97+
// ❌ 실패 케이스
98+
public record AvgDto(Float average) {} // Float 대신 Double 사용해야 함
99+
100+
/*
101+
org.hibernate.query.SemanticException: Missing constructor for type 'AvgDto'
102+
Caused by: java.lang.IllegalArgumentException: No suitable constructor found
103+
*/
104+
```
105+
106+
```java
107+
// ✅ 성공 케이스
108+
public record AvgDto(Double average) {}
109+
```
110+
111+
## 3. 계산된 필드의 타입 매칭
112+
113+
```java
114+
// ❌ 실패 케이스
115+
public record PriceDto(
116+
Long id,
117+
Integer calculatedPrice // 계산 결과는 보통 Double
118+
) {}
119+
120+
@Query("""
121+
SELECT new com.example.PriceDto(
122+
p.id,
123+
p.price * (1 - p.discount)
124+
) FROM Product p
125+
""")
126+
127+
/*
128+
org.hibernate.query.SemanticException: Missing constructor for type 'PriceDto'
129+
Caused by: java.lang.IllegalArgumentException: Parameter type mismatch
130+
*/
131+
```
132+
133+
```java
134+
// ✅ 성공 케이스 1: 명시적 캐스팅
135+
@Query("""
136+
SELECT new com.example.PriceDto(
137+
p.id,
138+
CAST(p.price * (1 - p.discount) AS int)
139+
) FROM Product p
140+
""")
141+
142+
// ✅ 성공 케이스 2: Object로 받기
143+
public record PriceDto(
144+
Long id,
145+
Object calculatedPrice
146+
) {
147+
public Integer getCalculatedPrice() {
148+
return ((Number) calculatedPrice).intValue();
149+
}
150+
}
151+
```
152+
153+
## 4. 해결 전략
154+
155+
### 4.1. 디버깅을 위한 SQL 로그 활성화
156+
```properties
157+
# application.properties
158+
spring.jpa.show-sql=true
159+
spring.jpa.properties.hibernate.format_sql=true
160+
```
161+
162+
### 4.2. 안전한 타입 변환 유틸리티
163+
```java
164+
public class ProjectionUtils {
165+
public static Double toDouble(Object value) {
166+
if (value == null) return null;
167+
if (value instanceof Number) {
168+
return ((Number) value).doubleValue();
169+
}
170+
throw new IllegalArgumentException(
171+
String.format("Cannot convert %s to Double: %s",
172+
value.getClass(), value)
173+
);
174+
}
175+
}
176+
```
177+
178+
### 4.3. Native Query 사용
179+
```java
180+
@Query(
181+
value = """
182+
SELECT
183+
id,
184+
name,
185+
CAST(SIMILARITY(name, :keyword) AS float8) as similarity
186+
FROM brands
187+
""",
188+
nativeQuery = true
189+
)
190+
List<SimilarityProjection> findSimilar(@Param("keyword") String keyword);
191+
```
192+
193+
## 결론
194+
195+
Spring Data JPA Projections에서 생성자 타입 매칭 오류를 해결하기 위한 핵심 포인트:
196+
197+
1. 데이터베이스 함수의 실제 반환 타입 확인
198+
2. 에러 메시지를 통한 정확한 원인 파악
199+
3. `Object` 또는 `Number`를 사용한 유연한 타입 처리
200+
4. 필요한 경우 명시적 타입 캐스팅 사용
201+
202+
이러한 가이드라인을 따르면 "Missing constructor" 에러를 효과적으로 해결하고 안정적인 Projection을 구현할 수 있습니다.
203+
204+
#SpringBoot #JPA #Hibernate #DatabaseProjection #ErrorHandling #JavaTips
205+

0 commit comments

Comments
 (0)