Simplify package structure.

See #3830
This commit is contained in:
Mark Paluch
2025-03-28 12:02:07 +01:00
parent 22dfb1c22b
commit 4b0a83a97b
18 changed files with 101 additions and 78 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;

View File

@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.validation.constraints.Null;
import java.util.function.Function;
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;
@@ -35,7 +36,8 @@ record AotQueries(AotQuery result, AotQuery count) {
/**
* Derive a count query from the given query.
*/
public static AotQueries from(StringAotQuery query, @Null String countProjection, QueryEnhancerSelector selector) {
public static AotQueries from(StringAotQuery query, @Nullable String countProjection,
QueryEnhancerSelector selector) {
return from(query, StringAotQuery::getQuery, countProjection, selector);
}
@@ -43,7 +45,7 @@ record AotQueries(AotQuery result, AotQuery count) {
* Derive a count query from the given query.
*/
public static <T extends AotQuery> AotQueries from(T query, Function<T, DeclaredQuery> queryMapper,
@Null String countProjection, QueryEnhancerSelector selector) {
@Nullable String countProjection, QueryEnhancerSelector selector) {
DeclaredQuery underlyingQuery = queryMapper.apply(query);
QueryEnhancer queryEnhancer = selector.select(underlyingQuery).create(underlyingQuery);

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import java.util.List;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import java.lang.reflect.Method;
@@ -32,6 +32,7 @@ import org.springframework.data.repository.core.support.RepositoryFactoryBeanSup
import org.springframework.data.repository.query.ParametersSource;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.ValueExpressionDelegate;
import org.springframework.data.util.Lazy;
import org.springframework.util.ConcurrentLruCache;
/**
@@ -48,11 +49,11 @@ public class AotRepositoryFragmentSupport {
private final ProjectionFactory projectionFactory;
private final ConcurrentLruCache<DeclaredQuery, QueryEnhancer> enhancers;
private final Lazy<ConcurrentLruCache<DeclaredQuery, QueryEnhancer>> enhancers;
private final ConcurrentLruCache<String, ValueExpression> expressions;
private final Lazy<ConcurrentLruCache<String, ValueExpression>> expressions;
private final ConcurrentLruCache<Method, ValueEvaluationContextProvider> contextProviders;
private final Lazy<ConcurrentLruCache<Method, ValueEvaluationContextProvider>> contextProviders;
protected AotRepositoryFragmentSupport(QueryEnhancerSelector selector,
RepositoryFactoryBeanSupport.FragmentCreationContext context) {
@@ -66,10 +67,10 @@ public class AotRepositoryFragmentSupport {
this.repositoryMetadata = repositoryMetadata;
this.valueExpressions = valueExpressions;
this.projectionFactory = projectionFactory;
this.enhancers = new ConcurrentLruCache<>(32, query -> selector.select(query).create(query));
this.expressions = new ConcurrentLruCache<>(32, valueExpressions::parse);
this.contextProviders = new ConcurrentLruCache<>(32, it -> valueExpressions
.createValueContextProvider(new JpaParameters(ParametersSource.of(repositoryMetadata, it))));
this.enhancers = Lazy.of(() -> new ConcurrentLruCache<>(32, query -> selector.select(query).create(query)));
this.expressions = Lazy.of(() -> new ConcurrentLruCache<>(32, valueExpressions::parse));
this.contextProviders = Lazy.of(() -> new ConcurrentLruCache<>(32, it -> valueExpressions
.createValueContextProvider(new JpaParameters(ParametersSource.of(repositoryMetadata, it)))));
}
/**
@@ -82,7 +83,7 @@ public class AotRepositoryFragmentSupport {
*/
protected String rewriteQuery(DeclaredQuery query, Sort sort, Class<?> returnedType) {
QueryEnhancer queryStringEnhancer = this.enhancers.get(query);
QueryEnhancer queryStringEnhancer = this.enhancers.get().get(query);
return queryStringEnhancer.rewrite(new DefaultQueryRewriteInformation(sort,
ReturnedType.of(returnedType, repositoryMetadata.getDomainType(), projectionFactory)));
}
@@ -97,8 +98,8 @@ public class AotRepositoryFragmentSupport {
*/
protected @Nullable Object evaluateExpression(Method method, String expressionString, Object... args) {
ValueExpression expression = this.expressions.get(expressionString);
ValueEvaluationContextProvider contextProvider = this.contextProviders.get(method);
ValueExpression expression = this.expressions.get().get(expressionString);
ValueEvaluationContextProvider contextProvider = this.contextProviders.get().get(method);
return expression.evaluate(contextProvider.getEvaluationContext(args, expression.getExpressionDependencies()));
}

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
@@ -55,7 +55,6 @@ import org.springframework.util.StringUtils;
class JpaCodeBlocks {
/**
* @param context
* @return new {@link QueryBlockBuilder}.
*/
public static QueryBlockBuilder queryBuilder(AotQueryMethodGenerationContext context, JpaQueryMethod queryMethod) {
@@ -63,7 +62,6 @@ class JpaCodeBlocks {
}
/**
* @param context
* @return new {@link QueryExecutionBlockBuilder}.
*/
static QueryExecutionBlockBuilder executionBuilder(AotQueryMethodGenerationContext context,
@@ -79,9 +77,8 @@ class JpaCodeBlocks {
private final AotQueryMethodGenerationContext context;
private final JpaQueryMethod queryMethod;
private String queryVariableName = "query";
private AotQueries queries;
private @Nullable AotQueries queries;
private MergedAnnotation<QueryHints> queryHints = MergedAnnotation.missing();
private MergedAnnotation<org.springframework.data.jpa.repository.Query> query = MergedAnnotation.missing();
private @Nullable String sqlResultSetMapping;
private @Nullable Class<?> queryReturnType;
@@ -101,18 +98,6 @@ class JpaCodeBlocks {
return this;
}
public QueryBlockBuilder queryHints(MergedAnnotation<QueryHints> queryHints) {
this.queryHints = queryHints;
return this;
}
public QueryBlockBuilder query(MergedAnnotation<org.springframework.data.jpa.repository.Query> query) {
this.query = query;
return this;
}
public QueryBlockBuilder nativeQuery(MergedAnnotation<NativeQuery> nativeQuery) {
if (nativeQuery.isPresent()) {
@@ -121,6 +106,12 @@ class JpaCodeBlocks {
return this;
}
public QueryBlockBuilder queryHints(MergedAnnotation<QueryHints> queryHints) {
this.queryHints = queryHints;
return this;
}
public QueryBlockBuilder queryReturnType(@Nullable Class<?> queryReturnType) {
this.queryReturnType = queryReturnType;
return this;
@@ -142,7 +133,7 @@ class JpaCodeBlocks {
String queryStringNameVariableName = null;
if (queries.result() instanceof StringAotQuery sq) {
if (queries != null && queries.result() instanceof StringAotQuery sq) {
queryStringNameVariableName = "%sString".formatted(queryVariableName);
builder.addStatement("$T $L = $S", String.class, queryStringNameVariableName, sq.getQueryString());
@@ -157,9 +148,6 @@ class JpaCodeBlocks {
builder.addStatement("$T $L = $S", String.class, countQueryStringNameVariableName, sq.getQueryString());
}
// sorting
// TODO: refactor into sort builder
String sortParameterName = context.getSortParameterName();
if (sortParameterName == null && context.getPageableParameterName() != null) {
sortParameterName = "%s.getSort()".formatted(context.getPageableParameterName());
@@ -202,7 +190,7 @@ class JpaCodeBlocks {
builder.beginControlFlow("if ($L.isSorted())", sort);
builder.addStatement("$T declaredQuery = $T.$L($L)", DeclaredQuery.class, DeclaredQuery.class,
queries.isNative() ? "nativeQuery" : "jpqlQuery",
queries != null && queries.isNative() ? "nativeQuery" : "jpqlQuery",
queryString);
builder.addStatement("$L = rewriteQuery(declaredQuery, $L, $T.class)", queryString, sort, actualReturnType);
@@ -227,7 +215,7 @@ class JpaCodeBlocks {
builder.beginControlFlow("if ($L.isLimited())", limit);
builder.addStatement("$L.setMaxResults($L.max())", queryVariableName, limit);
builder.endControlFlow();
} else if (queries.result().isLimited()) {
} else if (queries != null && queries.result().isLimited()) {
builder.addStatement("$L.setMaxResults($L)", queryVariableName, queries.result().getLimit().max());
}
@@ -358,7 +346,7 @@ class JpaCodeBlocks {
Builder builder = CodeBlock.builder();
ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(context.getMethod());
var parameterNames = discoverer.getParameterNames(context.getMethod());
String expressionString = expr.expression().getExpressionString();
// re-wrap expression

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024-2025 the original author or authors.
* 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.
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQueryReference;
import jakarta.persistence.metamodel.Metamodel;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -57,6 +58,7 @@ import org.springframework.data.repository.core.support.RepositoryFactoryBeanSup
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.util.TypeInformation;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.TypeName;
import org.springframework.javapoet.TypeSpec;
@@ -76,13 +78,23 @@ import org.springframework.util.StringUtils;
*/
public class JpaRepositoryContributor extends RepositoryContributor {
private final AotMetamodel metaModel;
private final EntityManagerFactory emf;
private final Metamodel metaModel;
private final PersistenceProvider persistenceProvider;
public JpaRepositoryContributor(AotRepositoryContext repositoryContext) {
super(repositoryContext);
this.metaModel = new AotMetamodel(repositoryContext.getResolvedTypes());
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(metaModel.getEntityManagerFactory());
AotMetamodel amm = new AotMetamodel(repositoryContext.getResolvedTypes());
this.metaModel = amm;
this.emf = amm.getEntityManagerFactory();
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(amm.getEntityManagerFactory());
}
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory) {
super(repositoryContext);
this.emf = entityManagerFactory;
this.metaModel = entityManagerFactory.getMetamodel();
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(entityManagerFactory);
}
@Override
@@ -118,6 +130,17 @@ public class JpaRepositoryContributor extends RepositoryContributor {
return null;
}
ReturnedType returnedType = queryMethod.getResultProcessor().getReturnedType();
// no interface/dynamic projections for now.
if (returnedType.isProjecting() && returnedType.getReturnedType().isInterface()) {
return null;
}
if (queryMethod.getParameters().hasDynamicProjection()) {
return null;
}
// no KeysetScrolling for now.
if (queryMethod.getParameters().hasScrollPositionParameter()) {
return null;
@@ -125,9 +148,13 @@ public class JpaRepositoryContributor extends RepositoryContributor {
if (queryMethod.isModifyingQuery()) {
Class<?> returnType = repositoryInformation.getReturnType(method).getType();
if (!ClassUtils.isVoidType(returnType)
&& !JpaCodeBlocks.QueryExecutionBlockBuilder.returnsModifying(returnType)) {
TypeInformation<?> returnType = repositoryInformation.getReturnType(method);
boolean returnsCount = JpaCodeBlocks.QueryExecutionBlockBuilder.returnsModifying(returnType.getType());
boolean isVoid = ClassUtils.isVoidType(returnType.getType());
if (!returnsCount && !isVoid) {
return null;
}
}
@@ -140,15 +167,14 @@ public class JpaRepositoryContributor extends RepositoryContributor {
MergedAnnotation<NativeQuery> nativeQuery = context.getAnnotation(NativeQuery.class);
MergedAnnotation<QueryHints> queryHints = context.getAnnotation(QueryHints.class);
MergedAnnotation<Modifying> modifying = context.getAnnotation(Modifying.class);
ReturnedType returnedType = context.getReturnedType();
body.add(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName())));
AotQueries aotQueries = getQueries(context, query, selector, queryMethod, returnedType);
body.add(JpaCodeBlocks.queryBuilder(context, queryMethod).filter(aotQueries)
.queryReturnType(getQueryReturnType(aotQueries.result(), returnedType, context)).query(query)
.nativeQuery(nativeQuery).queryHints(queryHints).build());
.queryReturnType(getQueryReturnType(aotQueries.result(), returnedType, context)).nativeQuery(nativeQuery)
.queryHints(queryHints).build());
body.add(
JpaCodeBlocks.executionBuilder(context, queryMethod).modifying(modifying).query(aotQueries.result()).build());
@@ -178,8 +204,7 @@ public class JpaRepositoryContributor extends RepositoryContributor {
UnaryOperator<String> operator = s -> s.replaceAll("#\\{#entityName}", domainType.getName());
boolean isNative = query.getBoolean("nativeQuery");
Function<String, StringAotQuery> queryFunction = isNative ? StringAotQuery::nativeQuery
: StringAotQuery::jpqlQuery;
Function<String, StringAotQuery> queryFunction = isNative ? StringAotQuery::nativeQuery : StringAotQuery::jpqlQuery;
queryFunction = operator.andThen(queryFunction);
String queryString = query.getString("value");
@@ -252,8 +277,6 @@ public class JpaRepositoryContributor extends RepositoryContributor {
returnedType.getReturnedType(), returnedType.getTypeToRead(), void.class, null, Long.class, Integer.class,
Long.TYPE, Integer.TYPE, Number.class);
EntityManagerFactory emf = metaModel.getEntityManagerFactory();
for (Class<?> candidate : candidates) {
Map<String, ? extends TypedQueryReference<?>> namedQueries = emf.getNamedQueries(candidate);

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import java.util.List;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import java.util.List;

View File

@@ -0,0 +1,5 @@
/**
* Ahead-of-Time (AOT) generation for Spring Data JPA repositories.
*/
@org.jspecify.annotations.NullMarked
package org.springframework.data.jpa.repository.aot;

View File

@@ -52,7 +52,7 @@ import org.springframework.dao.DataAccessException;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.aot.AotContext;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.aot.generated.JpaRepositoryContributor;
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor;
import org.springframework.data.jpa.repository.support.DefaultJpaContext;
import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor;
import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import static org.assertj.core.api.Assertions.*;
@@ -399,9 +399,10 @@ class JpaRepositoryContributorIntegrationTests {
assertThat(page.getContent()).extracting(UserDtoProjection::getEmailAddress).containsExactly("han@smuggler.net",
"kylo@new-empire.com");
// TODO
Page<UserDtoProjection> noResults = fragment.findUserProjectionByLastnameStartingWith("a",
PageRequest.of(0, 2, Sort.by("emailAddress")));
assertThat(noResults).isEmpty();
}
// modifying
@@ -419,6 +420,13 @@ class JpaRepositoryContributorIntegrationTests {
assertThat(yodaShouldBeGone).isNull();
}
@Test
void shouldOmitAnnotatedDeleteReturningDomainType() {
assertThatException().isThrownBy(() -> fragment.deleteAnnotatedQueryByEmailAddress("foo"))
.withRootCauseInstanceOf(NoSuchMethodException.class);
}
@Test
void shouldApplyModifying() {
@@ -456,11 +464,11 @@ class JpaRepositoryContributorIntegrationTests {
void todo() {
// entity graphs
// interface projections
// dynamic projections
// class type parameter
// entity graphs
// synthetic parameters (keyset scrolling! yuck!)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import java.lang.reflect.Method;
import java.util.Set;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2024 the original author or authors.
* 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.Entity;
import jakarta.persistence.MappedSuperclass;

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
/**
* @author Christoph Strobl

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.aot.generated;
package org.springframework.data.jpa.repository.aot;
import jakarta.persistence.QueryHint;
@@ -131,8 +131,7 @@ interface UserRepository extends CrudRepository<User, Integer> {
User deleteByEmailAddress(String username);
Long deleteReturningDeleteCountByEmailAddress(String username);
// cannot generate delete and return a domain object
@Modifying
@Query("delete from User u where u.emailAddress = ?1")
User deleteAnnotatedQueryByEmailAddress(String username);

View File

@@ -24,6 +24,7 @@ import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
@@ -169,7 +170,7 @@ class JpaQueryExecutionUnitTests {
@Test
void modifyingExecutionRejectsNonIntegerOrVoidReturnType() {
when(method.getReturnType()).thenReturn((Class) Long.class);
when(method.getReturnType()).thenReturn((Class) BigDecimal.class);
assertThatIllegalArgumentException().isThrownBy(() -> new ModifyingExecution(method, em));
}

View File

@@ -300,10 +300,6 @@ public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecifi
// DATAJPA-460
List<User> deleteByLastname(String lastname);
@Modifying
@Query("delete from User u where u.emailAddress = ?1")
User deleteAnnotatedQueryByEmailAddress(String username);
/**
* Explicitly mapped to a procedure with name "plus1inout" in database.
*/