Skip to content

Commit e890fd7

Browse files
committed
Polishing.
Fix Like with starts/ends, use proper parameter origins instead of assuming binding name matches parameter names. Simplify binding block. See #3830
1 parent 52e453c commit e890fd7

File tree

7 files changed

+88
-57
lines changed

7 files changed

+88
-57
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/generated/AotQueryCreator.java

-34
This file was deleted.

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/generated/JpaCodeBlocks.java

+33-15
Original file line numberDiff line numberDiff line change
@@ -226,31 +226,49 @@ private CodeBlock createQuery(String queryVariableName, String queryStringNameVa
226226
for (ParameterBinding binding : query.getParameterBindings()) {
227227

228228
Object prepare = binding.prepare("s");
229+
Object parameterIdentifier = getParameterName(binding.getIdentifier());
230+
String valueFormat = parameterIdentifier instanceof CharSequence ? "$S" : "$L";
229231

230232
if (prepare instanceof String prepared && !prepared.equals("s")) {
233+
231234
String format = prepared.replaceAll("%", "%%").replace("s", "%s");
232-
if (binding.getIdentifier().hasPosition()) {
233-
builder.addStatement("$L.setParameter($L, $S.formatted($L))", queryVariableName,
234-
binding.getIdentifier().getPosition(), format,
235-
context.getParameterNameOfPosition(binding.getIdentifier().getPosition() - 1));
236-
} else {
237-
builder.addStatement("$L.setParameter($S, $S.formatted($L))", queryVariableName,
238-
binding.getIdentifier().getName(), format, binding.getIdentifier().getName());
239-
}
235+
builder.addStatement("$L.setParameter(%s, $S.formatted($L))".formatted(valueFormat), queryVariableName,
236+
parameterIdentifier, format, getParameter(binding.getOrigin()));
240237
} else {
241-
if (binding.getIdentifier().hasPosition()) {
242-
builder.addStatement("$L.setParameter($L, $L)", queryVariableName, binding.getIdentifier().getPosition(),
243-
context.getParameterNameOfPosition(binding.getIdentifier().getPosition() - 1));
244-
} else {
245-
builder.addStatement("$L.setParameter($S, $L)", queryVariableName, binding.getIdentifier().getName(),
246-
binding.getIdentifier().getName());
247-
}
238+
builder.addStatement("$L.setParameter(%s, $L)".formatted(valueFormat), queryVariableName, parameterIdentifier,
239+
getParameter(binding.getOrigin()));
248240
}
249241
}
250242

251243
return builder.build();
252244
}
253245

