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.support;
17
18 import static org.springframework.data.jpa.repository.query.QueryUtils.*;
19
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27
28 import javax.persistence.EntityManager;
29 import javax.persistence.LockModeType;
30 import javax.persistence.NoResultException;
31 import javax.persistence.Parameter;
32 import javax.persistence.Query;
33 import javax.persistence.TypedQuery;
34 import javax.persistence.criteria.CriteriaBuilder;
35 import javax.persistence.criteria.CriteriaQuery;
36 import javax.persistence.criteria.Order;
37 import javax.persistence.criteria.ParameterExpression;
38 import javax.persistence.criteria.Path;
39 import javax.persistence.criteria.Predicate;
40 import javax.persistence.criteria.Root;
41
42 import org.springframework.dao.EmptyResultDataAccessException;
43 import org.springframework.data.domain.Example;
44 import org.springframework.data.domain.Page;
45 import org.springframework.data.domain.PageImpl;
46 import org.springframework.data.domain.Pageable;
47 import org.springframework.data.domain.Sort;
48 import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
49 import org.springframework.data.jpa.domain.Specification;
50 import org.springframework.data.jpa.provider.PersistenceProvider;
51 import org.springframework.data.jpa.repository.EntityGraph;
52 import org.springframework.data.jpa.repository.query.EscapeCharacter;
53 import org.springframework.data.jpa.repository.query.QueryUtils;
54 import org.springframework.data.jpa.repository.support.QueryHints.NoHints;
55 import org.springframework.data.repository.support.PageableExecutionUtils;
56 import org.springframework.data.util.ProxyUtils;
57 import org.springframework.lang.Nullable;
58 import org.springframework.stereotype.Repository;
59 import org.springframework.transaction.annotation.Transactional;
60 import org.springframework.util.Assert;
61
62 /**
63  * Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer
64  * you a more sophisticated interface than the plain {@link EntityManager} .
65  *
66  * @author Oliver Gierke
67  * @author Eberhard Wolff
68  * @author Thomas Darimont
69  * @author Mark Paluch
70  * @author Christoph Strobl
71  * @author Stefan Fussenegger
72  * @author Jens Schauder
73  * @author David Madden
74  * @author Moritz Becker
75  * @param <T> the type of the entity to handle
76  * @param <ID> the type of the entity's identifier
77  */

78 @Repository
79 @Transactional(readOnly = true)
80 public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
81
82     private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!";
83
84     private final JpaEntityInformation<T, ?> entityInformation;
85     private final EntityManager em;
86     private final PersistenceProvider provider;
87
88     private @Nullable CrudMethodMetadata metadata;
89     private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
90
91     private static <T> Collection<T> toCollection(Iterable<T> ts) {
92
93         if (ts instanceof Collection) {
94             return (Collection<T>) ts;
95         }
96
97         List<T> tCollection = new ArrayList<T>();
98         for (T t : ts) {
99             tCollection.add(t);
100         }
101         return tCollection;
102     }
103
104     /**
105      * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}.
106      *
107      * @param entityInformation must not be {@literal null}.
108      * @param entityManager must not be {@literal null}.
109      */

110     public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
111
112         Assert.notNull(entityInformation, "JpaEntityInformation must not be null!");
113         Assert.notNull(entityManager, "EntityManager must not be null!");
114
115         this.entityInformation = entityInformation;
116         this.em = entityManager;
117         this.provider = PersistenceProvider.fromEntityManager(entityManager);
118     }
119
120     /**
121      * Creates a new {@link SimpleJpaRepository} to manage objects of the given domain type.
122      *
123      * @param domainClass must not be {@literal null}.
124      * @param em must not be {@literal null}.
125      */

126     public SimpleJpaRepository(Class<T> domainClass, EntityManager em) {
127         this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
128     }
129
130     /**
131      * Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be
132      * applied to queries.
133      *
134      * @param crudMethodMetadata
135      */

