@@ -88,16 +88,31 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.javacrumbs.json-unit</groupId>
|
||||
<artifactId>json-unit-assertj</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>hsqldb</artifactId>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package org.springframework.data.jpa.repository.aot;
|
||||
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -23,6 +25,7 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.jpa.repository.query.DeclaredQuery;
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancer;
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
|
||||
import org.springframework.data.repository.aot.generate.QueryMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -68,4 +71,52 @@ record AotQueries(AotQuery result, AotQuery count) {
|
||||
return result().isNative();
|
||||
}
|
||||
|
||||
public QueryMetadata toMetadata(boolean paging) {
|
||||
return new AotQueryMetadata(paging);
|
||||
}
|
||||
|
||||
/**
|
||||
* String and Named Query-based {@link QueryMetadata}.
|
||||
*/
|
||||
private class AotQueryMetadata implements QueryMetadata {
|
||||
|
||||
private final boolean paging;
|
||||
|
||||
AotQueryMetadata(boolean paging) {
|
||||
this.paging = paging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
|
||||
Map<String, Object> serialized = new LinkedHashMap<>();
|
||||
|
||||
if (result() instanceof NamedAotQuery nq) {
|
||||
|
||||
serialized.put("name", nq.getName());
|
||||
serialized.put("query", nq.getQueryString());
|
||||
}
|
||||
|
||||
if (result() instanceof StringAotQuery sq) {
|
||||
serialized.put("query", sq.getQueryString());
|
||||
}
|
||||
|
||||
if (paging) {
|
||||
|
||||
if (count() instanceof NamedAotQuery nq) {
|
||||
|
||||
serialized.put("count-name", nq.getName());
|
||||
serialized.put("count-query", nq.getQueryString());
|
||||
}
|
||||
|
||||
if (count() instanceof StringAotQuery sq) {
|
||||
serialized.put("count-query", sq.getQueryString());
|
||||
}
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityManagerFactory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
@@ -31,10 +34,12 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
import org.springframework.data.jpa.repository.query.JpaParameters;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
|
||||
import org.springframework.data.jpa.repository.query.Procedure;
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
|
||||
import org.springframework.data.repository.aot.generate.AotRepositoryConstructorBuilder;
|
||||
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata;
|
||||
import org.springframework.data.repository.aot.generate.MethodContributor;
|
||||
import org.springframework.data.repository.aot.generate.QueryMetadata;
|
||||
import org.springframework.data.repository.aot.generate.RepositoryContributor;
|
||||
import org.springframework.data.repository.config.AotRepositoryContext;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
@@ -46,6 +51,7 @@ import org.springframework.javapoet.CodeBlock;
|
||||
import org.springframework.javapoet.TypeName;
|
||||
import org.springframework.javapoet.TypeSpec;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* JPA-specific {@link RepositoryContributor} contributing an AOT repository fragment using the {@link EntityManager}
|
||||
@@ -113,20 +119,50 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
|
||||
// no stored procedures for now.
|
||||
if (queryMethod.isProcedureQuery()) {
|
||||
|
||||
Procedure procedure = AnnotatedElementUtils.findMergedAnnotation(method, Procedure.class);
|
||||
|
||||
MethodContributor.QueryMethodMetadataContributorBuilder<JpaQueryMethod> builder = MethodContributor
|
||||
.forQueryMethod(queryMethod);
|
||||
|
||||
|
||||
if (procedure != null) {
|
||||
|
||||
if (StringUtils.hasText(procedure.name())) {
|
||||
return builder.metadataOnly(new NamedStoredProcedureMetadata(procedure.name()));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(procedure.procedureName())) {
|
||||
return builder.metadataOnly(new StoredProcedureMetadata(procedure.procedureName()));
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(procedure.value())) {
|
||||
return builder.metadataOnly(new StoredProcedureMetadata(procedure.value()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Better fallback.
|
||||
return null;
|
||||
}
|
||||
|
||||
ReturnedType returnedType = queryMethod.getResultProcessor().getReturnedType();
|
||||
JpaParameters parameters = queryMethod.getParameters();
|
||||
|
||||
MergedAnnotation<Query> query = MergedAnnotations.from(method).get(Query.class);
|
||||
|
||||
AotQueries aotQueries = queriesFactory.createQueries(repositoryInformation, query, selector, queryMethod,
|
||||
returnedType);
|
||||
|
||||
// no KeysetScrolling for now.
|
||||
if (parameters.hasScrollPositionParameter()) {
|
||||
return null;
|
||||
return MethodContributor.forQueryMethod(queryMethod)
|
||||
.metadataOnly(aotQueries.toMetadata(queryMethod.isPageQuery()));
|
||||
}
|
||||
|
||||
// no dynamic projections.
|
||||
if (parameters.hasDynamicProjection()) {
|
||||
return null;
|
||||
return MethodContributor.forQueryMethod(queryMethod)
|
||||
.metadataOnly(aotQueries.toMetadata(queryMethod.isPageQuery()));
|
||||
}
|
||||
|
||||
if (queryMethod.isModifyingQuery()) {
|
||||
@@ -138,15 +174,16 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
boolean isVoid = ClassUtils.isVoidType(returnType.getType());
|
||||
|
||||
if (!returnsCount && !isVoid) {
|
||||
return null;
|
||||
return MethodContributor.forQueryMethod(queryMethod)
|
||||
.metadataOnly(aotQueries.toMetadata(queryMethod.isPageQuery()));
|
||||
}
|
||||
}
|
||||
|
||||
return MethodContributor.forQueryMethod(queryMethod).contribute(context -> {
|
||||
return MethodContributor.forQueryMethod(queryMethod).withMetadata(aotQueries.toMetadata(queryMethod.isPageQuery()))
|
||||
.contribute(context -> {
|
||||
|
||||
CodeBlock.Builder body = CodeBlock.builder();
|
||||
|
||||
MergedAnnotation<Query> query = context.getAnnotation(Query.class);
|
||||
MergedAnnotation<NativeQuery> nativeQuery = context.getAnnotation(NativeQuery.class);
|
||||
MergedAnnotation<QueryHints> queryHints = context.getAnnotation(QueryHints.class);
|
||||
MergedAnnotation<EntityGraph> entityGraph = context.getAnnotation(EntityGraph.class);
|
||||
@@ -154,7 +191,6 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
|
||||
body.add(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName())));
|
||||
|
||||
AotQueries aotQueries = queriesFactory.createQueries(context, query, selector, queryMethod, returnedType);
|
||||
AotEntityGraph aotEntityGraph = entityGraphLookup.findEntityGraph(entityGraph, repositoryInformation,
|
||||
returnedType, queryMethod);
|
||||
|
||||
@@ -170,4 +206,20 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
});
|
||||
}
|
||||
|
||||
record StoredProcedureMetadata(String procedure) implements QueryMetadata {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
return Map.of("procedure", procedure());
|
||||
}
|
||||
}
|
||||
|
||||
record NamedStoredProcedureMetadata(String procedureName) implements QueryMetadata {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
return Map.of("procedure-name", procedureName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ import org.springframework.data.jpa.repository.query.PreprocessedQuery;
|
||||
class NamedAotQuery extends AotQuery {
|
||||
|
||||
private final String name;
|
||||
private final DeclaredQuery queryString;
|
||||
private final DeclaredQuery query;
|
||||
|
||||
private NamedAotQuery(String name, DeclaredQuery queryString, List<ParameterBinding> parameterBindings) {
|
||||
super(parameterBindings);
|
||||
this.name = name;
|
||||
this.queryString = queryString;
|
||||
this.query = queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,13 +51,17 @@ class NamedAotQuery extends AotQuery {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DeclaredQuery getQueryString() {
|
||||
return queryString;
|
||||
public DeclaredQuery getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public String getQueryString() {
|
||||
return getQuery().getQueryString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNative() {
|
||||
return queryString.isNative();
|
||||
return query.isNative();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,11 +73,11 @@ class QueriesFactory {
|
||||
* @param returnedType
|
||||
* @return
|
||||
*/
|
||||
public AotQueries createQueries(AotQueryMethodGenerationContext context, MergedAnnotation<Query> query,
|
||||
public AotQueries createQueries(RepositoryInformation repositoryInformation, MergedAnnotation<Query> query,
|
||||
QueryEnhancerSelector selector, JpaQueryMethod queryMethod, ReturnedType returnedType) {
|
||||
|
||||
if (query.isPresent() && StringUtils.hasText(query.getString("value"))) {
|
||||
return buildStringQuery(context.getRepositoryInformation().getDomainType(), returnedType, selector, query,
|
||||
return buildStringQuery(repositoryInformation.getDomainType(), returnedType, selector, query,
|
||||
queryMethod);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class QueriesFactory {
|
||||
return buildNamedQuery(returnedType, selector, namedQuery, query, queryMethod);
|
||||
}
|
||||
|
||||
return buildPartTreeQuery(returnedType, context, query, queryMethod);
|
||||
return buildPartTreeQuery(returnedType, repositoryInformation, query, queryMethod);
|
||||
}
|
||||
|
||||
private AotQueries buildStringQuery(Class<?> domainType, ReturnedType returnedType, QueryEnhancerSelector selector,
|
||||
@@ -159,7 +159,7 @@ class QueriesFactory {
|
||||
|
||||
String countProjection = query.isPresent() ? query.getString("countProjection") : null;
|
||||
return AotQueries.from(aotQuery, it -> {
|
||||
return StringAotQuery.of(aotQuery.getQueryString()).getQuery();
|
||||
return StringAotQuery.of(aotQuery.getQuery()).getQuery();
|
||||
}, countProjection, selector);
|
||||
}
|
||||
|
||||
@@ -197,10 +197,10 @@ class QueriesFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
private AotQueries buildPartTreeQuery(ReturnedType returnedType, AotQueryMethodGenerationContext context,
|
||||
private AotQueries buildPartTreeQuery(ReturnedType returnedType, RepositoryInformation repositoryInformation,
|
||||
MergedAnnotation<Query> query, JpaQueryMethod queryMethod) {
|
||||
|
||||
PartTree partTree = new PartTree(context.getMethod().getName(), context.getRepositoryInformation().getDomainType());
|
||||
PartTree partTree = new PartTree(queryMethod.getName(), repositoryInformation.getDomainType());
|
||||
// TODO make configurable
|
||||
JpqlQueryTemplates templates = JpqlQueryTemplates.UPPER;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.QueryCreationListener;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
|
||||
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
@@ -298,7 +299,8 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
getEntityInformation(metadata.getDomainType()), entityManager, resolver, crudMethodMetadata);
|
||||
invokeAwareMethods(querydslJpaPredicateExecutor);
|
||||
|
||||
return RepositoryFragments.just(querydslJpaPredicateExecutor);
|
||||
return RepositoryFragments
|
||||
.of(RepositoryFragment.implemented(QuerydslPredicateExecutor.class, querydslJpaPredicateExecutor));
|
||||
}
|
||||
|
||||
return RepositoryFragments.empty();
|
||||
|
||||
@@ -20,11 +20,11 @@ import jakarta.persistence.PersistenceContext;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.data.jpa.repository.query.EscapeCharacter;
|
||||
@@ -54,7 +54,7 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
private @Nullable EntityManager entityManager;
|
||||
private EntityPathResolver entityPathResolver;
|
||||
private EntityPathResolver entityPathResolver = SimpleEntityPathResolver.INSTANCE;
|
||||
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
|
||||
private @Nullable JpaQueryMethodFactory queryMethodFactory;
|
||||
private @Nullable Function<BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource;
|
||||
|
||||
@@ -103,21 +103,21 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
em.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderWithoutArguments() {
|
||||
|
||||
List<User> users = fragment.findUserNoArgumentsBy();
|
||||
assertThat(users).hasSize(7).hasOnlyElementsOfType(User.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testFindDerivedQuerySingleEntity() {
|
||||
|
||||
User user = fragment.findOneByEmailAddress("luke@jedi.org");
|
||||
assertThat(user.getLastname()).isEqualTo("Skywalker");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testFindDerivedFinderOptionalEntity() {
|
||||
|
||||
Optional<User> user = fragment.findOptionalOneByEmailAddress("yoda@jedi.org");
|
||||
@@ -125,21 +125,21 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
.hasValueSatisfying(it -> assertThat(it).extracting(User::getFirstname).isEqualTo("Yoda"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedCount() {
|
||||
|
||||
Long value = fragment.countUsersByLastname("Skywalker");
|
||||
assertThat(value).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedExists() {
|
||||
|
||||
Boolean exists = fragment.existsUserByLastname("Skywalker");
|
||||
assertThat(exists).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningList() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWith("S");
|
||||
@@ -147,7 +147,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "han@smuggler.net");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldReturnStream() {
|
||||
|
||||
Stream<User> users = fragment.streamByLastnameLike("S%");
|
||||
@@ -155,14 +155,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "han@smuggler.net");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testLimitedDerivedFinder() {
|
||||
|
||||
List<User> users = fragment.findTop2ByLastnameStartingWith("S");
|
||||
assertThat(users).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testSortedDerivedFinder() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWithOrderByEmailAddress("S");
|
||||
@@ -170,14 +170,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderWithLimitArgument() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWith("S", Limit.of(2));
|
||||
assertThat(users).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderWithSort() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWith("S", Sort.by("emailAddress"));
|
||||
@@ -185,21 +185,21 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderWithSortAndLimit() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWith("S", Sort.by("emailAddress"), Limit.of(2));
|
||||
assertThat(users).extracting(User::getEmailAddress).containsExactly("han@smuggler.net", "kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningListWithPageable() {
|
||||
|
||||
List<User> users = fragment.findByLastnameStartingWith("S", PageRequest.of(0, 2, Sort.by("emailAddress")));
|
||||
assertThat(users).extracting(User::getEmailAddress).containsExactly("han@smuggler.net", "kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningPage() {
|
||||
|
||||
Page<User> page = fragment.findPageOfUsersByLastnameStartingWith("S",
|
||||
@@ -211,7 +211,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningSlice() {
|
||||
|
||||
Slice<User> slice = fragment.findSliceOfUserByLastnameStartingWith("S",
|
||||
@@ -223,14 +223,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderReturningSingleValueWithQuery() {
|
||||
|
||||
User user = fragment.findAnnotatedQueryByEmailAddress("yoda@jedi.org");
|
||||
assertThat(user).isNotNull().extracting(User::getFirstname).isEqualTo("Yoda");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderReturningListWithQuery() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastname("S");
|
||||
@@ -238,7 +238,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderUsingNamedParameterPlaceholderReturningListWithQuery() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastnameParameter("S");
|
||||
@@ -246,7 +246,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyAnnotatedLikeStartsEnds() {
|
||||
|
||||
// start with case
|
||||
@@ -260,7 +260,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"chewie@smuggler.net", "yoda@jedi.org");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedMultilineFinderWithQuery() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedMultilineQueryByLastname("S");
|
||||
@@ -268,14 +268,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderWithQueryAndLimit() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastname("S", Limit.of(2));
|
||||
assertThat(users).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderWithQueryAndSort() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastname("S", Sort.by("emailAddress"));
|
||||
@@ -283,21 +283,21 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderWithQueryLimitAndSort() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastname("S", Limit.of(2), Sort.by("emailAddress"));
|
||||
assertThat(users).extracting(User::getEmailAddress).containsExactly("han@smuggler.net", "kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderReturningListWithPageable() {
|
||||
|
||||
List<User> users = fragment.findAnnotatedQueryByLastname("S", PageRequest.of(0, 2, Sort.by("emailAddress")));
|
||||
assertThat(users).extracting(User::getEmailAddress).containsExactly("han@smuggler.net", "kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderReturningPage() {
|
||||
|
||||
Page<User> page = fragment.findAnnotatedQueryPageOfUsersByLastname("S",
|
||||
@@ -309,7 +309,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testPagingAnnotatedQueryWithSort() {
|
||||
|
||||
Page<User> page = fragment.findAnnotatedQueryPageWithStaticSort("S", PageRequest.of(0, 2, Sort.unsorted()));
|
||||
@@ -320,7 +320,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testAnnotatedFinderReturningSlice() {
|
||||
|
||||
Slice<User> slice = fragment.findAnnotatedQuerySliceOfUsersByLastname("S",
|
||||
@@ -331,7 +331,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldResolveTemplatedQuery() {
|
||||
|
||||
User user = fragment.findTemplatedByEmailAddress("han@smuggler.net");
|
||||
@@ -340,7 +340,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getFirstname()).isEqualTo("Han");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldEvaluateExpressionByName() {
|
||||
|
||||
User user = fragment.findValueExpressionNamedByEmailAddress("han@smuggler.net");
|
||||
@@ -349,7 +349,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getFirstname()).isEqualTo("Han");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldEvaluateExpressionByPosition() {
|
||||
|
||||
User user = fragment.findValueExpressionPositionalByEmailAddress("han@smuggler.net");
|
||||
@@ -358,7 +358,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getFirstname()).isEqualTo("Han");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningListOfProjections() {
|
||||
|
||||
List<UserDtoProjection> users = fragment.findUserProjectionByLastnameStartingWith("S");
|
||||
@@ -366,7 +366,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedFinderReturningPageOfProjections() {
|
||||
|
||||
Page<UserDtoProjection> page = fragment.findUserProjectionByLastnameStartingWith("S",
|
||||
@@ -383,7 +383,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(noResults).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplySqlResultSetMapping() {
|
||||
|
||||
User.EmailDto result = fragment.findEmailDtoByNativeQuery(kylo.getId());
|
||||
@@ -391,7 +391,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getOne()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyNamedDto() {
|
||||
|
||||
// named queries cannot be rewritten
|
||||
@@ -399,7 +399,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
.isThrownBy(() -> fragment.findNamedDtoEmailAddress(kylo.getEmailAddress()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyDerivedDto() {
|
||||
|
||||
UserRepository.Names names = fragment.findDtoByEmailAddress(kylo.getEmailAddress());
|
||||
@@ -408,7 +408,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(names.firstname()).isEqualTo(kylo.getFirstname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyDerivedDtoPage() {
|
||||
|
||||
Page<UserRepository.Names> names = fragment.findDtoPageByEmailAddress(kylo.getEmailAddress(), PageRequest.of(0, 1));
|
||||
@@ -417,7 +417,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(names.getContent().get(0).lastname()).isEqualTo(kylo.getLastname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyAnnotatedDto() {
|
||||
|
||||
UserRepository.Names names = fragment.findAnnotatedDtoEmailAddress(kylo.getEmailAddress());
|
||||
@@ -426,7 +426,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(names.firstname()).isEqualTo(kylo.getFirstname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyAnnotatedDtoPage() {
|
||||
|
||||
Page<UserRepository.Names> names = fragment.findAnnotatedDtoPageByEmailAddress(kylo.getEmailAddress(),
|
||||
@@ -436,7 +436,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(names.getContent().get(0).lastname()).isEqualTo(kylo.getLastname());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyDerivedQueryInterfaceProjection() {
|
||||
|
||||
UserRepository.EmailOnly result = fragment.findEmailProjectionById(kylo.getId());
|
||||
@@ -444,7 +444,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyInterfaceProjectionPage() {
|
||||
|
||||
Page<UserRepository.EmailOnly> result = fragment.findProjectedPageByEmailAddress(kylo.getEmailAddress(),
|
||||
@@ -454,7 +454,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getContent().get(0).getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyInterfaceProjectionSlice() {
|
||||
|
||||
Slice<UserRepository.EmailOnly> result = fragment.findProjectedSliceByEmailAddress(kylo.getEmailAddress(),
|
||||
@@ -464,7 +464,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getContent().get(0).getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyInterfaceProjectionToDerivedQueryStream() {
|
||||
|
||||
Stream<UserRepository.EmailOnly> result = fragment.streamProjectedByEmailAddress(kylo.getEmailAddress());
|
||||
@@ -472,7 +472,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result).hasSize(1).map(UserRepository.EmailOnly::getEmailAddress).contains(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyAnnotatedQueryInterfaceProjection() {
|
||||
|
||||
UserRepository.EmailOnly result = fragment.findAnnotatedEmailProjectionByEmailAddress(kylo.getEmailAddress());
|
||||
@@ -480,7 +480,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyAnnotatedInterfaceProjectionQueryPage() {
|
||||
|
||||
Page<UserRepository.EmailOnly> result = fragment.findAnnotatedProjectedPageByEmailAddress(kylo.getEmailAddress(),
|
||||
@@ -490,7 +490,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getContent().get(0).getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyNativeInterfaceProjection() {
|
||||
|
||||
UserRepository.EmailOnly result = fragment.findEmailProjectionByNativeQuery(kylo.getId());
|
||||
@@ -498,7 +498,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyNamedQueryInterfaceProjection() {
|
||||
|
||||
UserRepository.EmailOnly result = fragment.findNamedProjectionEmailAddress(kylo.getEmailAddress());
|
||||
@@ -506,7 +506,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result.getEmailAddress()).isEqualTo(kylo.getEmailAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void testDerivedDeleteSingle() {
|
||||
|
||||
User result = fragment.deleteByEmailAddress("yoda@jedi.org");
|
||||
@@ -519,14 +519,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(yodaShouldBeGone).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldOmitAnnotatedDeleteReturningDomainType() {
|
||||
|
||||
assertThatException().isThrownBy(() -> fragment.deleteAnnotatedQueryByEmailAddress("foo"))
|
||||
.withRootCauseInstanceOf(NoSuchMethodException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyModifying() {
|
||||
|
||||
int affected = fragment.renameAllUsersTo("Jones");
|
||||
@@ -539,7 +539,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(yodaShouldBeGone).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void nativeQuery() {
|
||||
|
||||
Page<String> page = fragment.findByNativeQueryWithPageable(PageRequest.of(0, 2));
|
||||
@@ -549,14 +549,14 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(page.getContent()).containsExactly("Anakin", "Ben");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldUseNamedQuery() {
|
||||
|
||||
User user = fragment.findByEmailAddress("luke@jedi.org");
|
||||
assertThat(user.getLastname()).isEqualTo("Skywalker");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldUseNamedQueryAndDeriveCountQuery() {
|
||||
|
||||
Page<User> user = fragment.findPagedByEmailAddress(PageRequest.of(0, 1), "luke@jedi.org");
|
||||
@@ -565,7 +565,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getTotalElements()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldUseNamedQueryAndProvidedCountQuery() {
|
||||
|
||||
Page<User> user = fragment.findPagedWithCountByEmailAddress(PageRequest.of(0, 1), "luke@jedi.org");
|
||||
@@ -574,7 +574,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getTotalElements()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldUseNamedQueryAndNamedCountQuery() {
|
||||
|
||||
Page<User> user = fragment.findPagedWithNamedCountByEmailAddress(PageRequest.of(0, 1), "luke@jedi.org");
|
||||
@@ -583,13 +583,13 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(user.getTotalElements()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyQueryHints() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> fragment.findHintedByLastname("Skywalker"))
|
||||
.withMessageContaining("No enum constant jakarta.persistence.CacheStoreMode.foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyNamedEntityGraph() {
|
||||
|
||||
User chewie = fragment.findWithNamedEntityGraphByFirstname("Chewbacca");
|
||||
@@ -598,7 +598,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(chewie.getRoles()).isNotInstanceOf(HibernateProxy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyDeclaredEntityGraph() {
|
||||
|
||||
User chewie = fragment.findWithDeclaredEntityGraphByFirstname("Chewbacca");
|
||||
@@ -610,7 +610,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(han.getManager()).isInstanceOf(HibernateProxy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldQuerySubtype() {
|
||||
|
||||
SpecialUser snoopy = new SpecialUser();
|
||||
@@ -625,7 +625,7 @@ class JpaRepositoryContributorIntegrationTests {
|
||||
assertThat(result).isInstanceOf(SpecialUser.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // GH-3830
|
||||
void shouldApplyQueryRewriter() {
|
||||
|
||||
User result = fragment.findAndApplyQueryRewriter(kylo.getEmailAddress());
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2025 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.aot;
|
||||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link UserRepository} JSON metadata.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@SpringJUnitConfig(classes = JpaRepositoryMetadataIntegrationTests.JpaRepositoryContributorConfiguration.class)
|
||||
@Transactional
|
||||
class JpaRepositoryMetadataIntegrationTests {
|
||||
|
||||
@Autowired AbstractApplicationContext context;
|
||||
|
||||
@Configuration
|
||||
static class JpaRepositoryContributorConfiguration extends AotFragmentTestConfigurationSupport {
|
||||
public JpaRepositoryContributorConfiguration() {
|
||||
super(UserRepository.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentBase() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).isObject() //
|
||||
.containsEntry("name", UserRepository.class.getName()) //
|
||||
.containsEntry("module", "") // TODO: JPA should be here
|
||||
.containsEntry("type", "IMPERATIVE");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentDerivedQuery() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[0]").isObject().containsEntry("name", "countUsersByLastname");
|
||||
assertThatJson(json).inPath("$.methods[0].query").isObject().containsEntry("query",
|
||||
"SELECT COUNT(u) FROM org.springframework.data.jpa.domain.sample.User u WHERE u.lastname = ?1");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentPagedQuery() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findAndApplyQueryRewriter')].query").isArray().element(1)
|
||||
.isObject().containsEntry("query", "select u from OTHER u where u.emailAddress = ?1")
|
||||
.containsEntry("count-query", "select count(u) from OTHER u where u.emailAddress = ?1");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentQueryWithExpression() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findValueExpressionNamedByEmailAddress')].query").isArray()
|
||||
.first().isObject().containsEntry("query", "select u from User u where u.emailAddress = :__$synthetic$__1");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentNamedQuery() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'findPagedWithNamedCountByEmailAddress')].query").isArray()
|
||||
.first().isObject().containsEntry("name", "User.findByEmailAddress")
|
||||
.containsEntry("query", "SELECT u FROM User u WHERE u.emailAddress = ?1")
|
||||
.containsEntry("count-name", "User.findByEmailAddress.count-provided")
|
||||
.containsEntry("count-query", "SELECT count(u) FROM User u WHERE u.emailAddress = ?1");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentNamedProcedure() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'namedProcedure')].query").isArray().first().isObject()
|
||||
.containsEntry("procedure-name", "User.plus1IO");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentProvidedProcedure() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'providedProcedure')].query").isArray().first().isObject()
|
||||
.containsEntry("procedure", "sp_add");
|
||||
}
|
||||
|
||||
@Test // GH-3830
|
||||
void shouldDocumentBaseFragment() throws IOException {
|
||||
|
||||
Resource resource = getResource();
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'existsById')].fragment").isArray().first().isObject()
|
||||
.containsEntry("fragment", "org.springframework.data.jpa.repository.support.SimpleJpaRepository");
|
||||
}
|
||||
|
||||
private Resource getResource() {
|
||||
|
||||
String location = UserRepository.class.getPackageName().replace('.', '/') + "/"
|
||||
+ UserRepository.class.getSimpleName() + ".json";
|
||||
return new UrlResource(context.getBeanFactory().getBeanClassLoader().getResource(location));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -107,7 +107,12 @@ class StubRepositoryInformation implements RepositoryInformation {
|
||||
|
||||
@Override
|
||||
public boolean isQueryMethod(Method method) {
|
||||
return false;
|
||||
|
||||
if (isBaseClassMethod(method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,4 +129,10 @@ class StubRepositoryInformation implements RepositoryInformation {
|
||||
public Method getTargetClassMethod(Method method) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryComposition getRepositoryComposition() {
|
||||
return baseComposition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ import org.springframework.data.jpa.repository.NativeQuery;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryHints;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.jpa.repository.query.Procedure;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
@@ -236,6 +238,15 @@ interface UserRepository extends CrudRepository<User, Integer> {
|
||||
@Query(value = "select u from OTHER u where u.emailAddress = ?1", queryRewriter = MyQueryRewriter.class)
|
||||
Page<User> findAndApplyQueryRewriter(String emailAddress, Pageable pageable);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Unsupported: Procedures
|
||||
// -------------------------------------------------------------------------
|
||||
@Procedure(name = "User.plus1IO") // Named
|
||||
Integer namedProcedure(@Param("arg") Integer arg);
|
||||
|
||||
@Procedure(value = "sp_add") // Stored procedure
|
||||
Integer providedProcedure(@Param("arg") Integer arg);
|
||||
|
||||
interface EmailOnly {
|
||||
String getEmailAddress();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user