diff --git a/pom.xml b/pom.xml
index db0fdf4333..86a83ec605 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
+ * {@link ExpressionMarker} is intended to be used via {@link AotQueryMethodGenerationContext} to maintain usage info, + * making sure the code is only added ({@link #isInUse()}) when {@link #enclosingMethod()} was called for generating + * code. + * + *
+ * ExpressionMarker marker = context.getExpressionMarker();
+ * CodeBlock.builder().add("evaluate($L, $S, $L)", marker.enclosingMethod(), queryString, parameters);
+ *
+ *
+ * @author Christoph Strobl
+ * @since 4.0
+ */
+public class ExpressionMarker {
+
+ private final String typeName;
+ private boolean inUse = false;
+
+ ExpressionMarker() {
+ this("ExpressionMarker");
+ }
+
+ ExpressionMarker(String typeName) {
+ this.typeName = typeName;
+ }
+
+ /**
+ * @return {@code class ExpressionMarker}.
+ */
+ CodeBlock declaration() {
+ return CodeBlock.of("class $L{};\n", typeName);
+ }
+
+ /**
+ * Calling this method sets the {@link ExpressionMarker} as {@link #isInUse() in-use}.
+ *
+ * @return {@code ExpressionMarker.class}.
+ */
+ public CodeBlock marker() {
+
+ if (!inUse) {
+ inUse = true;
+ }
+ return CodeBlock.of("$L.class", typeName);
+ }
+
+ /**
+ * Calling this method sets the {@link ExpressionMarker} as {@link #isInUse() in-use}.
+ *
+ * @return {@code ExpressionMarker.class.getEnclosingMethod()}
+ */
+ public CodeBlock enclosingMethod() {
+ return CodeBlock.of("$L.getEnclosingMethod()", marker());
+ }
+
+ /**
+ * @return if the marker is in use.
+ */
+ public boolean isInUse() {
+ return inUse;
+ }
+}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java
index a80559ebe8..fd8ad840b7 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java
@@ -15,20 +15,24 @@
*/
package org.springframework.data.repository.aot.generate;
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
import example.UserRepository;
import example.UserRepository.User;
import java.lang.reflect.Method;
import java.util.List;
+import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
-
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.TypeInformation;
@@ -45,10 +49,12 @@ class AotRepositoryMethodBuilderUnitTests {
@BeforeEach
void beforeEach() {
+
repositoryInformation = Mockito.mock(RepositoryInformation.class);
methodGenerationContext = Mockito.mock(AotQueryMethodGenerationContext.class);
when(methodGenerationContext.getRepositoryInformation()).thenReturn(repositoryInformation);
+ when(methodGenerationContext.getExpressionMarker()).thenReturn(new ExpressionMarker());
}
@Test // GH-3279
@@ -87,4 +93,37 @@ void generatesMethodWithGenerics() throws NoSuchMethodException {
.containsPattern("public .*List<.*User> findByFirstnameIn\\(") //
.containsPattern(".*List<.*String> firstnames\\)");
}
+
+ @ParameterizedTest // GH-3279
+ @MethodSource(value = { "expressionMarkers" })
+ void generatesExpressionMarkerIfInUse(ExpressionMarker expressionMarker) throws NoSuchMethodException {
+
+ Method method = UserRepository.class.getMethod("findByFirstname", String.class);
+ when(methodGenerationContext.getMethod()).thenReturn(method);
+ when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
+ doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
+ doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
+ MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
+ methodMetadata.addParameter(ParameterSpec.builder(String.class, "firstname").build());
+ when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata);
+ when(methodGenerationContext.getExpressionMarker()).thenReturn(expressionMarker);
+
+ AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(methodGenerationContext);
+ String methodCode = builder.buildMethod().toString();
+ if (expressionMarker.isInUse()) {
+ assertThat(methodCode).contains("class ExpressionMarker{};");
+ } else {
+ assertThat(methodCode).doesNotContain("class ExpressionMarker{};");
+ }
+ }
+
+ static Stream