1
16 package org.springframework.data.jpa.repository.query;
17
18 import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*;
19
20 import javax.persistence.EntityManager;
21 import javax.persistence.Query;
22 import javax.persistence.Tuple;
23 import javax.persistence.TypedQuery;
24
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import org.springframework.data.jpa.provider.QueryExtractor;
29 import org.springframework.data.repository.query.Parameters;
30 import org.springframework.data.repository.query.QueryCreationException;
31 import org.springframework.data.repository.query.RepositoryQuery;
32 import org.springframework.data.repository.query.ResultProcessor;
33 import org.springframework.data.repository.query.ReturnedType;
34 import org.springframework.lang.Nullable;
35
36
43 final class NamedQuery extends AbstractJpaQuery {
44
45 private static final String CANNOT_EXTRACT_QUERY = "Your persistence provider does not support extracting the JPQL query from a "
46 + "named query thus you can't use Pageable inside your query method. Make sure you "
47 + "have a JpaDialect configured at your EntityManagerFactoryBean as this affects "
48 + "discovering the concrete persistence provider.";
49
50 private static final Logger LOG = LoggerFactory.getLogger(NamedQuery.class);
51
52 private final String queryName;
53 private final String countQueryName;
54 private final @Nullable String countProjection;
55 private final QueryExtractor extractor;
56 private final boolean namedCountQueryIsPresent;
57 private final DeclaredQuery declaredQuery;
58 private final QueryParameterSetter.QueryMetadataCache metadataCache;
59
60
63 private NamedQuery(JpaQueryMethod method, EntityManager em) {
64
65 super(method, em);
66
67 this.queryName = method.getNamedQueryName();
68 this.countQueryName = method.getNamedCountQueryName();
69 this.extractor = method.getQueryExtractor();
70 this.countProjection = method.getCountQueryProjection();
71
72 Parameters<?, ?> parameters = method.getParameters();
73
74 if (parameters.hasSortParameter()) {
75 throw new IllegalStateException(String.format("Finder method %s is backed " + "by a NamedQuery and must "
76 + "not contain a sort parameter as we cannot modify the query! Use @Query instead!", method));
77 }
78
79 this.namedCountQueryIsPresent = hasNamedQuery(em, countQueryName);
80
81 Query query = em.createNamedQuery(queryName);
82 String queryString = extractor.extractQueryString(query);
83
84 this.declaredQuery = DeclaredQuery.of(queryString);
85
86 boolean weNeedToCreateCountQuery = !namedCountQueryIsPresent && method.getParameters().hasPageableParameter();
87 boolean cantExtractQuery = !this.extractor.canExtractQuery();
88
89 if (weNeedToCreateCountQuery && cantExtractQuery) {
90 throw QueryCreationException.create(method, CANNOT_EXTRACT_QUERY);
91 }
92
93 if (parameters.hasPageableParameter()) {
94 LOG.warn("Finder method {} is backed by a NamedQuery" + " but contains a Pageable parameter! Sorting delivered "
95 + "via this Pageable will not be applied!", method);
96 }
97
98 this.metadataCache = new QueryParameterSetter.QueryMetadataCache();
99 }
100
101
108 private static boolean hasNamedQuery(EntityManager em, String queryName) {
109
110
114 EntityManager lookupEm = em.getEntityManagerFactory().createEntityManager();
115
116 try {
117 lookupEm.createNamedQuery(queryName);
118 return true;
119 } catch (IllegalArgumentException e) {
120 LOG.debug("Did not find named query {}", queryName);
121 return false;
122 } finally {
123 lookupEm.close();
124 }
125 }
126
127
134 @Nullable
135 public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em) {
136
137 final String queryName = method.getNamedQueryName();
138
139 LOG.debug("Looking up named query {}", queryName);
140
141 if (!hasNamedQuery(em, queryName)) {
142 return null;
143 }
144
145 try {
146 RepositoryQuery query = new NamedQuery(method, em);
147 LOG.debug("Found named query {}!", queryName);
148 return query;
149 } catch (IllegalArgumentException e) {
150 return null;
151 }
152 }
153
154
158 @Override
159 protected Query doCreateQuery(JpaParametersParameterAccessor accessor) {
160
161 EntityManager em = getEntityManager();
162
163 JpaQueryMethod queryMethod = getQueryMethod();
164 ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
165
166 Class<?> typeToRead = getTypeToRead(processor.getReturnedType());
167
168 Query query = typeToRead == null
169 ? em.createNamedQuery(queryName)
170 : em.createNamedQuery(queryName, typeToRead);
171
172 QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryName, query);
173
174 return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
175 }
176
177
181 @Override
182 protected TypedQuery<Long> doCreateCountQuery(JpaParametersParameterAccessor accessor) {
183
184 EntityManager em = getEntityManager();
185 TypedQuery<Long> countQuery;
186
187 String cacheKey;
188 if (namedCountQueryIsPresent) {
189 cacheKey = countQueryName;
190 countQuery = em.createNamedQuery(countQueryName, Long.class);
191
192 } else {
193
194 String countQueryString = declaredQuery.deriveCountQuery(null, countProjection).getQueryString();
195 cacheKey = countQueryString;
196 countQuery = em.createQuery(countQueryString, Long.class);
197 }
198
199 QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(cacheKey, countQuery);
200
201 return parameterBinder.get().bind(countQuery, metadata, accessor);
202 }
203
204
208 @Override
209 protected Class<?> getTypeToRead(ReturnedType returnedType) {
210
211 if (getQueryMethod().isNativeQuery()) {
212
213 Class<?> type = returnedType.getReturnedType();
214 Class<?> domainType = returnedType.getDomainType();
215
216
217 if (domainType.isAssignableFrom(type)) {
218 return type;
219 }
220
221
222 if (type.isAssignableFrom(domainType)) {
223 return domainType;
224 }
225
226
227 return type.isInterface() ? Tuple.class : null;
228 }
229
230 return declaredQuery.hasConstructorExpression()
231 ? null
232 : super.getTypeToRead(returnedType);
233 }
234 }
235