136     @Override
137     public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) {
138         this.metadata = crudMethodMetadata;
139     }
140
141     @Override
142     public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
143         this.escapeCharacter = escapeCharacter;
144     }
145
146     @Nullable
147     protected CrudMethodMetadata getRepositoryMethodMetadata() {
148         return metadata;
149     }
150
151     protected Class<T> getDomainClass() {
152         return entityInformation.getJavaType();
153     }
154
155     private String getDeleteAllQueryString() {
156         return getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName());
157     }
158
159     private String getCountQueryString() {
160
161         String countQuery = String.format(COUNT_QUERY_STRING, provider.getCountQueryPlaceholder(), "%s");
162         return getQueryString(countQuery, entityInformation.getEntityName());
163     }
164
165     /*
166      * (non-Javadoc)
167      * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
168      */

169     @Transactional
170     @Override
171     public void deleteById(ID id) {
172
173         Assert.notNull(id, ID_MUST_NOT_BE_NULL);
174
175         delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
176                 String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
177     }
178
179     /*
180      * (non-Javadoc)
181      * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
182      */

183     @Override
184     @Transactional
185     @SuppressWarnings("unchecked")
186     public void delete(T entity) {
187
188         Assert.notNull(entity, "Entity must not be null!");
189
190         if (entityInformation.isNew(entity)) {
191             return;
192         }
193
194         Class<?> type = ProxyUtils.getUserClass(entity);
195
196         T existing = (T) em.find(type, entityInformation.getId(entity));
197
198         // if the entity to be deleted doesn't exist, delete is a NOOP
199         if (existing == null) {
200             return;
201         }
202
203         em.remove(em.contains(entity) ? entity : em.merge(entity));
204     }
205
206     /*
207      * (non-Javadoc)
208      * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
209      */

210     @Transactional
211     @Override
212     public void deleteAll(Iterable<? extends T> entities) {
213
214         Assert.notNull(entities, "Entities must not be null!");
215
216         for (T entity : entities) {
217             delete(entity);
218         }
219     }
220
221     /*
222      * (non-Javadoc)
223      * @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable)
224      */

225     @Transactional
226     @Override
227     public void deleteInBatch(Iterable<T> entities) {
228
229         Assert.notNull(entities, "Entities must not be null!");
230
231         if (!entities.iterator().hasNext()) {
232             return;
233         }
234
235         applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em)
236                 .executeUpdate();
237     }
238
239     /*
240      * (non-Javadoc)
241      * @see org.springframework.data.repository.Repository#deleteAll()
242      */

243     @Transactional
244     @Override
245     public void deleteAll() {
246
247         for (T element : findAll()) {
248             delete(element);
249         }
250     }
251
252     /*
253      * (non-Javadoc)
254      * @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch()
255      */

256     @Transactional
257     @Override
258     public void deleteAllInBatch() {
259         em.createQuery(getDeleteAllQueryString()).executeUpdate();
260     }
261
262     /*
263      * (non-Javadoc)
264      * @see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable)
265      */

266     @Override
267     public Optional<T> findById(ID id) {
268
269         Assert.notNull(id, ID_MUST_NOT_BE_NULL);
270
271         Class<T> domainType = getDomainClass();
272
273         if (metadata == null) {
274             return Optional.ofNullable(em.find(domainType, id));
275         }
276
277         LockModeType type = metadata.getLockModeType();
278
279         Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
280
281         return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
282     }
283
284     /**
285      * Returns {@link QueryHints} with the query hints based on the current {@link CrudMethodMetadata} and potential
286      * {@link EntityGraph} information.
287      *
288      * @return
289      */

290     protected QueryHints getQueryHints() {
291         return metadata == null ? NoHints.INSTANCE : DefaultQueryHints.of(entityInformation, metadata);
292     }
293
294     /*
295      * (non-Javadoc)
296      * @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable)
297      */

298     @Override
299     public T getOne(ID id) {
300
301         Assert.notNull(id, ID_MUST_NOT_BE_NULL);
302         return em.getReference(getDomainClass(), id);
303     }
304
305     /*
306      * (non-Javadoc)
307      * @see org.springframework.data.repository.CrudRepository#existsById(java.io.Serializable)
308      */

