Skip to content

Commit aa6a809

Browse files
committed
Introduce @meta data support for repository methods.
Closes #775.
1 parent 36764e5 commit aa6a809

15 files changed

+1164
-21
lines changed

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

+33-8
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@
1818
import static org.springframework.data.jpa.provider.JpaClassUtils.*;
1919
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
2020

21-
import java.util.Collections;
22-
import java.util.NoSuchElementException;
23-
import java.util.Set;
24-
2521
import jakarta.persistence.EntityManager;
2622
import jakarta.persistence.Query;
2723
import jakarta.persistence.metamodel.IdentifiableType;
2824
import jakarta.persistence.metamodel.Metamodel;
2925
import jakarta.persistence.metamodel.SingularAttribute;
3026

27+
import java.util.Collections;
28+
import java.util.NoSuchElementException;
29+
import java.util.Set;
30+
31+
import org.eclipse.persistence.config.QueryHints;
3132
import org.eclipse.persistence.jpa.JpaQuery;
3233
import org.eclipse.persistence.queries.ScrollableCursor;
3334
import org.hibernate.ScrollMode;
3435
import org.hibernate.ScrollableResults;
3536
import org.hibernate.proxy.HibernateProxy;
36-
3737
import org.springframework.data.jpa.repository.query.JpaParameters;
3838
import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
3939
import org.springframework.data.util.CloseableIterator;
@@ -49,8 +49,9 @@
4949
* @author Thomas Darimont
5050
* @author Mark Paluch
5151
* @author Jens Schauder
52+
* @author Greg Turnquist
5253
*/
53-
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
54+
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
5455

5556
/**
5657
* Hibernate persistence provider.
@@ -102,9 +103,15 @@ public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
102103
}
103104

104105
@Override
105-
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
106+
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
107+
EntityManager em) {
106108
return new HibernateJpaParametersParameterAccessor(parameters, values, em);
107109
}
110+
111+
@Override
112+
public String getCommentHintKey() {
113+
return "org.hibernate.comment";
114+
}
108115
},
109116

110117
/**
@@ -133,6 +140,16 @@ public Object getIdentifierFrom(Object entity) {
133140
public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
134141
return new EclipseLinkScrollableResultsIterator<>(jpaQuery);
135142
}
143+
144+
@Override
145+
public String getCommentHintKey() {
146+
return QueryHints.HINT;
147+
}
148+
149+
@Override
150+
public String getCommentHintValue(String comment) {
151+
return "/* " + comment + " */";
152+
}
136153
},
137154

138155
/**
@@ -161,11 +178,18 @@ public boolean shouldUseAccessorFor(Object entity) {
161178
public Object getIdentifierFrom(Object entity) {
162179
return null;
163180
}
181+
182+
@Nullable
183+
@Override
184+
public String getCommentHintKey() {
185+
return null;
186+
}
164187
};
165188

166189
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
167190
private final Iterable<String> entityManagerClassNames;
168191
private final Iterable<String> metamodelClassNames;
192+
169193
/**
170194
* Creates a new {@link PersistenceProvider}.
171195
*
@@ -249,7 +273,8 @@ public static PersistenceProvider fromMetamodel(Metamodel metamodel) {
249273
return cacheAndReturn(metamodelType, GENERIC_JPA);
250274
}
251275

252-
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
276+
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
277+
EntityManager em) {
253278
return new JpaParametersParameterAccessor(parameters, values);
254279
}
255280

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2013-2022 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.provider;
17+
18+
import jakarta.persistence.Query;
19+
20+
import org.springframework.lang.Nullable;
21+
22+
/**
23+
* Interface to hide different implementations of query hints that insert comments into a {@link Query}.
24+
*
25+
* @author Greg Turnquist
26+
* @since 3.0
27+
*/
28+
public interface QueryComment {
29+
30+
@Nullable
31+
String getCommentHintKey();
32+
33+
@Nullable
34+
default String getCommentHintValue(String comment) {
35+
return comment;
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2014-2022 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;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* Annotation to assign metadata to repository operations.
26+
*
27+
* @author Greg Turnquist
28+
* @since 3.0
29+
* @see org.springframework.data.jpa.repository.query.Meta
30+
*/
31+
@Retention(RetentionPolicy.RUNTIME)
32+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
33+
@Documented
34+
public @interface Meta {
35+
36+
/**
37+
* Add a comment to the query.
38+
*
39+
* @return empty {@link String} by default.
40+
*/
41+
String comment() default "";
42+
43+
}

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java

+9
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ protected <T extends Query> T applyHints(T query, JpaQueryMethod method) {
187187
}
188188
}
189189

190+
// Apply any meta-attributes that exist
191+
if (method.hasQueryMetaAttributes()) {
192+
193+
if (provider.getCommentHintKey() != null) {
194+
query.setHint( //
195+
provider.getCommentHintKey(), provider.getCommentHintValue(method.getQueryMetaAttributes().getComment()));
196+
}
197+
}
198+
190199
return query;
191200
}
192201

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java

