package org.springframework.data.jpa.repository.query;
import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.lang.Nullable;
final class NamedQuery extends AbstractJpaQuery {
private static final String = "Your persistence provider does not support extracting the JPQL query from a "
+ "named query thus you can't use Pageable inside your query method. Make sure you "
+ "have a JpaDialect configured at your EntityManagerFactoryBean as this affects "
+ "discovering the concrete persistence provider.";
private static final Logger LOG = LoggerFactory.getLogger(NamedQuery.class);
private final String queryName;
private final String countQueryName;
private final @Nullable String countProjection;
private final QueryExtractor ;
private final boolean namedCountQueryIsPresent;
private final DeclaredQuery declaredQuery;
private final QueryParameterSetter.QueryMetadataCache metadataCache;
private NamedQuery(JpaQueryMethod method, EntityManager em) {
super(method, em);
this.queryName = method.getNamedQueryName();
this.countQueryName = method.getNamedCountQueryName();
this.extractor = method.getQueryExtractor();
this.countProjection = method.getCountQueryProjection();
Parameters<?, ?> parameters = method.getParameters();
if (parameters.hasSortParameter()) {
throw new IllegalStateException(String.format("Finder method %s is backed " + "by a NamedQuery and must "
+ "not contain a sort parameter as we cannot modify the query! Use @Query instead!", method));
}
this.namedCountQueryIsPresent = hasNamedQuery(em, countQueryName);
Query query = em.createNamedQuery(queryName);
String queryString = extractor.extractQueryString(query);
this.declaredQuery = DeclaredQuery.of(queryString);
boolean weNeedToCreateCountQuery = !namedCountQueryIsPresent && method.getParameters().hasPageableParameter();
boolean cantExtractQuery = !this.extractor.canExtractQuery();
if (weNeedToCreateCountQuery && cantExtractQuery) {
throw QueryCreationException.create(method, CANNOT_EXTRACT_QUERY);
}
if (parameters.hasPageableParameter()) {
LOG.warn("Finder method {} is backed by a NamedQuery" + " but contains a Pageable parameter! Sorting delivered "
+ "via this Pageable will not be applied!", method);
}
this.metadataCache = new QueryParameterSetter.QueryMetadataCache();
}
private static boolean hasNamedQuery(EntityManager em, String queryName) {
EntityManager lookupEm = em.getEntityManagerFactory().createEntityManager();
try {
lookupEm.createNamedQuery(queryName);
return true;
} catch (IllegalArgumentException e) {
LOG.debug("Did not find named query {}", queryName);
return false;
} finally {
lookupEm.close();
}
}
@Nullable
public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em) {
final String queryName = method.getNamedQueryName();
LOG.debug("Looking up named query {}", queryName);
if (!hasNamedQuery(em, queryName)) {
return null;
}
try {
RepositoryQuery query = new NamedQuery(method, em);
LOG.debug("Found named query {}!", queryName);
return query;
} catch (IllegalArgumentException e) {
return null;
}
}
@Override
protected Query doCreateQuery(JpaParametersParameterAccessor accessor) {
EntityManager em = getEntityManager();
JpaQueryMethod queryMethod = getQueryMethod();
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(accessor);
Class<?> typeToRead = getTypeToRead(processor.getReturnedType());
Query query = typeToRead == null
? em.createNamedQuery(queryName)
: em.createNamedQuery(queryName, typeToRead);
QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryName, query);
return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
}
@Override
protected TypedQuery<Long> doCreateCountQuery(JpaParametersParameterAccessor accessor) {
EntityManager em = getEntityManager();
TypedQuery<Long> countQuery;
String cacheKey;
if (namedCountQueryIsPresent) {
cacheKey = countQueryName;
countQuery = em.createNamedQuery(countQueryName, Long.class);
} else {
String countQueryString = declaredQuery.deriveCountQuery(null, countProjection).getQueryString();
cacheKey = countQueryString;
countQuery = em.createQuery(countQueryString, Long.class);
}
QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(cacheKey, countQuery);
return parameterBinder.get().bind(countQuery, metadata, accessor);
}
@Override
protected Class<?> getTypeToRead(ReturnedType returnedType) {
if (getQueryMethod().isNativeQuery()) {
Class<?> type = returnedType.getReturnedType();
Class<?> domainType = returnedType.getDomainType();
if (domainType.isAssignableFrom(type)) {
return type;
}
if (type.isAssignableFrom(domainType)) {
return domainType;
}
return type.isInterface() ? Tuple.class : null;
}
return declaredQuery.hasConstructorExpression()
? null
: super.getTypeToRead(returnedType);
}
}