309     @Override
310     public boolean existsById(ID id) {
311
312         Assert.notNull(id, ID_MUST_NOT_BE_NULL);
313
314         if (entityInformation.getIdAttribute() == null) {
315             return findById(id).isPresent();
316         }
317
318         String placeholder = provider.getCountQueryPlaceholder();
319         String entityName = entityInformation.getEntityName();
320         Iterable<String> idAttributeNames = entityInformation.getIdAttributeNames();
321         String existsQuery = QueryUtils.getExistsQueryString(entityName, placeholder, idAttributeNames);
322
323         TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);
324
325         if (!entityInformation.hasCompositeId()) {
326             query.setParameter(idAttributeNames.iterator().next(), id);
327             return query.getSingleResult() == 1L;
328         }
329
330         for (String idAttributeName : idAttributeNames) {
331
332             Object idAttributeValue = entityInformation.getCompositeIdAttributeValue(id, idAttributeName);
333
334             boolean complexIdParameterValueDiscovered = idAttributeValue != null
335                     && !query.getParameter(idAttributeName).getParameterType().isAssignableFrom(idAttributeValue.getClass());
336
337             if (complexIdParameterValueDiscovered) {
338
339                 // fall-back to findById(id) which does the proper mapping for the parameter.
340                 return findById(id).isPresent();
341             }
342
343             query.setParameter(idAttributeName, idAttributeValue);
344         }
345
346         return query.getSingleResult() == 1L;
347     }
348
349     /*
350      * (non-Javadoc)
351      * @see org.springframework.data.jpa.repository.JpaRepository#findAll()
352      */

353     @Override
354     public List<T> findAll() {
355         return getQuery(null, Sort.unsorted()).getResultList();
356     }
357
358     /*
359      * (non-Javadoc)
360      * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
361      */

362     @Override
363     public List<T> findAllById(Iterable<ID> ids) {
364
365         Assert.notNull(ids, "Ids must not be null!");
366
367         if (!ids.iterator().hasNext()) {
368             return Collections.emptyList();
369         }
370
371         if (entityInformation.hasCompositeId()) {
372
373             List<T> results = new ArrayList<T>();
374
375             for (ID id : ids) {
376                 findById(id).ifPresent(results::add);
377             }
378
379             return results;
380         }
381
382         Collection<ID> idCollection = toCollection(ids);
383
384         ByIdsSpecification<T> specification = new ByIdsSpecification<T>(entityInformation);
385         TypedQuery<T> query = getQuery(specification, Sort.unsorted());
386
387         return query.setParameter(specification.parameter, idCollection).getResultList();
388     }
389
390     /*
391      * (non-Javadoc)
392      * @see org.springframework.data.jpa.repository.JpaRepository#findAll(org.springframework.data.domain.Sort)
393      */

394     @Override
395     public List<T> findAll(Sort sort) {
396         return getQuery(null, sort).getResultList();
397     }
398
399     /*
400      * (non-Javadoc)
401      * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)
402      */

403     @Override
404     public Page<T> findAll(Pageable pageable) {
405
406         if (isUnpaged(pageable)) {
407             return new PageImpl<T>(findAll());
408         }
409
410         return findAll((Specification<T>) null, pageable);
411     }
412
413     /*
414      * (non-Javadoc)
415      * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findOne(org.springframework.data.jpa.domain.Specification)
416      */

417     @Override
418     public Optional<T> findOne(@Nullable Specification<T> spec) {
419
420         try {
421             return Optional.of(getQuery(spec, Sort.unsorted()).getSingleResult());
422         } catch (NoResultException e) {
423             return Optional.empty();
424         }
425     }
426
427     /*
428      * (non-Javadoc)
429      * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification)
430      */

431     @Override
432     public List<T> findAll(@Nullable Specification<T> spec) {
433         return getQuery(spec, Sort.unsorted()).getResultList();
434     }
435
436     /*
437      * (non-Javadoc)
438      * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification, org.springframework.data.domain.Pageable)
439      */

440     @Override
441     public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {
442
443         TypedQuery<T> query = getQuery(spec, pageable);
444         return isUnpaged(pageable) ? new PageImpl<T>(query.getResultList())
445                 : readPage(query, getDomainClass(), pageable, spec);
446     }
447
448     /*
449      * (non-Javadoc)
450      * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(org.springframework.data.jpa.domain.Specification, org.springframework.data.domain.Sort)
451      */