246+
private Object getParameterName(ParameterBinding.BindingIdentifier identifier) {
247+
248+
if (identifier.hasPosition()) {
249+
return identifier.getPosition();
250+
}
251+
252+
return identifier.getName();
253+
254+
}
255+
256+
private Object getParameter(ParameterBinding.ParameterOrigin origin) {
257+
258+
if (origin.isMethodArgument() && origin instanceof ParameterBinding.MethodInvocationArgument mia) {
259+
260+
if (mia.identifier().hasPosition()) {
261+
return context.getParameterNameOfPosition(mia.identifier().getPosition() - 1);
262+
}
263+
264+
if (mia.identifier().hasName()) {
265+
return mia.identifier().getName();
266+
}
267+
}
268+
269+
throw new UnsupportedOperationException("Not supported yet");
270+
}
271+
254272
private CodeBlock applyHints(String queryVariableName, MergedAnnotation<QueryHints> queryHints) {
255273

256274
Builder hintsBuilder = CodeBlock.builder();

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/generated/JpaRepositoryContributor.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.springframework.core.annotation.AnnotatedElementUtils;
2424
import org.springframework.core.annotation.MergedAnnotation;
2525
import org.springframework.core.annotation.MergedAnnotations;
26+
import org.springframework.data.domain.KeysetScrollPosition;
27+
import org.springframework.data.domain.ScrollPosition;
2628
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
2729
import org.springframework.data.jpa.repository.NativeQuery;
2830
import org.springframework.data.jpa.repository.Query;
@@ -59,14 +61,12 @@
5961
public class JpaRepositoryContributor extends RepositoryContributor {
6062

6163
private final CollectionAwareProjectionFactory projectionFactory = new CollectionAwareProjectionFactory();
62-
private final AotQueryCreator queryCreator;
6364
private final AotMetaModel metaModel;
6465

6566
public JpaRepositoryContributor(AotRepositoryContext repositoryContext) {
6667
super(repositoryContext);
6768

6869
this.metaModel = new AotMetaModel(repositoryContext.getResolvedTypes());
69-
this.queryCreator = new AotQueryCreator(metaModel);
7070
}
7171

7272
@Override
@@ -106,6 +106,12 @@ protected AotRepositoryMethodBuilder contributeRepositoryMethod(
106106
}
107107
}
108108

109+
// no KeysetScrolling for now.
110+
if (generationContext.getParameterNameOf(ScrollPosition.class) != null
111+
|| generationContext.getParameterNameOf(KeysetScrollPosition.class) != null) {
112+
return null;
113+
}
114+
109115
// TODO: Named query via EntityManager, NamedQuery via properties, also for count queries.
110116

111117
return new AotRepositoryMethodBuilder(generationContext).customize((context, body) -> {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/generated/StringAotQuery.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ private StringAotQuery(List<ParameterBinding> parameterBindings) {
3434
super(parameterBindings);
3535
}
3636

37+
/**
38+
* Creates a new {@code StringAotQuery} from a {@link DeclaredQuery}. Parses the query into {@link PreprocessedQuery}.
39+
*/
3740
static StringAotQuery of(DeclaredQuery query) {
3841

3942
if (query instanceof PreprocessedQuery pq) {
@@ -43,21 +46,37 @@ static StringAotQuery of(DeclaredQuery query) {
4346
return new DeclaredAotQuery(PreprocessedQuery.parse(query));
4447
}
4548

49+
/**
50+
* Creates a new {@code StringAotQuery} from a JPQL {@code queryString}. Parses the query into
51+
* {@link PreprocessedQuery}.
52+
*/
4653
static StringAotQuery jpqlQuery(String queryString) {
4754
return of(DeclaredQuery.jpqlQuery(queryString));
4855
}
4956

57+
/**
58+
* Creates a JPQL {@code StringAotQuery} using the given bindings and limit.
59+
*/
5060
public static StringAotQuery jpqlQuery(String queryString, List<ParameterBinding> bindings, Limit resultLimit) {
5161
return new LimitedAotQuery(queryString, bindings, resultLimit);
5262
}
5363

64+
/**
65+
* Creates a new {@code StringAotQuery} from a native (SQL) {@code queryString}. Parses the query into
66+
* {@link PreprocessedQuery}.
67+
*/
5468
static StringAotQuery nativeQuery(String queryString) {
5569
return of(DeclaredQuery.nativeQuery(queryString));
5670
}
5771

72+
/**
73+
* @return the underlying declared query.
74+
*/
5875
public abstract DeclaredQuery getQuery();
5976

60-
public abstract String getQueryString();
77+
public String getQueryString() {
78+
return getQuery().getQueryString();
79+
}
6180

6281
@Override
6382
public String toString() {
@@ -94,6 +113,8 @@ public PreprocessedQuery getQuery() {
94113
}
95114

96115
/**
116+
* Query with a limit associated.
117+
*
97118
* @author Mark Paluch
98119
*/
99120
static class LimitedAotQuery extends StringAotQuery {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import java.util.List;
2626
import java.util.stream.Collectors;
2727

28-
import org.springframework.data.expression.ValueExpression;
29-
3028
import org.jspecify.annotations.Nullable;
29+
30+
import org.springframework.data.expression.ValueExpression;
3131
import org.springframework.data.jpa.provider.PersistenceProvider;
3232
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
3333
import org.springframework.data.repository.query.Parameter;
@@ -608,7 +608,7 @@ public String toString() {
608608
* @author Mark Paluch
609609
* @since 3.1.2
610610
*/
611-
sealed interface ParameterOrigin permits Expression, MethodInvocationArgument, Synthetic {
611+
public sealed interface ParameterOrigin permits Expression, MethodInvocationArgument, Synthetic {
612612

613613
/**
614614
* Creates a {@link Expression} for the given {@code expression}.

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/generated/JpaRepositoryContributorIntegrationTests.java

+19-2
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,20 @@ void testAnnotatedFinderUsingNamedParameterPlaceholderReturningListWithQuery() {
218218
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
219219
}
220220

221+
@Test
222+
void shouldApplyAnnotatedLikeStartsEnds() {
223+
224+
// start with case
225+
List<User> users = fragment.findAnnotatedLikeStartsEnds("S");
226+
assertThat(users).extracting(User::getEmailAddress).containsExactlyInAnyOrder("han@smuggler.net",
227+
"kylo@new-empire.com", "luke@jedi.org", "vader@empire.com");
228+
229+
// ends case
230+
users = fragment.findAnnotatedLikeStartsEnds("a");
231+
assertThat(users).extracting(User::getEmailAddress).containsExactlyInAnyOrder("leia@resistance.gov",
232+
"chewie@smuggler.net", "yoda@jedi.org");
233+
}
234+
221235
@Test
222236
void testAnnotatedMultilineFinderWithQuery() {
223237

@@ -306,14 +320,16 @@ void shouldApplyQueryHints() {
306320
@Test
307321
void testDerivedFinderReturningPageOfProjections() {
308322

309-
// TODO: query.setParameter(1, "%s%%".formatted(lastname));
310323
Page<UserDtoProjection> page = fragment.findUserProjectionByLastnameStartingWith("S",
311324
PageRequest.of(0, 2, Sort.by("emailAddress")));
312325

313326
assertThat(page.getTotalElements()).isEqualTo(4);
314327
assertThat(page.getSize()).isEqualTo(2);
315328
assertThat(page.getContent()).extracting(UserDtoProjection::getEmailAddress).containsExactly("han@smuggler.net",
316329
"kylo@new-empire.com");
330+
331+
Page<UserDtoProjection> noResults = fragment.findUserProjectionByLastnameStartingWith("a",
332+
PageRequest.of(0, 2, Sort.by("emailAddress")));
317333
}
318334

319335
// modifying
@@ -345,9 +361,10 @@ void nativeQuery() {
345361

346362
// old stuff below
347363

348-
// TODO:
349364
void todo() {
350365

366+
// expressions, templated query with #{#entityName}
367+
// synthetic parameters (keyset scrolling! yuck!)
351368
// interface projections
352369
// named queries
353370
// dynamic projections

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/generated/UserRepository.java

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public interface UserRepository extends CrudRepository<User, Integer> {
7575
@Query("select u from User u where u.lastname like :lastname%")
7676
List<User> findAnnotatedQueryByLastnameParameter(String lastname);
7777

78+
@Query("select u from User u where u.lastname like :lastname% or u.lastname like %:lastname")
79+
List<User> findAnnotatedLikeStartsEnds(String lastname);
80+
7881
@Query("""
7982
select u
8083
from User u

0 commit comments

Comments
 (0)