1
16 package org.springframework.data.jpa.repository.query;
17
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.stream.Collectors;
25
26 import javax.persistence.EntityManager;
27 import javax.persistence.LockModeType;
28 import javax.persistence.Query;
29 import javax.persistence.QueryHint;
30 import javax.persistence.Tuple;
31 import javax.persistence.TupleElement;
32 import javax.persistence.TypedQuery;
33
34 import org.springframework.core.convert.converter.Converter;
35 import org.springframework.data.jpa.provider.PersistenceProvider;
36 import org.springframework.data.jpa.repository.EntityGraph;
37 import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution;
38 import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
39 import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
40 import org.springframework.data.jpa.repository.query.JpaQueryExecution.ProcedureExecution;
41 import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution;
42 import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExecution;
43 import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution;
44 import org.springframework.data.jpa.util.JpaMetamodel;
45 import org.springframework.data.repository.query.RepositoryQuery;
46 import org.springframework.data.repository.query.ResultProcessor;
47 import org.springframework.data.repository.query.ReturnedType;
48 import org.springframework.data.util.Lazy;
49 import org.springframework.lang.Nullable;
50 import org.springframework.util.Assert;
51
52
63 public abstract class AbstractJpaQuery implements RepositoryQuery {
64
65 private final JpaQueryMethod method;
66 private final EntityManager em;
67 private final JpaMetamodel metamodel;
68 private final PersistenceProvider provider;
69 private final Lazy<JpaQueryExecution> execution;
70
71 final Lazy<ParameterBinder> parameterBinder = new Lazy<>(this::createBinder);
72
73
79 public AbstractJpaQuery(JpaQueryMethod method, EntityManager em) {
80
81 Assert.notNull(method, "JpaQueryMethod must not be null!");
82 Assert.notNull(em, "EntityManager must not be null!");
83
84 this.method = method;
85 this.em = em;
86 this.metamodel = JpaMetamodel.of(em.getMetamodel());
87 this.provider = PersistenceProvider.fromEntityManager(em);
88 this.execution = Lazy.of(() -> {
89
90 if (method.isStreamQuery()) {
91 return new StreamExecution();
92 } else if (method.isProcedureQuery()) {
93 return new ProcedureExecution();
94 } else if (method.isCollectionQuery()) {
95 return new CollectionExecution();
96 } else if (method.isSliceQuery()) {
97 return new SlicedExecution();
98 } else if (method.isPageQuery()) {
99 return new PagedExecution();
100 } else if (method.isModifyingQuery()) {
101 return null;
102 } else {
103 return new SingleEntityExecution();
104 }
105 });
106 }
107
108
112 @Override
113 public JpaQueryMethod getQueryMethod() {
114 return method;
115 }
116
117
122 protected EntityManager getEntityManager() {
123 return em;
124 }
125
126
131 protected JpaMetamodel getMetamodel() {
132 return metamodel;
133 }
134
135
139 @Nullable
140 @Override
141 public Object execute(Object[] parameters) {
142 return doExecute(getExecution(), parameters);
143 }
144
145
150 @Nullable
151 private Object doExecute(JpaQueryExecution execution, Object[] values) {
152
153 JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor(method.getParameters(), values);
154 Object result = execution.execute(this, accessor);
155
156 ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);
157 return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));
158 }
159
160 protected JpaQueryExecution getExecution() {
161
162 JpaQueryExecution execution = this.execution.getNullable();
163
164 if (execution != null) {
165 return execution;
166 }
167
168 if (method.isModifyingQuery()) {
169 return new ModifyingExecution(method, em);
170 } else {
171 return new SingleEntityExecution();
172 }
173 }
174
175
181 protected <T extends Query> T applyHints(T query, JpaQueryMethod method) {
182
183 List<QueryHint> hints = method.getHints();
184
185 if (!hints.isEmpty()) {
186 for (QueryHint hint : hints) {
187 applyQueryHint(query, hint);
188 }
189 }
190
191 return query;
192 }
193
194
200 protected <T extends Query> void applyQueryHint(T query, QueryHint hint) {
201
202 Assert.notNull(query, "Query must not be null!");
203 Assert.notNull(hint, "QueryHint must not be null!");
204
205 query.setHint(hint.name(), hint.value());
206 }
207
208
215 private Query applyLockMode(Query query, JpaQueryMethod method) {
216
217 LockModeType lockModeType = method.getLockModeType();
218 return lockModeType == null ? query : query.setLockMode(lockModeType);
219 }
220
221 protected ParameterBinder createBinder() {
222 return ParameterBinderFactory.createBinder(getQueryMethod().getParameters());
223 }
224
225 protected Query createQuery(JpaParametersParameterAccessor parameters) {
226 return applyLockMode(applyEntityGraphConfiguration(applyHints(doCreateQuery(parameters), method), method), method);
227 }
228
229
237 private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) {
238
239 JpaEntityGraph entityGraph = method.getEntityGraph();
240
241 if (entityGraph != null) {
242 Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(),
243 getQueryMethod().getEntityInformation().getJavaType());
244
245 for (Map.Entry<String, Object> hint : hints.entrySet()) {
246 query.setHint(hint.getKey(), hint.getValue());
247 }
248 }
249
250 return query;
251 }
252
253 protected Query createCountQuery(JpaParametersParameterAccessor values) {
254 Query countQuery = doCreateCountQuery(values);
255 return method.applyHintsToCountQuery() ? applyHints(countQuery, method) : countQuery;
256 }
257
258
264 @Nullable
265 protected Class<?> getTypeToRead(ReturnedType returnedType) {
266
267 if (PersistenceProvider.ECLIPSELINK.equals(provider)) {
268 return null;
269 }
270
271 return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType())
272 ? Tuple.class
273 : null;
274 }
275
276
282 protected abstract Query doCreateQuery(JpaParametersParameterAccessor accessor);
283
284
290 protected abstract Query doCreateCountQuery(JpaParametersParameterAccessor accessor);
291
292 static class TupleConverter implements Converter<Object, Object> {
293
294 private final ReturnedType type;
295
296
301 public TupleConverter(ReturnedType type) {
302
303 Assert.notNull(type, "Returned type must not be null!");
304
305 this.type = type;
306 }
307
308
312 @Override
313 public Object convert(Object source) {
314
315 if (!(source instanceof Tuple)) {
316 return source;
317 }
318
319 Tuple tuple = (Tuple) source;
320 List<TupleElement<?>> elements = tuple.getElements();
321
322 if (elements.size() == 1) {
323
324 Object value = tuple.get(elements.get(0));
325
326 if (type.isInstance(value) || value == null) {
327 return value;
328 }
329 }
330
331 return new TupleBackedMap(tuple);
332 }
333
334
341 private static class TupleBackedMap implements Map<String, Object> {
342
343 private static final String UNMODIFIABLE_MESSAGE = "A TupleBackedMap cannot be modified.";
344
345 private final Tuple tuple;
346
347 TupleBackedMap(Tuple tuple) {
348 this.tuple = tuple;
349 }
350
351 @Override
352 public int size() {
353 return tuple.getElements().size();
354 }
355
356 @Override
357 public boolean isEmpty() {
358 return tuple.getElements().isEmpty();
359 }
360
361
368 @Override
369 public boolean containsKey(Object key) {
370
371 try {
372 tuple.get((String) key);
373 return true;
374 } catch (IllegalArgumentException e) {
375 return false;
376 }
377 }
378
379 @Override
380 public boolean containsValue(Object value) {
381 return Arrays.asList(tuple.toArray()).contains(value);
382 }
383
384
391 @Override
392 @Nullable
393 public Object get(Object key) {
394
395 if (!(key instanceof String)) {
396 return null;
397 }
398
399 try {
400 return tuple.get((String) key);
401 } catch (IllegalArgumentException e) {
402 return null;
403 }
404 }
405
406 @Override
407 public Object put(String key, Object value) {
408 throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE);
409 }
410
411 @Override
412 public Object remove(Object key) {
413 throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE);
414 }
415
416 @Override
417 public void putAll(Map<? extends String, ?> m) {
418 throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE);
419 }
420
421 @Override
422 public void clear() {
423 throw new UnsupportedOperationException(UNMODIFIABLE_MESSAGE);
424 }
425
426 @Override
427 public Set<String> keySet() {
428
429 return tuple.getElements().stream()
430 .map(TupleElement::getAlias)
431 .collect(Collectors.toSet());
432 }
433
434 @Override
435 public Collection<Object> values() {
436 return Arrays.asList(tuple.toArray());
437 }
438
439 @Override
440 public Set<Entry<String, Object>> entrySet() {
441
442 return tuple.getElements().stream()
443 .map(e -> new HashMap.SimpleEntry<String, Object>(e.getAlias(), tuple.get(e)))
444 .collect(Collectors.toSet());
445 }
446 }
447 }
448 }
449