+53
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.HashSet;
2626
import java.util.List;
27+
import java.util.Map;
2728
import java.util.Optional;
2829
import java.util.Set;
2930

@@ -32,6 +33,7 @@
3233
import org.springframework.data.jpa.provider.QueryExtractor;
3334
import org.springframework.data.jpa.repository.EntityGraph;
3435
import org.springframework.data.jpa.repository.Lock;
36+
import org.springframework.data.jpa.repository.Meta;
3537
import org.springframework.data.jpa.repository.Modifying;
3638
import org.springframework.data.jpa.repository.Query;
3739
import org.springframework.data.jpa.repository.QueryHints;
@@ -46,6 +48,7 @@
4648
import org.springframework.data.util.TypeInformation;
4749
import org.springframework.lang.Nullable;
4850
import org.springframework.util.Assert;
51+
import org.springframework.util.ConcurrentReferenceHashMap;
4952
import org.springframework.util.StringUtils;
5053

5154
/**
@@ -94,6 +97,7 @@ public class JpaQueryMethod extends QueryMethod {
9497
private final Lazy<Boolean> isCollectionQuery;
9598
private final Lazy<Boolean> isProcedureQuery;
9699
private final Lazy<JpaEntityMetadata<?>> entityMetadata;
100+
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;
97101

98102
/**
99103
* Creates a {@link JpaQueryMethod}.
@@ -135,6 +139,7 @@ protected JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionF
135139
this.isCollectionQuery = Lazy.of(() -> super.isCollectionQuery() && !NATIVE_ARRAY_TYPES.contains(this.returnType));
136140
this.isProcedureQuery = Lazy.of(() -> AnnotationUtils.findAnnotation(method, Procedure.class) != null);
137141
this.entityMetadata = Lazy.of(() -> new DefaultJpaEntityMetadata<>(getDomainClass()));
142+
this.annotationCache = new ConcurrentReferenceHashMap<>();
138143

139144
Assert.isTrue(!(isModifyingQuery() && getParameters().hasSpecialParameter()),
140145
String.format("Modifying method must not contain %s", Parameters.TYPES));
@@ -193,6 +198,13 @@ public boolean isModifyingQuery() {
193198
return modifying.getNullable() != null;
194199
}
195200

201+
@SuppressWarnings("unchecked")
202+
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
203+
204+
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
205+
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
206+
}
207+
196208
/**
197209
* Returns all {@link QueryHint}s annotated at this class. Note, that {@link QueryHints}
198210
*
@@ -259,6 +271,47 @@ Class<?> getReturnType() {
259271
return returnType;
260272
}
261273

274+
/**
275+
* @return return true if {@link Meta} annotation is available.
276+
* @since 3.0
277+
*/
278+
public boolean hasQueryMetaAttributes() {
279+
return getMetaAnnotation() != null;
280+
}
281+
282+
/**
283+
* Returns the {@link Meta} annotation that is applied to the method or {@code null} if not available.
284+
*
285+
* @return
286+
* @since 3.0
287+
*/
288+
@Nullable
289+
Meta getMetaAnnotation() {
290+
return doFindAnnotation(Meta.class).orElse(null);
291+
}
292+
293+
/**
294+
* Returns the {@link org.springframework.data.jpa.repository.query.Meta} attributes to be applied.
295+
*
296+
* @return never {@literal null}.
297+
* @since 1.6
298+
*/
299+
public org.springframework.data.jpa.repository.query.Meta getQueryMetaAttributes() {
300+
301+
Meta meta = getMetaAnnotation();
302+
if (meta == null) {
303+
return new org.springframework.data.jpa.repository.query.Meta();
304+
}
305+
306+
org.springframework.data.jpa.repository.query.Meta metaAttributes = new org.springframework.data.jpa.repository.query.Meta();
307+
308+
if (StringUtils.hasText(meta.comment())) {
309+
metaAttributes.setComment(meta.comment());
310+
}
311+
312+
return metaAttributes;
313+
}
314+
262315
/**
263316
* Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
264317
* nor the attribute was specified.

0 commit comments

Comments
 (0)