Skip to content

Commit 63c0099

Browse files
committed
DATAJPA-984 - Improved translation of one-element tuples in projections.
If a query returns a one-element tuple and that single element returned is type-compatible to the type to be returned we return it as is. Previously, we insisted on creating a Map from the tuple (which usually works for projection interfaces). This is needed to support custom simple types (i.e. types that the JPA provider can convert itself but which are not exposed through the metamodel - usually types managed through JPA AttributeConverters or provider specific conversion mechanisms).
1 parent a5f4136 commit 63c0099

File tree

2 files changed

+103
-4
lines changed

2 files changed

+103
-4
lines changed

src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.repository.query;
1717

1818
import java.util.HashMap;
19+
import java.util.List;
1920
import java.util.Map;
2021

2122
import javax.persistence.EntityManager;
@@ -39,6 +40,7 @@
3940
import org.springframework.data.repository.query.ParametersParameterAccessor;
4041
import org.springframework.data.repository.query.RepositoryQuery;
4142
import org.springframework.data.repository.query.ResultProcessor;
43+
import org.springframework.data.repository.query.ReturnedType;
4244
import org.springframework.util.Assert;
4345

4446
/**
@@ -116,7 +118,7 @@ private Object doExecute(JpaQueryExecution execution, Object[] values) {
116118
ParametersParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), values);
117119
ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);
118120

119-
return withDynamicProjection.processResult(result, TupleConverter.INSTANCE);
121+
return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));
120122
}
121123

122124
protected JpaQueryExecution getExecution() {
@@ -232,9 +234,21 @@ protected Query createCountQuery(Object[] values) {
232234
*/
233235
protected abstract Query doCreateCountQuery(Object[] values);
234236

235-
private static enum TupleConverter implements Converter<Object, Object> {
237+
static class TupleConverter implements Converter<Object, Object> {
236238

237-
INSTANCE;
239+
private final ReturnedType type;
240+
241+
/**
242+
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
243+
*
244+
* @param type must not be {@literal null}.
245+
*/
246+
public TupleConverter(ReturnedType type) {
247+
248+
Assert.notNull(type, "Returned type must not be null!");
249+
250+
this.type = type;
251+
}
238252

239253
/*
240254
* (non-Javadoc)
@@ -249,8 +263,18 @@ public Object convert(Object source) {
249263

250264
Tuple tuple = (Tuple) source;
251265
Map<String, Object> result = new HashMap<String, Object>();
266+
List<TupleElement<?>> elements = tuple.getElements();
267+
268+
if (elements.size() == 1) {
269+
270+
Object value = tuple.get(elements.get(0));
271+
272+
if (type.isInstance(value)) {
273+
return value;
274+
}
275+
}
252276

253-
for (TupleElement<?> element : tuple.getElements()) {
277+
for (TupleElement<?> element : elements) {
254278

255279
String alias = element.getAlias();
256280

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2016 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+
* http://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.jpa.repository.query;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
import static org.mockito.Mockito.*;
21+
22+
import java.util.Arrays;
23+
24+
import javax.persistence.Tuple;
25+
import javax.persistence.TupleElement;
26+
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.mockito.Mock;
30+
import org.mockito.runners.MockitoJUnitRunner;
31+
import org.springframework.data.jpa.repository.query.AbstractJpaQuery.TupleConverter;
32+
import org.springframework.data.projection.ProjectionFactory;
33+
import org.springframework.data.repository.CrudRepository;
34+
import org.springframework.data.repository.core.RepositoryMetadata;
35+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
36+
import org.springframework.data.repository.query.QueryMethod;
37+
import org.springframework.data.repository.query.ReturnedType;
38+
39+
/**
40+
* Unit tests for {@link TupleConverter}.
41+
*
42+
* @author Oliver Gierke
43+
* @soundtrack James Bay - Let it go (Chaos and the Calm)
44+
*/
45+
@RunWith(MockitoJUnitRunner.class)
46+
public class TupleConverterUnitTests {
47+
48+
@Mock Tuple tuple;
49+
@Mock TupleElement<String> element;
50+
@Mock ProjectionFactory factory;
51+
52+
/**
53+
* @see DATAJPA-984
54+
*/
55+
@Test
56+
@SuppressWarnings("unchecked")
57+
public void returnsSingleTupleElementIfItMatchesExpectedType() throws Exception {
58+
59+
RepositoryMetadata metadata = new DefaultRepositoryMetadata(SampleRepository.class);
60+
QueryMethod method = new QueryMethod(SampleRepository.class.getMethod("someMethod"), metadata, factory);
61+
ReturnedType type = method.getResultProcessor().getReturnedType();
62+
63+
doReturn(element).when(tuple).get(0);
64+
doReturn(Arrays.asList(element)).when(tuple).getElements();
65+
doReturn("Foo").when(tuple).get(element);
66+
67+
TupleConverter converter = new TupleConverter(type);
68+
69+
assertThat(converter.convert(tuple), is((Object) "Foo"));
70+
}
71+
72+
static interface SampleRepository extends CrudRepository<Object, Long> {
73+
String someMethod();
74+
}
75+
}

0 commit comments

Comments
 (0)