Skip to content

Commit 1663a4a

Browse files
committed
Skip unnamed DTO projection properties.
We now skip unnamed DTO projection properties and issue a warning log to raise awareness. Skipping unnamed (null) properties avoids identification as DTO and only selects properties stemming from named constructor arguments. Add tests for Kotlin data classes using value classes for verification. Closes #3225
1 parent 3a0699b commit 1663a4a

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

src/main/java/org/springframework/data/repository/query/ReturnedType.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import java.util.Map;
2525
import java.util.Set;
2626

27+
import org.apache.commons.logging.Log;
28+
import org.apache.commons.logging.LogFactory;
29+
2730
import org.springframework.data.mapping.Parameter;
2831
import org.springframework.data.mapping.PreferredConstructor;
2932
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
@@ -48,6 +51,8 @@
4851
*/
4952
public abstract class ReturnedType {
5053

54+
private static final Log logger = LogFactory.getLog(ReturnedType.class);
55+
5156
private static final Map<CacheKey, ReturnedType> cache = new ConcurrentReferenceHashMap<>(32);
5257

5358
private final Class<?> domainType;
@@ -296,10 +301,21 @@ private List<String> detectConstructorParameterNames(Class<?> type) {
296301
return Collections.emptyList();
297302
}
298303

299-
List<String> properties = new ArrayList<>(constructor.getConstructor().getParameterCount());
304+
int parameterCount = constructor.getConstructor().getParameterCount();
305+
List<String> properties = new ArrayList<>(parameterCount);
300306

301307
for (Parameter<Object, ?> parameter : constructor.getParameters()) {
302-
properties.add(parameter.getName());
308+
if (parameter.getName() != null) {
309+
properties.add(parameter.getName());
310+
}
311+
}
312+
313+
if (properties.isEmpty() && parameterCount > 0) {
314+
if (logger.isWarnEnabled()) {
315+
logger.warn(("No constructor parameter names discovered. "
316+
+ "Compile the affected code with '-parameters' instead or avoid its introspection: %s")
317+
.formatted(type.getName()));
318+
}
303319
}
304320

305321
return Collections.unmodifiableList(properties);

src/test/java/org/springframework/data/repository/query/ReturnedTypeUnitTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,25 @@ void cachesInstancesBySourceTypes() {
167167
assertThat(left).isSameAs(right);
168168
}
169169

170+
@Test // GH-3225
171+
void detectsKotlinInputProperties() {
172+
173+
var factory = new SpelAwareProxyProjectionFactory();
174+
175+
var returnedType = ReturnedType.of(SomeDataClass.class, Sample.class, factory);
176+
177+
assertThat(returnedType.getInputProperties()).containsExactly("firstname", "lastname");
178+
}
179+
180+
@Test // GH-3225
181+
void detectsKotlinValueClassInputProperties() {
182+
183+
var factory = new SpelAwareProxyProjectionFactory();
184+
185+
var returnedType = ReturnedType.of(SomeDataClassWithValues.class, Sample.class, factory);
186+
assertThat(returnedType.getInputProperties()).containsExactly("email", "firstname", "lastname");
187+
}
188+
170189
private static ReturnedType getReturnedType(String methodName, Class<?>... parameters) throws Exception {
171190
return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType();
172191
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.repository.query
17+
18+
/**
19+
* @author Mark Paluch
20+
*/
21+
data class SomeDataClass(val firstname: String, val lastname: String = "Doe")
22+
23+
@JvmInline
24+
value class Email(val value: String)
25+
26+
data class SomeDataClassWithValues(
27+
val email: Email,
28+
val firstname: String,
29+
val lastname: String = "Doe"
30+
)

0 commit comments

Comments
 (0)