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 javax.persistence.EntityManager;
19 import javax.persistence.Query;
20
21 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
22 import org.springframework.data.repository.query.ResultProcessor;
23 import org.springframework.data.repository.query.ReturnedType;
24 import org.springframework.expression.spel.standard.SpelExpressionParser;
25 import org.springframework.util.Assert;
26
27 /**
28  * Base class for {@link String} based JPA queries.
29  *
30  * @author Oliver Gierke
31  * @author Thomas Darimont
32  * @author Jens Schauder
33  * @author Tom Hombergs
34  * @author David Madden
35  * @author Mark Paluch
36  */

37 abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
38
39     private final DeclaredQuery query;
40     private final DeclaredQuery countQuery;
41     private final QueryMethodEvaluationContextProvider evaluationContextProvider;
42     private final SpelExpressionParser parser;
43     private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
44
45     /**
46      * Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
47      * query {@link String}.
48      *
49      * @param method must not be {@literal null}.
50      * @param em must not be {@literal null}.
51      * @param queryString must not be {@literal null}.
52      * @param evaluationContextProvider must not be {@literal null}.
53      * @param parser must not be {@literal null}.
54      */

55     public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
56             QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
57
58         super(method, em);
59
60         Assert.hasText(queryString, "Query string must not be null or empty!");
61         Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!");
62         Assert.notNull(parser, "Parser must not be null!");
63
64         this.evaluationContextProvider = evaluationContextProvider;
65         this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser);
66
67         DeclaredQuery countQuery = query.deriveCountQuery(method.getCountQuery(), method.getCountQueryProjection());
68         this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser);
69
70         this.parser = parser;
71
72         Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
73                 "JDBC style parameters (?) are not supported for JPA queries.");
74     }
75
76     /*
77      * (non-Javadoc)
78      * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateQuery(JpaParametersParameterAccessor)
79      */

80     @Override
81     public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
82
83         String sortedQueryString = QueryUtils.applySorting(query.getQueryString(), accessor.getSort(), query.getAlias());
84         ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
85
86         Query query = createJpaQuery(sortedQueryString, processor.getReturnedType());
87
88         QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query);
89
90         // it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the
91         // parameters in the query do not change.
92         return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
93     }
94
95     /*
96      * (non-Javadoc)
97      * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#createBinder(JpaParametersParameterAccessor)
98      */

99     @Override
100     protected ParameterBinder createBinder() {
101
102         return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
103                 evaluationContextProvider);
104     }
105
106     /*
107      * (non-Javadoc)
108      * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(JpaParametersParameterAccessor)
109      */

110     @Override
111     protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
112
113         String queryString = countQuery.getQueryString();
114         EntityManager em = getEntityManager();
115
116         Query query = getQueryMethod().isNativeQuery() //
117                 ? em.createNativeQuery(queryString) //
118                 : em.createQuery(queryString, Long.class);
119
120         QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryString, query);
121
122         parameterBinder.get().bind(metadata.withQuery(query), accessor, QueryParameterSetter.ErrorHandling.LENIENT);
123
124         return query;
125     }
126
127     /**
128      * @return the query
129      */

130     public DeclaredQuery getQuery() {
131         return query;
132     }
133
134     /**
135      * @return the countQuery
136      */

137     public DeclaredQuery getCountQuery() {
138         return countQuery;
139     }
140
141     /**
142      * Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
143      * type.
144      */

145     protected Query createJpaQuery(String queryString, ReturnedType returnedType) {
146
147         EntityManager em = getEntityManager();
148
149         if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) {
150             return em.createQuery(queryString);
151         }
152
153         Class<?> typeToRead = getTypeToRead(returnedType);
154
155         return typeToRead == null //
156                 ? em.createQuery(queryString) //
157                 : em.createQuery(queryString, typeToRead);
158     }
159 }
160