@@ -18,22 +18,22 @@ package org.springframework.data.jpa.provider;
|
||||
import static org.springframework.data.jpa.provider.JpaClassUtils.*;
|
||||
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.metamodel.IdentifiableType;
|
||||
import jakarta.persistence.metamodel.Metamodel;
|
||||
import jakarta.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.persistence.config.QueryHints;
|
||||
import org.eclipse.persistence.jpa.JpaQuery;
|
||||
import org.eclipse.persistence.queries.ScrollableCursor;
|
||||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters;
|
||||
import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
|
||||
import org.springframework.data.util.CloseableIterator;
|
||||
@@ -49,8 +49,9 @@ import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
|
||||
|
||||
/**
|
||||
* Hibernate persistence provider.
|
||||
@@ -102,9 +103,15 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
|
||||
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
|
||||
EntityManager em) {
|
||||
return new HibernateJpaParametersParameterAccessor(parameters, values, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentHintKey() {
|
||||
return "org.hibernate.comment";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -133,6 +140,16 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
|
||||
return new EclipseLinkScrollableResultsIterator<>(jpaQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentHintKey() {
|
||||
return QueryHints.HINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommentHintValue(String comment) {
|
||||
return "/* " + comment + " */";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -161,11 +178,18 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
public Object getIdentifierFrom(Object entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getCommentHintKey() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
|
||||
private final Iterable<String> entityManagerClassNames;
|
||||
private final Iterable<String> metamodelClassNames;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistenceProvider}.
|
||||
*
|
||||
@@ -249,7 +273,8 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
|
||||
return cacheAndReturn(metamodelType, GENERIC_JPA);
|
||||
}
|
||||
|
||||
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
|
||||
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
|
||||
EntityManager em) {
|
||||
return new JpaParametersParameterAccessor(parameters, values);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.provider;
|
||||
|
||||
import jakarta.persistence.Query;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface to hide different implementations of query hints that insert comments into a {@link Query}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface QueryComment {
|
||||
|
||||
@Nullable
|
||||
String getCommentHintKey();
|
||||
|
||||
@Nullable
|
||||
default String getCommentHintValue(String comment) {
|
||||
return comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to assign metadata to repository operations.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
* @see org.springframework.data.jpa.repository.query.Meta
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Documented
|
||||
public @interface Meta {
|
||||
|
||||
/**
|
||||
* Add a comment to the query.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
String comment() default "";
|
||||
|
||||
}
|
||||
@@ -187,6 +187,15 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any meta-attributes that exist
|
||||
if (method.hasQueryMetaAttributes()) {
|
||||
|
||||
if (provider.getCommentHintKey() != null) {
|
||||
query.setHint( //
|
||||
provider.getCommentHintKey(), provider.getCommentHintValue(method.getQueryMetaAttributes().getComment()));
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -32,6 +33,7 @@ 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.Meta;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
@@ -46,6 +48,7 @@ import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -94,6 +97,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
private final Lazy<Boolean> isCollectionQuery;
|
||||
private final Lazy<Boolean> isProcedureQuery;
|
||||
private final Lazy<JpaEntityMetadata<?>> entityMetadata;
|
||||
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
|
||||
|
||||
/**
|
||||
* Creates a {@link JpaQueryMethod}.
|
||||
@@ -135,6 +139,7 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
|
||||
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
|
||||
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
|
||||
this.annotationCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
Assert.isTrue(!(isModifyingQuery() && getParameters().hasSpecialParameter()),
|
||||
String.format("Modifying method must not contain %s", Parameters.TYPES));
|
||||
@@ -193,6 +198,13 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
return modifying.getNullable() != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
|
||||
|
||||
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
|
||||
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link QueryHint}s annotated at this class. Note, that {@link QueryHints}
|
||||
*
|
||||
@@ -259,6 +271,47 @@ public class JpaQueryMethod extends QueryMethod {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return return true if {@link Meta} annotation is available.
|
||||
* @since 3.0
|
||||
*/
|
||||
public boolean hasQueryMetaAttributes() {
|
||||
return getMetaAnnotation() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Meta} annotation that is applied to the method or {@code null} if not available.
|
||||
*
|
||||
* @return
|
||||
* @since 3.0
|
||||
*/
|
||||
@Nullable
|
||||
Meta getMetaAnnotation() {
|
||||
return doFindAnnotation(Meta.class).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link org.springframework.data.jpa.repository.query.Meta} attributes to be applied.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @since 1.6
|
||||
*/
|
||||
public org.springframework.data.jpa.repository.query.Meta getQueryMetaAttributes() {
|
||||
|
||||
Meta meta = getMetaAnnotation();
|
||||
if (meta == null) {
|
||||
return new org.springframework.data.jpa.repository.query.Meta();
|
||||
}
|
||||
|
||||
org.springframework.data.jpa.repository.query.Meta metaAttributes = new org.springframework.data.jpa.repository.query.Meta();
|
||||
|
||||
if (StringUtils.hasText(meta.comment())) {
|
||||
metaAttributes.setComment(meta.comment());
|
||||
}
|
||||
|
||||
return metaAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
|
||||
* nor the attribute was specified.
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to hold metadata about repository methods.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
* @see org.springframework.data.jpa.repository.Meta
|
||||
*/
|
||||
public class Meta {
|
||||
|
||||
private enum MetaKey {
|
||||
COMMENT("comment");
|
||||
|
||||
private String key;
|
||||
|
||||
MetaKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> values = Collections.emptyMap();
|
||||
|
||||
public Meta() {}
|
||||
|
||||
/**
|
||||
* Copy a {@link Meta} object.
|
||||
*
|
||||
* @since 3.0
|
||||
* @param source
|
||||
*/
|
||||
Meta(Meta source) {
|
||||
this.values = new LinkedHashMap<>(source.values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment to the query that is propagated to the profile log.
|
||||
*
|
||||
* @param comment
|
||||
*/
|
||||
public void setComment(String comment) {
|
||||
setValue(MetaKey.COMMENT.key, comment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@literal null} if not set.
|
||||
*/
|
||||
@Nullable
|
||||
public String getComment() {
|
||||
return getValue(MetaKey.COMMENT.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public boolean hasValues() {
|
||||
return !this.values.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get {@link Iterable} of set meta values.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Iterable<Map.Entry<String, Object>> values() {
|
||||
return Collections.unmodifiableSet(this.values.entrySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or removes the value in case of {@literal null} or empty {@link String}.
|
||||
*
|
||||
* @param key must not be {@literal null} or empty.
|
||||
* @param value
|
||||
*/
|
||||
void setValue(String key, @Nullable Object value) {
|
||||
|
||||
Assert.hasText(key, "Meta key must not be 'null' or blank");
|
||||
|
||||
if (values == Collections.EMPTY_MAP) {
|
||||
values = new LinkedHashMap<>(2);
|
||||
}
|
||||
|
||||
if (value == null || (value instanceof String && !StringUtils.hasText((String) value))) {
|
||||
this.values.remove(key);
|
||||
}
|
||||
this.values.put(key, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getValue(String key) {
|
||||
return (T) this.values.get(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Christoph Strobl
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface CrudMethodMetadata {
|
||||
|
||||
@@ -59,6 +60,14 @@ public interface CrudMethodMetadata {
|
||||
*/
|
||||
QueryHints getQueryHintsForCount();
|
||||
|
||||
/**
|
||||
* Returns query comment to be applied to query.
|
||||
*
|
||||
* @return
|
||||
* @since 3.0
|
||||
*/
|
||||
String getComment();
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityGraph} to be used.
|
||||
*
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.support;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.QueryHint;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
@@ -23,9 +26,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.QueryHint;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.TargetSource;
|
||||
@@ -36,6 +36,7 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.Lock;
|
||||
import org.springframework.data.jpa.repository.Meta;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor;
|
||||
@@ -180,6 +181,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
private final @Nullable LockModeType lockModeType;
|
||||
private final org.springframework.data.jpa.repository.support.QueryHints queryHints;
|
||||
private final org.springframework.data.jpa.repository.support.QueryHints queryHintsForCount;
|
||||
private final String comment;
|
||||
private final Optional<EntityGraph> entityGraph;
|
||||
private final Method method;
|
||||
|
||||
@@ -195,6 +197,7 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
this.lockModeType = findLockModeType(method);
|
||||
this.queryHints = findQueryHints(method, it -> true);
|
||||
this.queryHintsForCount = findQueryHints(method, QueryHints::forCounting);
|
||||
this.comment = findComment(method);
|
||||
this.entityGraph = findEntityGraph(method);
|
||||
this.method = method;
|
||||
}
|
||||
@@ -233,6 +236,13 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
return queryHints;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String findComment(Method method) {
|
||||
|
||||
Meta annotation = AnnotatedElementUtils.findMergedAnnotation(method, Meta.class);
|
||||
return annotation == null ? null : (String) AnnotationUtils.getValue(annotation, "comment");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public LockModeType getLockModeType() {
|
||||
@@ -249,6 +259,11 @@ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, B
|
||||
return queryHintsForCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EntityGraph> getEntityGraph() {
|
||||
return entityGraph;
|
||||
|
||||
@@ -238,6 +238,8 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
query.setParameter("ids", idsCollection);
|
||||
}
|
||||
|
||||
applyQueryHints(query);
|
||||
|
||||
query.executeUpdate();
|
||||
}
|
||||
}
|
||||
@@ -279,7 +281,12 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteAllInBatch() {
|
||||
em.createQuery(getDeleteAllQueryString()).executeUpdate();
|
||||
|
||||
Query query = em.createQuery(getDeleteAllQueryString());
|
||||
|
||||
applyQueryHints(query);
|
||||
|
||||
query.executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,8 +303,13 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
LockModeType type = metadata.getLockModeType();
|
||||
|
||||
Map<String, Object> hints = new HashMap<>();
|
||||
|
||||
getQueryHints().withFetchGraphs(em).forEach(hints::put);
|
||||
|
||||
if (metadata.getComment() != null && provider.getCommentHintKey() != null) {
|
||||
hints.put(provider.getCommentHintKey(), provider.getCommentHintValue(metadata.getComment()));
|
||||
}
|
||||
|
||||
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
|
||||
}
|
||||
|
||||
@@ -355,6 +367,15 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
|
||||
TypedQuery<Long> query = em.createQuery(existsQuery, Long.class);
|
||||
|
||||
Map<String, Object> hints = new HashMap<>();
|
||||
getQueryHints().withFetchGraphs(em).forEach(hints::put);
|
||||
|
||||
if (metadata.getComment() != null && provider.getCommentHintKey() != null) {
|
||||
hints.put(provider.getCommentHintKey(), provider.getCommentHintValue(metadata.getComment()));
|
||||
}
|
||||
|
||||
hints.forEach(query::setHint);
|
||||
|
||||
if (!entityInformation.hasCompositeId()) {
|
||||
query.setParameter(idAttributeNames.iterator().next(), id);
|
||||
return query.getSingleResult() == 1L;
|
||||
@@ -556,9 +577,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
Assert.notNull(spec, "Specification must not be null");
|
||||
Assert.notNull(queryFunction, "Query function must not be null");
|
||||
|
||||
Function<Sort, TypedQuery<T>> finder = sort -> {
|
||||
return getQuery(spec, getDomainClass(), sort);
|
||||
};
|
||||
Function<Sort, TypedQuery<T>> finder = sort -> getQuery(spec, getDomainClass(), sort);
|
||||
|
||||
FetchableFluentQuery<R> fluentQuery = new FetchableFluentQueryBySpecification<T, R>(spec, getDomainClass(),
|
||||
Sort.unsorted(), null, finder, this::count, this::exists, this.em);
|
||||
@@ -568,7 +587,12 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return em.createQuery(getCountQueryString(), Long.class).getSingleResult();
|
||||
|
||||
TypedQuery<Long> query = em.createQuery(getCountQueryString(), Long.class);
|
||||
|
||||
applyQueryHintsForCount(query);
|
||||
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -804,7 +828,12 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
}
|
||||
|
||||
private void applyQueryHints(Query query) {
|
||||
|
||||
getQueryHints().withFetchGraphs(em).forEach(query::setHint);
|
||||
|
||||
if (metadata.getComment() != null && provider.getCommentHintKey() != null) {
|
||||
query.setHint(provider.getCommentHintKey(), provider.getCommentHintValue(metadata.getComment()));
|
||||
}
|
||||
}
|
||||
|
||||
private <S> TypedQuery<S> applyRepositoryMethodMetadataForCount(TypedQuery<S> query) {
|
||||
@@ -819,7 +848,12 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
|
||||
}
|
||||
|
||||
private void applyQueryHintsForCount(Query query) {
|
||||
|
||||
getQueryHintsForCount().forEach(query::setHint);
|
||||
|
||||
if (metadata.getComment() != null && provider.getCommentHintKey() != null) {
|
||||
query.setHint(provider.getCommentHintKey(), provider.getCommentHintValue(metadata.getComment()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,15 +19,15 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.QueryHint;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import jakarta.persistence.LockModeType;
|
||||
import jakarta.persistence.QueryHint;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.sample.Role;
|
||||
import org.springframework.data.jpa.repository.Meta;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.sample.RoleRepositoryWithMeta;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
/**
|
||||
* Verify that {@link Meta}-annotated methods properly embed comments into EclipseLink queries.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class MetaAnnotatedQueryMethodEclipseLinkIntegrationTests {
|
||||
|
||||
@Autowired RoleRepositoryWithMeta repository;
|
||||
|
||||
private static final ResourceLoader RESOURCE_LOADER = new DefaultResourceLoader();
|
||||
private static final String LOG_FILE = "test-eclipselink-meta.log";
|
||||
|
||||
@BeforeEach
|
||||
void cleanoutLogfile() throws IOException {
|
||||
new FileOutputStream(LOG_FILE).close();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void deleteLogfile() throws IOException {
|
||||
FileSystemUtils.deleteRecursively(Path.of(LOG_FILE));
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllShouldLogAComment() {
|
||||
|
||||
repository.findAll();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findByIdShouldLogAComment() {
|
||||
|
||||
repository.findById(0);
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void existsByIdShouldLogAComment() {
|
||||
|
||||
repository.existsById(0);
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void customFinderShouldLogAComment() throws Exception {
|
||||
|
||||
repository.findByName("name");
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findOneWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findOne(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findAll(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllWithExampleAndSortShouldLogAComment() {
|
||||
|
||||
repository.findAll(Example.of(new Role()), Sort.by("name"));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findByFluentDslWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findBy(Example.of(new Role()), FluentQuery.FetchableFluentQuery::all);
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void existsByExampleShouldLogAComment() {
|
||||
|
||||
repository.exists(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void countShouldLogAComment() {
|
||||
|
||||
repository.count();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void customCountShouldLogAComment() {
|
||||
|
||||
repository.countByName("name");
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void deleteAllInBatchShouldLogAComment() {
|
||||
|
||||
repository.deleteAllInBatch();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
void assertAtLeastOneComment() {
|
||||
|
||||
try (Reader reader = new InputStreamReader(RESOURCE_LOADER.getResource("file:" + LOG_FILE).getInputStream(),
|
||||
StandardCharsets.UTF_8)) {
|
||||
|
||||
String logFileOutput = FileCopyUtils.copyToString(reader);
|
||||
assertThat(logFileOutput).contains("/* foobar */");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = "org.springframework.data.jpa.repository.sample")
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Properties jpaProperties() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.put("eclipselink.weaving", "false");
|
||||
properties.put("eclipselink.logging.level.sql", "FINE");
|
||||
properties.put("eclipselink.logging.file", LOG_FILE);
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AbstractJpaVendorAdapter vendorAdaptor() {
|
||||
|
||||
EclipseLinkJpaVendorAdapter vendorAdapter = new EclipseLinkJpaVendorAdapter();
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
return vendorAdapter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EntityManagerFactory entityManagerFactory() {
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setDataSource(dataSource());
|
||||
factory.setPersistenceUnitName("spring-data-jpa");
|
||||
factory.setJpaVendorAdapter(vendorAdaptor());
|
||||
factory.setJpaProperties(jpaProperties());
|
||||
factory.afterPropertiesSet();
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
return new JpaTransactionManager(entityManagerFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.read.ListAppender;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.sample.Role;
|
||||
import org.springframework.data.jpa.repository.Meta;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.sample.RoleRepositoryWithMeta;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Verify that {@link Meta}-annotated methods properly embed comments into Hibernate queries.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class MetaAnnotatedQueryMethodHibernateIntegrationTests {
|
||||
|
||||
@Autowired RoleRepositoryWithMeta repository;
|
||||
|
||||
Logger testLogger = (Logger) LoggerFactory.getLogger("org.hibernate.SQL");
|
||||
ListAppender<ILoggingEvent> testAppender;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
|
||||
testAppender = new ListAppender<>();
|
||||
testAppender.start();
|
||||
testLogger.setLevel(Level.DEBUG);
|
||||
testLogger.addAppender(testAppender);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void clearUp() {
|
||||
testLogger.detachAppender(testAppender);
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllShouldLogAComment() {
|
||||
|
||||
repository.findAll();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findByIdShouldNotLogAComment() {
|
||||
|
||||
repository.findById(0);
|
||||
|
||||
assertNoComments();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void existsByIdShouldLogAComment() {
|
||||
|
||||
repository.existsById(0);
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void customFinderShouldLogAComment() {
|
||||
|
||||
repository.findByName("name");
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findOneWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findOne(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findAll(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findAllWithExampleAndSortShouldLogAComment() {
|
||||
|
||||
repository.findAll(Example.of(new Role()), Sort.by("name"));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void findByFluentDslWithExampleShouldLogAComment() {
|
||||
|
||||
repository.findBy(Example.of(new Role()), FluentQuery.FetchableFluentQuery::all);
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void existsByExampleShouldLogAComment() {
|
||||
|
||||
repository.exists(Example.of(new Role()));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void countShouldLogAComment() {
|
||||
|
||||
repository.count();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void customCountShouldLogAComment() {
|
||||
|
||||
repository.countByName("name");
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void deleteAllByIdInBatchShouldLogAComment() {
|
||||
|
||||
repository.deleteAllByIdInBatch(List.of(0, 1, 2));
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
@Test // GH-775
|
||||
void deleteAllInBatchShouldLogAComment() {
|
||||
|
||||
repository.deleteAllInBatch();
|
||||
|
||||
assertAtLeastOneComment();
|
||||
}
|
||||
|
||||
private final static Predicate<String> hasComment = s -> s.startsWith("/* foobar */");
|
||||
|
||||
private void assertAtLeastOneComment() {
|
||||
assertThat(testAppender.list).extracting(ILoggingEvent::getFormattedMessage)
|
||||
.haveAtLeastOne(new Condition<String>(hasComment, "SQL contains a comment"));
|
||||
}
|
||||
|
||||
private void assertNoComments() {
|
||||
assertThat(testAppender.list).extracting(ILoggingEvent::getFormattedMessage).noneMatch(hasComment);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = "org.springframework.data.jpa.repository.sample")
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder().generateUniqueName(true).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Properties jpaProperties() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("hibernate.use_sql_comments", "true");
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AbstractJpaVendorAdapter vendorAdaptor() {
|
||||
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
return vendorAdapter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EntityManagerFactory entityManagerFactory() {
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setDataSource(dataSource());
|
||||
factory.setPersistenceUnitName("spring-data-jpa");
|
||||
factory.setJpaVendorAdapter(vendorAdaptor());
|
||||
factory.setJpaProperties(jpaProperties());
|
||||
factory.afterPropertiesSet();
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() {
|
||||
return new JpaTransactionManager(entityManagerFactory());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.Meta;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* Verify that {@link Meta}-annotated methods property capture comments.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class MetaAnnotatedQueryMethodUnitTests {
|
||||
|
||||
@Mock QueryExtractor extractor;
|
||||
|
||||
private ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
@Test // GH-775
|
||||
void metaAnnotationCommentsAreCapturedInJpaQueryMethod() throws Exception {
|
||||
|
||||
Method method = UserRepository.class.getMethod("metaMethod");
|
||||
DefaultRepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
JpaQueryMethod jpaQueryMethod = new JpaQueryMethod(method, repositoryMetadata, factory, extractor);
|
||||
|
||||
assertThat(jpaQueryMethod.getQueryMetaAttributes().getComment()).isEqualTo("Comments embedded in SQL");
|
||||
}
|
||||
|
||||
interface UserRepository extends CrudRepository<User, String> {
|
||||
|
||||
@Meta(comment = "Comments embedded in SQL")
|
||||
void metaMethod();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2008-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.sample;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.sample.Role;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Meta;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* Typed repository for {@link Role} but with {@link Meta} annotations applied.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 3.0
|
||||
*/
|
||||
public interface RoleRepositoryWithMeta extends JpaRepository<Role, Integer>, QuerydslPredicateExecutor<Role> {
|
||||
|
||||
// Finders
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
List<Role> findAll();
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
Optional<Role> findById(Integer id);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
Optional<Role> findOne(Predicate predicate);
|
||||
|
||||
@Meta(comment = "foobar")
|
||||
List<Role> findByName(String name);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
<S extends Role> Optional<S> findOne(Example<S> example);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
<S extends Role> List<S> findAll(Example<S> example);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
<S extends Role> List<S> findAll(Example<S> example, Sort sort);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
<S extends Role, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
|
||||
|
||||
// counters
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
long count();
|
||||
|
||||
@Meta(comment = "foobar")
|
||||
long countByName(String name);
|
||||
|
||||
// exists
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
boolean existsById(Integer integer);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
<S extends Role> boolean exists(Example<S> example);
|
||||
|
||||
// delete
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
void deleteAllInBatch();
|
||||
|
||||
@Override
|
||||
@Meta(comment = "foobar")
|
||||
void deleteAllByIdInBatch(Iterable<Integer> integers);
|
||||
}
|
||||
@@ -726,6 +726,139 @@ public interface UserRepository extends Repository<User, Long> {
|
||||
====
|
||||
The preceding declaration would apply the configured `@QueryHint` for that actually query but omit applying it to the count query triggered to calculate the total number of pages.
|
||||
|
||||
[[jpa.query-hints.comments]]
|
||||
==== Adding Comments to Queries
|
||||
Sometimes, you need to debug a query based upon database performance.
|
||||
The query your database administrator shows you may look VERY different than what you wrote using `@Query`, or it may look
|
||||
nothing like what you presume Spring Data JPA has generated regarding a custom finder or if you used query by example.
|
||||
|
||||
To make this process easier, you can insert custom comments into almost any JPA operation, whether its a query or other operation
|
||||
by applying the `@Meta` annotation.
|
||||
|
||||
.Apply `@Meta` annotation to repository operations
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
public interface RoleRepository extends JpaRepository<Role, Integer> {
|
||||
|
||||
@Meta(comment = "find roles by name")
|
||||
List<Role> findByName(String name);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "find roles using QBE")
|
||||
<S extends Role> List<S> findAll(Example<S> example);
|
||||
|
||||
@Meta(comment = "count roles for a given name")
|
||||
long countByName(String name);
|
||||
|
||||
@Override
|
||||
@Meta(comment = "exists based on QBE")
|
||||
<S extends Role> boolean exists(Example<S> example);
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This sample repository has a mixture of custom finders as well as overriding the inherited operations from `JpaRepository`.
|
||||
Either way, the `@Meta` annotation lets you add a `comment` that will be inserted into queries before they are sent to the database.
|
||||
|
||||
It's also important to note that this feature isn't confined solely to queries. It extends to the `count` and `exists` operations.
|
||||
And while not shown, it also extends to certain `delete` operations.
|
||||
|
||||
IMPORTANT: While we have attempted to apply this feature everywhere possible, some operations of the underlying `EntityManager` don't support comments. For example, `entityManager.createQuery()` is clearly documented as supporting comments, but `entityManager.find()` operations do not.
|
||||
|
||||
Neither JPQL logging nor SQL logging is a standard in JPA, so each provider requires custom configuration, as shown the sections below.
|
||||
|
||||
===== Activating Hibernate comments
|
||||
To activate query comments in Hibernate, you must set `hibernate.use_sql_comments` to `true`.
|
||||
|
||||
If you are using Java-based configuration settings, this can be done like this:
|
||||
|
||||
.Java-based JPA configuration
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public Properties jpaProperties() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("hibernate.use_sql_comments", "true");
|
||||
return properties;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you have a `persistence.xml` file, you can apply it there:
|
||||
|
||||
.`persistence.xml`-based configuration
|
||||
====
|
||||
[source, xml]
|
||||
----
|
||||
<persistence-unit name="my-persistence-unit">
|
||||
|
||||
...registered classes...
|
||||
|
||||
<properties>
|
||||
<property name="hibernate.use_sql_comments" value="true" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
----
|
||||
====
|
||||
|
||||
Finally, if you are using Spring Boot, then you can set it up inside your `application.properties` file:
|
||||
|
||||
.Spring Boot property-based configuration
|
||||
====
|
||||
----
|
||||
spring.jpa.properties.hibernate.use_sql_comments=true
|
||||
----
|
||||
====
|
||||
|
||||
===== Activating EclipseLink comments
|
||||
To activate query comments in EclipseLink, you must set `eclipselink.logging.level.sql` to `FINE`.
|
||||
|
||||
If you are using Java-based configuration settings, this can be done like this:
|
||||
|
||||
.Java-based JPA configuration
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public Properties jpaProperties() {
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("eclipselink.logging.level.sql", "FINE");
|
||||
return properties;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you have a `persistence.xml` file, you can apply it there:
|
||||
|
||||
.`persistence.xml`-based configuration
|
||||
====
|
||||
[source, xml]
|
||||
----
|
||||
<persistence-unit name="my-persistence-unit">
|
||||
|
||||
...registered classes...
|
||||
|
||||
<properties>
|
||||
<property name="eclipselink.logging.level.sql" value="FINE" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
----
|
||||
====
|
||||
|
||||
Finally, if you are using Spring Boot, then you can set it up inside your `application.properties` file:
|
||||
|
||||
.Spring Boot property-based configuration
|
||||
====
|
||||
----
|
||||
spring.jpa.properties.eclipselink.logging.level.sql=FINE
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
[[jpa.entity-graph]]
|
||||
=== Configuring Fetch- and LoadGraphs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user