1
16 package org.springframework.data.jpa.repository.query;
17
18 import java.lang.reflect.Method;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Optional;
22
23 import javax.persistence.EntityManager;
24 import javax.persistence.NoResultException;
25 import javax.persistence.Query;
26 import javax.persistence.StoredProcedureQuery;
27
28 import org.springframework.core.convert.ConversionService;
29 import org.springframework.core.convert.support.ConfigurableConversionService;
30 import org.springframework.core.convert.support.DefaultConversionService;
31 import org.springframework.dao.InvalidDataAccessApiUsageException;
32 import org.springframework.data.domain.Pageable;
33 import org.springframework.data.domain.Slice;
34 import org.springframework.data.domain.SliceImpl;
35 import org.springframework.data.jpa.provider.PersistenceProvider;
36 import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
37 import org.springframework.data.repository.support.PageableExecutionUtils;
38 import org.springframework.data.util.CloseableIterator;
39 import org.springframework.data.util.StreamUtils;
40 import org.springframework.lang.Nullable;
41 import org.springframework.util.Assert;
42 import org.springframework.util.ClassUtils;
43 import org.springframework.util.ReflectionUtils;
44
45
57 public abstract class JpaQueryExecution {
58
59 private static final ConversionService CONVERSION_SERVICE;
60
61 static {
62
63 ConfigurableConversionService conversionService = new DefaultConversionService();
64
65 conversionService.addConverter(JpaResultConverters.BlobToByteArrayConverter.INSTANCE);
66 conversionService.removeConvertible(Collection.class, Object.class);
67 potentiallyRemoveOptionalConverter(conversionService);
68
69 CONVERSION_SERVICE = conversionService;
70 }
71
72
79 @Nullable
80 public Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
81
82 Assert.notNull(query, "AbstractJpaQuery must not be null!");
83 Assert.notNull(accessor, "JpaParametersParameterAccessor must not be null!");
84
85 Object result;
86
87 try {
88 result = doExecute(query, accessor);
89 } catch (NoResultException e) {
90 return null;
91 }
92
93 if (result == null) {
94 return null;
95 }
96
97 JpaQueryMethod queryMethod = query.getQueryMethod();
98 Class<?> requiredType = queryMethod.getReturnType();
99
100 if (void.class.equals(requiredType) || requiredType.isAssignableFrom(result.getClass())) {
101 return result;
102 }
103
104 return CONVERSION_SERVICE.canConvert(result.getClass(), requiredType)
105 ? CONVERSION_SERVICE.convert(result, requiredType)
106 : result;
107 }
108
109
116 @Nullable
117 protected abstract Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor);
118
119
122 static class CollectionExecution extends JpaQueryExecution {
123
124 @Override
125 protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
126 return query.createQuery(accessor).getResultList();
127 }
128 }
129
130
136 static class SlicedExecution extends JpaQueryExecution {
137
138
142 @Override
143 @SuppressWarnings("unchecked")
144 protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
145
146 Pageable pageable = accessor.getPageable();
147 Query createQuery = query.createQuery(accessor);
148
149 int pageSize = 0;
150 if (pageable.isPaged()) {
151
152 pageSize = pageable.getPageSize();
153 createQuery.setMaxResults(pageSize + 1);
154 }
155
156 List<Object> resultList = createQuery.getResultList();
157
158 boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
159
160 return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
161
162 }
163 }
164
165
169 static class PagedExecution extends JpaQueryExecution {
170
171 @Override
172 @SuppressWarnings("unchecked")
173 protected Object doExecute(final AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
174
175 Query query = repositoryQuery.createQuery(accessor);
176
177 return PageableExecutionUtils.getPage(query.getResultList(), accessor.getPageable(),
178 () -> count(repositoryQuery, accessor));
179 }
180
181 private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
182
183 List<?> totals = repositoryQuery.createCountQuery(accessor).getResultList();
184 return (totals.size() == 1 ? CONVERSION_SERVICE.convert(totals.get(0), Long.class) : totals.size());
185 }
186 }
187
188
191 static class SingleEntityExecution extends JpaQueryExecution {
192
193 @Override
194 protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
195
196 return query.createQuery(accessor).getSingleResult();
197 }
198 }
199
200
203 static class ModifyingExecution extends JpaQueryExecution {
204
205 private final EntityManager em;
206 private final boolean flush;
207 private final boolean clear;
208
209
215 public ModifyingExecution(JpaQueryMethod method, EntityManager em) {
216
217 Assert.notNull(em, "The EntityManager must not be null.");
218
219 Class<?> returnType = method.getReturnType();
220
221 boolean isVoid = void.class.equals(returnType) || Void.class.equals(returnType);
222 boolean isInt = int.class.equals(returnType) || Integer.class.equals(returnType);
223
224 Assert.isTrue(isInt || isVoid, "Modifying queries can only use void or int/Integer as return type!");
225
226 this.em = em;
227 this.flush = method.getFlushAutomatically();
228 this.clear = method.getClearAutomatically();
229 }
230
231 @Override
232 protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
233
234 if (flush) {
235 em.flush();
236 }
237
238 int result = query.createQuery(accessor).executeUpdate();
239
240 if (clear) {
241 em.clear();
242 }
243
244 return result;
245 }
246 }
247
248
255 static class DeleteExecution extends JpaQueryExecution {
256
257 private final EntityManager em;
258
259 public DeleteExecution(EntityManager em) {
260 this.em = em;
261 }
262
263
267 @Override
268 protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor accessor) {
269
270 Query query = jpaQuery.createQuery(accessor);
271 List<?> resultList = query.getResultList();
272
273 for (Object o : resultList) {
274 em.remove(o);
275 }
276
277 return jpaQuery.getQueryMethod().isCollectionQuery() ? resultList : resultList.size();
278 }
279 }
280
281
287 static class ExistsExecution extends JpaQueryExecution {
288
289 @Override
290 protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
291 return !query.createQuery(accessor).getResultList().isEmpty();
292 }
293 }
294
295
301 static class ProcedureExecution extends JpaQueryExecution {
302
303
307 @Override
308 protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor accessor) {
309
310 Assert.isInstanceOf(StoredProcedureJpaQuery.class, jpaQuery);
311
312 StoredProcedureJpaQuery storedProcedureJpaQuery = (StoredProcedureJpaQuery) jpaQuery;
313 StoredProcedureQuery storedProcedure = storedProcedureJpaQuery.createQuery(accessor);
314 storedProcedure.execute();
315
316 return storedProcedureJpaQuery.extractOutputValue(storedProcedure);
317 }
318 }
319
320
326 static class StreamExecution extends JpaQueryExecution {
327
328 private static final String NO_SURROUNDING_TRANSACTION = "You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed. Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction.";
329
330 private static Method streamMethod = ReflectionUtils.findMethod(Query.class, "getResultStream");
331
332
336 @Override
337 protected Object doExecute(final AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
338
339 if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive()) {
340 throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
341 }
342
343 Query jpaQuery = query.createQuery(accessor);
344
345
346 if (streamMethod != null) {
347 return ReflectionUtils.invokeMethod(streamMethod, jpaQuery);
348 }
349
350
351 PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(query.getEntityManager());
352 CloseableIterator<Object> iter = persistenceProvider.executeQueryWithResultStream(jpaQuery);
353
354 return StreamUtils.createStreamFromIterator(iter);
355 }
356 }
357
358
364 public static void potentiallyRemoveOptionalConverter(ConfigurableConversionService conversionService) {
365
366 ClassLoader classLoader = JpaQueryExecution.class.getClassLoader();
367
368 if (ClassUtils.isPresent("java.util.Optional", classLoader)) {
369
370 try {
371
372 Class<?> optionalType = ClassUtils.forName("java.util.Optional", classLoader);
373 conversionService.removeConvertible(Object.class, optionalType);
374
375 } catch (ClassNotFoundException | LinkageError o_O) {}
376 }
377 }
378 }
379