1 /*
2  * Copyright 2008-2020 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.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 /**
46  * Set of classes to contain query execution strategies. Depending (mostly) on the return type of a
47  * {@link org.springframework.data.repository.query.QueryMethod} a {@link AbstractStringBasedJpaQuery} can be executed
48  * in various flavors.
49  *
50  * @author Oliver Gierke
51  * @author Thomas Darimont
52  * @author Mark Paluch
53  * @author Christoph Strobl
54  * @author Nicolas Cirigliano
55  * @author Jens Schauder
56  */

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     /**
73      * Executes the given {@link AbstractStringBasedJpaQuery} with the given {@link ParameterBinder}.
74      *
75      * @param query must not be {@literal null}.
76      * @param values must not be {@literal null}.
77      * @return
78      */

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     /**
110      * Method to implement {@link AbstractStringBasedJpaQuery} executions by single enum values.
111      *
112      * @param query
113      * @param values
114      * @return
115      */

116     @Nullable
117     protected abstract Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor);
118
119     /**
120      * Executes the query to return a simple collection of entities.
121      */

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     /**
131      * Executes the query to return a {@link Slice} of entities.
132      *
133      * @author Oliver Gierke
134      * @since 1.6
135      */

136     static class SlicedExecution extends JpaQueryExecution {
137
138         /*
139          * (non-Javadoc)
140          * @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
141          */

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     /**
166      * Executes the {@link AbstractStringBasedJpaQuery} to return a {@link org.springframework.data.domain.Page} of
167      * entities.
168      */

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     /**
189      * Executes a {@link AbstractStringBasedJpaQuery} to return a single entity.
190      */

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     /**
201      * Executes a modifying query such as an update, insert or delete.
202      */

203     static class ModifyingExecution extends JpaQueryExecution {
204
205         private final EntityManager em;
206         private final boolean flush;
207         private final boolean clear;
208
209         /**
210          * Creates an execution that automatically flushes the given {@link EntityManager} before execution and/or clears
211          * the given {@link EntityManager} after execution.
212          *
213          * @param em Must not be {@literal null}.
214          */

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     /**
249      * {@link JpaQueryExecution} removing entities matching the query.
250      *
251      * @author Thomas Darimont
252      * @author Oliver Gierke
253      * @since 1.6
254      */

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         /*
264          * (non-Javadoc)
265          * @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
266          */

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     /**
282      * {@link JpaQueryExecution} performing an exists check on the query.
283      *
284      * @author Mark Paluch
285      * @since 1.11
286      */

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     /**
296      * {@link JpaQueryExecution} executing a stored procedure.
297      *
298      * @author Thomas Darimont
299      * @since 1.6
300      */

301     static class ProcedureExecution extends JpaQueryExecution {
302
303         /*
304          * (non-Javadoc)
305          * @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, java.lang.Object[])
306          */

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     /**
321      * {@link JpaQueryExecution} executing a Java 8 Stream.
322      *
323      * @author Thomas Darimont
324      * @since 1.8
325      */

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         /*
333          * (non-Javadoc)
334          * @see org.springframework.data.jpa.repository.query.JpaQueryExecution#doExecute(org.springframework.data.jpa.repository.query.AbstractJpaQuery, JpaParametersParameterAccessor)
335          */

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             // JPA 2.2 on the classpath
346             if (streamMethod != null) {
347                 return ReflectionUtils.invokeMethod(streamMethod, jpaQuery);
348             }
349
350             // Fall back to legacy stream execution
351             PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(query.getEntityManager());
352             CloseableIterator<Object> iter = persistenceProvider.executeQueryWithResultStream(jpaQuery);
353
354             return StreamUtils.createStreamFromIterator(iter);
355         }
356     }
357
358     /**
359      * Removes the converter being able to convert any object into an {@link Optional} from the given
360      * {@link ConversionService} in case we're running on Java 8.
361      *
362      * @param conversionService must not be {@literal null}.
363      */

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