package org.springframework.data.jpa.repository.query;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.persistence.LockModeType;
import javax.persistence.QueryHint;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class JpaQueryMethod extends QueryMethod {
private static final Set<Class<?>> NATIVE_ARRAY_TYPES;
private static final StoredProcedureAttributeSource storedProcedureAttributeSource = StoredProcedureAttributeSource.INSTANCE;
static {
Set<Class<?>> types = new HashSet<>();
types.add(byte[].class);
types.add(Byte[].class);
types.add(char[].class);
types.add(Character[].class);
NATIVE_ARRAY_TYPES = Collections.unmodifiableSet(types);
}
private final QueryExtractor ;
private final Method method;
private @Nullable StoredProcedureAttributes storedProcedureAttributes;
private final Lazy<LockModeType> lockModeType;
private final Lazy<QueryHints> queryHints;
private final Lazy<JpaEntityGraph> jpaEntityGraph;
private final Lazy<Modifying> modifying;
private final Lazy<Boolean> isNativeQuery;
private final Lazy<Boolean> isCollectionQuery;
private final Lazy<Boolean> isProcedureQuery;
private final Lazy<JpaEntityMetadata<?>> entityMetadata;
public (Method method, RepositoryMetadata metadata, ProjectionFactory factory,
QueryExtractor extractor) {
super(method, metadata, factory);
Assert.notNull(method, "Method must not be null!");
Assert.notNull(extractor, "Query extractor must not be null!");
this.method = method;
this.extractor = extractor;
this.lockModeType = Lazy
.of(() -> (LockModeType) Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, Lock.class))
.map(AnnotationUtils::getValue)
.orElse(null));
this.queryHints = Lazy.of(() -> AnnotatedElementUtils.findMergedAnnotation(method, QueryHints.class));
this.modifying = Lazy.of(() -> AnnotatedElementUtils.findMergedAnnotation(method, Modifying.class));
this.jpaEntityGraph = Lazy.of(() -> {
EntityGraph entityGraph = AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class);
if (entityGraph == null) {
return null;
}
return new JpaEntityGraph(entityGraph, getNamedQueryName());
});
this.isNativeQuery = Lazy.of(() -> getAnnotationValue("nativeQuery", Boolean.class));
this.isCollectionQuery = Lazy
.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(method.getReturnType()));
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
Assert.isTrue(!(isModifyingQuery() && getParameters().hasSpecialParameter()),
String.format("Modifying method must not contain %s!", Parameters.TYPES));
assertParameterNamesInAnnotatedQuery();
}
private void assertParameterNamesInAnnotatedQuery() {
String annotatedQuery = getAnnotatedQuery();
if (!DeclaredQuery.of(annotatedQuery).hasNamedParameter()) {
return;
}
for (Parameter parameter : getParameters()) {
if (!parameter.isNamedParameter()) {
continue;
}
if (StringUtils.isEmpty(annotatedQuery)
|| !annotatedQuery.contains(String.format(":%s", parameter.getName().get()))
&& !annotatedQuery.contains(String.format("#%s", parameter.getName().get()))) {
throw new IllegalStateException(
String.format("Using named parameters for method %s but parameter '%s' not found in annotated query '%s'!",
method, parameter.getName(), annotatedQuery));
}
}
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public JpaEntityMetadata<?> getEntityInformation() {
return this.entityMetadata.get();
}
@Override
public boolean isModifyingQuery() {
return modifying.getNullable() != null;
}
List<QueryHint> getHints() {
QueryHints hints = this.queryHints.getNullable();
if (hints != null) {
return Arrays.asList(hints.value());
}
return Collections.emptyList();
}
@Nullable
LockModeType getLockModeType() {
return lockModeType.getNullable();
}
@Nullable
JpaEntityGraph getEntityGraph() {
return jpaEntityGraph.getNullable();
}
boolean applyHintsToCountQuery() {
QueryHints hints = this.queryHints.getNullable();
return hints != null ? hints.forCounting() : false;
}
QueryExtractor () {
return extractor;
}
Class<?> getReturnType() {
return method.getReturnType();
}
@Nullable
String getAnnotatedQuery() {
String query = getAnnotationValue("value", String.class);
return StringUtils.hasText(query) ? query : null;
}
String getRequiredAnnotatedQuery() throws IllegalStateException {
String query = getAnnotatedQuery();
if (query != null) {
return query;
}
throw new IllegalStateException(String.format("No annotated query found for query method %s!", getName()));
}
@Nullable
String getCountQuery() {
String countQuery = getAnnotationValue("countQuery", String.class);
return StringUtils.hasText(countQuery) ? countQuery : null;
}
@Nullable
String getCountQueryProjection() {
String countProjection = getAnnotationValue("countProjection", String.class);
return StringUtils.hasText(countProjection) ? countProjection : null;
}
boolean isNativeQuery() {
return this.isNativeQuery.get();
}
@Override
public String getNamedQueryName() {
String annotatedName = getAnnotationValue("name", String.class);
return StringUtils.hasText(annotatedName) ? annotatedName : super.getNamedQueryName();
}
String getNamedCountQueryName() {
String annotatedName = getAnnotationValue("countName", String.class);
return StringUtils.hasText(annotatedName) ? annotatedName : getNamedQueryName() + ".count";
}
boolean getFlushAutomatically() {
return getMergedOrDefaultAnnotationValue("flushAutomatically", Modifying.class, Boolean.class);
}
boolean getClearAutomatically() {
return getMergedOrDefaultAnnotationValue("clearAutomatically", Modifying.class, Boolean.class);
}
private <T> T getAnnotationValue(String attribute, Class<T> type) {
return getMergedOrDefaultAnnotationValue(attribute, Query.class, type);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> T getMergedOrDefaultAnnotationValue(String attribute, Class annotationType, Class<T> targetType) {
Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(method, annotationType);
if (annotation == null) {
return targetType.cast(AnnotationUtils.getDefaultValue(annotationType, attribute));
}
return targetType.cast(AnnotationUtils.getValue(annotation, attribute));
}
@Override
protected JpaParameters createParameters(Method method) {
return new JpaParameters(method);
}
@Override
public JpaParameters getParameters() {
return (JpaParameters) super.getParameters();
}
@Override
public boolean isCollectionQuery() {
return this.isCollectionQuery.get();
}
public boolean isProcedureQuery() {
return this.isProcedureQuery.get();
}
StoredProcedureAttributes getProcedureAttributes() {
if (storedProcedureAttributes == null) {
this.storedProcedureAttributes = storedProcedureAttributeSource.createFrom(method, getEntityInformation());
}
return storedProcedureAttributes;
}
}