452     @Override
453     public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
454         return getQuery(spec, sort).getResultList();
455     }
456
457     /*
458      * (non-Javadoc)
459      * @see org.springframework.data.repository.query.QueryByExampleExecutor#findOne(org.springframework.data.domain.Example)
460      */

461     @Override
462     public <S extends T> Optional<S> findOne(Example<S> example) {
463
464         try {
465             return Optional
466                     .of(getQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())
467                             .getSingleResult());
468         } catch (NoResultException e) {
469             return Optional.empty();
470         }
471     }
472
473     /*
474      * (non-Javadoc)
475      * @see org.springframework.data.repository.query.QueryByExampleExecutor#count(org.springframework.data.domain.Example)
476      */

477     @Override
478     public <S extends T> long count(Example<S> example) {
479         return executeCountQuery(
480                 getCountQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType()));
481     }
482
483     /*
484      * (non-Javadoc)
485      * @see org.springframework.data.repository.query.QueryByExampleExecutor#exists(org.springframework.data.domain.Example)
486      */

487     @Override
488     public <S extends T> boolean exists(Example<S> example) {
489         return !getQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())
490                 .getResultList().isEmpty();
491     }
492
493     /*
494      * (non-Javadoc)
495      * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
496      */

497     @Override
498     public <S extends T> List<S> findAll(Example<S> example) {
499         return getQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())
500                 .getResultList();
501     }
502
503     /*
504      * (non-Javadoc)
505      * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
506      */

507     @Override
508     public <S extends T> List<S> findAll(Example<S> example, Sort sort) {
509         return getQuery(new ExampleSpecification<S>(example, escapeCharacter), example.getProbeType(), sort)
510                 .getResultList();
511     }
512
513     /*
514      * (non-Javadoc)
515      * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Pageable)
516      */

517     @Override
518     public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
519
520         ExampleSpecification<S> spec = new ExampleSpecification<>(example, escapeCharacter);
521         Class<S> probeType = example.getProbeType();
522         TypedQuery<S> query = getQuery(new ExampleSpecification<>(example, escapeCharacter), probeType, pageable);
523
524         return isUnpaged(pageable) ? new PageImpl<>(query.getResultList()) : readPage(query, probeType, pageable, spec);
525     }
526
527     /*
528      * (non-Javadoc)
529      * @see org.springframework.data.repository.CrudRepository#count()
530      */

531     @Override
532     public long count() {
533         return em.createQuery(getCountQueryString(), Long.class).getSingleResult();
534     }
535
536     /*
537      * (non-Javadoc)
538      * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
539      */

540     @Override
541     public long count(@Nullable Specification<T> spec) {
542         return executeCountQuery(getCountQuery(spec, getDomainClass()));
543     }
544
545     /*
546      * (non-Javadoc)
547      * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
548      */

549     @Transactional
550     @Override
551     public <S extends T> S save(S entity) {
552
553         if (entityInformation.isNew(entity)) {
554             em.persist(entity);
555             return entity;
556         } else {
557             return em.merge(entity);
558         }
559     }
560
561     /*
562      * (non-Javadoc)
563      * @see org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java.lang.Object)
564      */

565     @Transactional
566     @Override
567     public <S extends T> S saveAndFlush(S entity) {
568
569         S result = save(entity);
570         flush();
571
572         return result;
573     }
574
575     /*
576      * (non-Javadoc)
577      * @see org.springframework.data.jpa.repository.JpaRepository#save(java.lang.Iterable)
578      */

579     @Transactional
580     @Override
581     public <S extends T> List<S> saveAll(Iterable<S> entities) {
582
583         Assert.notNull(entities, "Entities must not be null!");
584
585         List<S> result = new ArrayList<S>();
586
587         for (S entity : entities) {
588             result.add(save(entity));
589         }
590
591         return result;
592     }
593
594     /*
595      * (non-Javadoc)
596      * @see org.springframework.data.jpa.repository.JpaRepository#flush()
597      */

598     @Transactional
599     @Override
600     public void flush() {
601         em.flush();
602     }
603
604     /**
605      * Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and
606      * {@link Specification}.
607      *
608      * @param query must not be {@literal null}.
609      * @param spec can be {@literal null}.
610      * @param pageable must not be {@literal null}.
611      * @return
612      * @deprecated use {@link #readPage(TypedQuery, Class, Pageable, Specification)} instead
613      */

614     @Deprecated
615     protected Page<T> readPage(TypedQuery<T> query, Pageable pageable, @Nullable Specification<T> spec) {
616         return readPage(query, getDomainClass(), pageable, spec);
617     }
618
619     /**
620      * Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and
621      * {@link Specification}.
622      *
623      * @param query must not be {@literal null}.
624      * @param domainClass must not be {@literal null}.
625      * @param spec can be {@literal null}.
626      * @param pageable can be {@literal null}.
627      * @return
628      */

629     protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,
630             @Nullable Specification<S> spec) {
631
632         if (pageable.isPaged()) {
633             query.setFirstResult((int) pageable.getOffset());
634             query.setMaxResults(pageable.getPageSize());
635         }
636
637         return PageableExecutionUtils.getPage(query.getResultList(), pageable,
638                 () -> executeCountQuery(getCountQuery(spec, domainClass)));
639     }
640
641     /**
642      * Creates a new {@link TypedQuery} from the given {@link Specification}.
643      *
644      * @param spec can be {@literal null}.
645      * @param pageable must not be {@literal null}.
646      * @return
647      */

648     protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Pageable pageable) {
649
650         Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
651         return getQuery(spec, getDomainClass(), sort);
652     }
653
654     /**
655      * Creates a new {@link TypedQuery} from the given {@link Specification}.
656      *
657      * @param spec can be {@literal null}.
658      * @param domainClass must not be {@literal null}.
659      * @param pageable must not be {@literal null}.
660      * @return
661      */

662     protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass,
663             Pageable pageable) {
664
665         Sort sort = pageable.isPaged() ? pageable.getSort() : Sort.unsorted();
666         return getQuery(spec, domainClass, sort);
667     }
668
669     /**
670      * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
671      *
672      * @param spec can be {@literal null}.
673      * @param sort must not be {@literal null}.
674      * @return
675      */

676     protected TypedQuery<T> getQuery(@Nullable Specification<T> spec, Sort sort) {
677         return getQuery(spec, getDomainClass(), sort);
678     }
679
680     /**
681      * Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
682      *
683      * @param spec can be {@literal null}.
684      * @param domainClass must not be {@literal null}.
685      * @param sort must not be {@literal null}.
686      * @return
687      */

688     protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {
689
690         CriteriaBuilder builder = em.getCriteriaBuilder();
691         CriteriaQuery<S> query = builder.createQuery(domainClass);
692
693         Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
694         query.select(root);
695
696         if (sort.isSorted()) {
697             query.orderBy(toOrders(sort, root, builder));
698         }
699
700         return applyRepositoryMethodMetadata(em.createQuery(query));
701     }
702
703     /**
704      * Creates a new count query for the given {@link Specification}.
705      *
706      * @param spec can be {@literal null}.
707      * @return
708      * @deprecated override {@link #getCountQuery(Specification, Class)} instead
709      */

710     @Deprecated
711     protected TypedQuery<Long> getCountQuery(@Nullable Specification<T> spec) {
712         return getCountQuery(spec, getDomainClass());
713     }
714
715     /**
716      * Creates a new count query for the given {@link Specification}.
717      *
718      * @param spec can be {@literal null}.
719      * @param domainClass must not be {@literal null}.
720      * @return
721      */

722     protected <S extends T> TypedQuery<Long> getCountQuery(@Nullable Specification<S> spec, Class<S> domainClass) {
723
724         CriteriaBuilder builder = em.getCriteriaBuilder();
725         CriteriaQuery<Long> query = builder.createQuery(Long.class);
726
727         Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
728
729         if (query.isDistinct()) {
730             query.select(builder.countDistinct(root));
731         } else {
732             query.select(builder.count(root));
733         }
734
735         // Remove all Orders the Specifications might have applied
736         query.orderBy(Collections.<Order> emptyList());
737
738         return em.createQuery(query);
739     }
740
741     /**
742      * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
743      *
744      * @param spec can be {@literal null}.
745      * @param domainClass must not be {@literal null}.
746      * @param query must not be {@literal null}.
747      * @return
748      */

749     private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass,
750             CriteriaQuery<S> query) {
751
752         Assert.notNull(domainClass, "Domain class must not be null!");
753         Assert.notNull(query, "CriteriaQuery must not be null!");
754
755         Root<U> root = query.from(domainClass);
756
757         if (spec == null) {
758             return root;
759         }
760
761         CriteriaBuilder builder = em.getCriteriaBuilder();
762         Predicate predicate = spec.toPredicate(root, query, builder);
763
764         if (predicate != null) {
765             query.where(predicate);
766         }
767
768         return root;
769     }
770
771     private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {
772
773         if (metadata == null) {
774             return query;
775         }
776
777         LockModeType type = metadata.getLockModeType();
778         TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);
779
780         applyQueryHints(toReturn);
781
782         return toReturn;
783     }
784
785     private void applyQueryHints(Query query) {
786
787         for (Entry<String, Object> hint : getQueryHints().withFetchGraphs(em)) {
788             query.setHint(hint.getKey(), hint.getValue());
789         }
790     }
791
792     /**
793      * Executes a count query and transparently sums up all values returned.
794      *
795      * @param query must not be {@literal null}.
796      * @return
797      */

798     private static long executeCountQuery(TypedQuery<Long> query) {
799
800         Assert.notNull(query, "TypedQuery must not be null!");
801
802         List<Long> totals = query.getResultList();
803         long total = 0L;
804
805         for (Long element : totals) {
806             total += element == null ? 0 : element;
807         }
808
809         return total;
810     }
811
812     private static boolean isUnpaged(Pageable pageable) {
813         return pageable.isUnpaged();
814     }
815
816     /**
817      * Specification that gives access to the {@link Parameter} instance used to bind the ids for
818      * {@link SimpleJpaRepository#findAllById(Iterable)}. Workaround for OpenJPA not binding collections to in-clauses
819      * correctly when using by-name binding.
820      *
821      * @see <a href="https://issues.apache.org/jira/browse/OPENJPA-2018?focusedCommentId=13924055">OPENJPA-2018</a>
822      * @author Oliver Gierke
823      */

824     @SuppressWarnings("rawtypes")
825     private static final class ByIdsSpecification<T> implements Specification<T> {
826
827         private static final long serialVersionUID = 1L;
828
829         private final JpaEntityInformation<T, ?> entityInformation;
830
831         @Nullable ParameterExpression<Collection<?>> parameter;
832
833         ByIdsSpecification(JpaEntityInformation<T, ?> entityInformation) {
834             this.entityInformation = entityInformation;
835         }
836
837         /*
838          * (non-Javadoc)
839          * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
840          */

841         @Override
842         public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
843
844             Path<?> path = root.get(entityInformation.getIdAttribute());
845             parameter = (ParameterExpression<Collection<?>>) (ParameterExpression) cb.parameter(Collection.class);
846             return path.in(parameter);
847         }
848     }
849
850     /**
851      * {@link Specification} that gives access to the {@link Predicate} instance representing the values contained in the
852      * {@link Example}.
853      *
854      * @author Christoph Strobl
855      * @since 1.10
856      * @param <T>
857      */

858     private static class ExampleSpecification<T> implements Specification<T> {
859
860         private static final long serialVersionUID = 1L;
861
862         private final Example<T> example;
863         private final EscapeCharacter escapeCharacter;
864
865         /**
866          * Creates new {@link ExampleSpecification}.
867          *
868          * @param example
869          * @param escapeCharacter
870          */

871         ExampleSpecification(Example<T> example, EscapeCharacter escapeCharacter) {
872
873             Assert.notNull(example, "Example must not be null!");
874             Assert.notNull(escapeCharacter, "EscapeCharacter must not be null!");
875
876             this.example = example;
877             this.escapeCharacter = escapeCharacter;
878         }
879
880         /*
881          * (non-Javadoc)
882          * @see org.springframework.data.jpa.domain.Specification#toPredicate(javax.persistence.criteria.Root, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder)
883          */

884         @Override
885         public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
886             return QueryByExamplePredicateBuilder.getPredicate(root, cb, example, escapeCharacter);
887         }
888     }
889 }
890