bindings = new ArrayList<>();
+
+ queryMethod.getParameters().getBindableParameters().forEach(parameter -> {
+ bindings.add(ParameterBinding.of(parameter));
+ });
+
+ parsedQuery.getParameterMap().forEach((name, expression) -> {
+ bindings.add(ParameterBinding.named(name, ParameterBinding.ParameterOrigin.ofExpression(expression)));
+ });
+
+ return bindings;
+ }
+
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/ReturnStatement.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/ReturnStatement.java
new file mode 100644
index 0000000000..3e144acf54
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/ReturnStatement.java
@@ -0,0 +1,202 @@
+/*
+ * 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.jdbc.repository.aot;
+
+import org.springframework.data.util.ReflectionUtils;
+import org.springframework.javapoet.CodeBlock;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Utility for building return statements for code generation purposes.
+ *
+ * Provides a fluent API to construct return statements based on the given return type. Typically used in AOT scenarios
+ * to generate code fragments for repository methods.
+ *
+ * @author Mark Paluch
+ */
+class ReturnStatement {
+
+ /**
+ * Create a builder for a return statement targeting the given return type.
+ *
+ * @param returnType the method return type
+ * @return a new {@code ReturnStatementBuilder}
+ */
+ public static ReturnStatementBuilder forType(Class> returnType) {
+ return new ReturnStatementBuilder(returnType);
+ }
+
+ /**
+ * Builder for constructing return statements based on the target return type.
+ */
+ static class ReturnStatementBuilder {
+
+ private final Class> returnType;
+ private final CodeBlock.Builder builder = CodeBlock.builder();
+ private boolean hasReturn = false;
+
+ /**
+ * Create a new builder for the given return type.
+ *
+ * @param returnType the method return type
+ */
+ private ReturnStatementBuilder(Class> returnType) {
+ this.returnType = returnType;
+
+ // consider early return cases for Void and void.
+ whenBoxed(Void.class, "null");
+ this.hasReturn = ReflectionUtils.isVoid(returnType);
+ }
+
+ /**
+ * Add return statements for numeric types if the given {@code resultToReturn} points to a {@link Number}. Considers
+ * primitive and boxed {@code int} and {@code long} type return paths and that {@code resultToReturn} can be
+ * {@literal null}.
+ *
+ * @param resultToReturn the argument or variable name holding the result.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder number(String resultToReturn) {
+ return whenBoxedLong("$1L != null ? $1L.longValue() : null", resultToReturn)
+ .whenLong("$1L != null ? $1L.longValue() : 0L", resultToReturn)
+ .whenBoxedInteger("$1L != null ? $1L.intValue() : null", resultToReturn)
+ .whenInt("$1L != null ? $1L.intValue() : 0", resultToReturn);
+ }
+
+ /**
+ * Add a return statement if the return type is boolean (primitive or box type).
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenBoolean(String format, Object... args) {
+ return when(returnType == boolean.class || returnType == Boolean.class, format, args);
+ }
+
+ /**
+ * Add a return statement if the return type is {@link Long} (boxed {@code long} type).
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenBoxedLong(String format, Object... args) {
+ return whenBoxed(long.class, format, args);
+ }
+
+ /**
+ * Add a return statement if the return type is a primitive {@code long} type.
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenLong(String format, Object... args) {
+ return when(returnType == long.class, format, args);
+ }
+
+ /**
+ * Add a return statement if the return type is {@link Integer} (boxed {@code int} type).
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenBoxedInteger(String format, Object... args) {
+ return whenBoxed(int.class, format, args);
+ }
+
+ /**
+ * Add a return statement if the return type is a primitive {@code int} type.
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenInt(String format, Object... args) {
+ return when(returnType == int.class, format, args);
+ }
+
+ /**
+ * Add a return statement if the return type matches the given boxed wrapper type.
+ *
+ * @param primitiveOrWrapper the primitive or wrapper type.
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder whenBoxed(Class> primitiveOrWrapper, String format, Object... args) {
+ Class> primitiveWrapper = ClassUtils.resolvePrimitiveIfNecessary(primitiveOrWrapper);
+ return when(returnType == primitiveWrapper, format, args);
+ }
+
+ /**
+ * Add a return statement if the declared return type is assignable from the given {@code returnType}.
+ *
+ * @param returnType the candidate return type.
+ * @param format the code format string.
+ * @param args the format arguments
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder when(Class> returnType, String format, Object... args) {
+ return when(this.returnType.isAssignableFrom(returnType), format, args);
+ }
+
+ /**
+ * Add a return statement if the given condition is {@code true}.
+ *
+ * @param condition the condition to evaluate.
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder when(boolean condition, String format, Object... args) {
+
+ if (hasReturn) {
+ return this;
+ }
+
+ if (condition) {
+ builder.addStatement("return " + format, args);
+ hasReturn = true;
+ }
+
+ return this;
+ }
+
+ /**
+ * Add a fallback return statement if no previous return statement was added.
+ *
+ * @param format the code format string.
+ * @param args the format arguments.
+ * @return {@code this} builder.
+ */
+ public ReturnStatementBuilder otherwise(String format, Object... args) {
+ return when(!hasReturn, format, args);
+ }
+
+ /**
+ * Build the code block representing the return statement.
+ *
+ * @return the resulting {@code CodeBlock}
+ */
+ public CodeBlock build() {
+ return builder.build();
+ }
+
+ }
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/StringAotQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/StringAotQuery.java
new file mode 100644
index 0000000000..cd25bcd679
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/StringAotQuery.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jdbc.repository.aot;
+
+import java.util.List;
+
+import org.springframework.data.jdbc.repository.query.ParameterBinding;
+
+/**
+ * An AOT query represented by a string.
+ *
+ * @author Mark Paluch
+ * @since 4.0
+ */
+abstract class StringAotQuery extends AotQuery {
+
+ StringAotQuery(List parameterBindings) {
+ super(parameterBindings);
+ }
+
+ static StringAotQuery of(String query, List parameterBindings) {
+ return new DeclaredAotQuery(query, parameterBindings);
+ }
+
+ static StringAotQuery named(String queryName, String query, List parameterBindings) {
+ return new NamedStringAotQuery(queryName, query, parameterBindings);
+ }
+
+ public abstract String getQueryString();
+
+ @Override
+ public String toString() {
+ return getQueryString();
+ }
+
+ /**
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ */
+ private static class DeclaredAotQuery extends StringAotQuery {
+
+ private final String query;
+
+ DeclaredAotQuery(String query, List parameterBindings) {
+ super(parameterBindings);
+ this.query = query;
+ }
+
+ @Override
+ public String getQueryString() {
+ return query;
+ }
+
+ }
+
+ static class NamedStringAotQuery extends DeclaredAotQuery {
+
+ private final String queryName;
+
+ NamedStringAotQuery(String queryName, String query, List parameterBindings) {
+ super(query, parameterBindings);
+ this.queryName = queryName;
+ }
+
+ public String getQueryName() {
+ return queryName;
+ }
+
+ }
+
+}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/package-info.java
new file mode 100644
index 0000000000..b41a2f489f
--- /dev/null
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Ahead-of-Time (AOT) generation for Spring Data JDBC repositories.
+ */
+@org.jspecify.annotations.NullMarked
+package org.springframework.data.jdbc.repository.aot;
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java
index cd65bfdf5f..3525f68f07 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java
@@ -21,17 +21,27 @@
import java.util.Locale;
import java.util.Optional;
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.aot.generate.GenerationContext;
+import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
+import org.springframework.data.jdbc.repository.aot.JdbcRepositoryContributor;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.Table;
+import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
import org.springframework.data.repository.config.RepositoryConfigurationSource;
+import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
+import org.springframework.data.util.TypeContributor;
import org.springframework.util.StringUtils;
/**
@@ -109,6 +119,11 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
}
}
+ @Override
+ public Class extends BeanRegistrationAotProcessor> getRepositoryAotProcessor() {
+ return JdbcRepositoryRegistrationAotProcessor.class;
+ }
+
/**
* In strict mode only domain types having a {@link Table} annotation get a repository.
*/
@@ -116,4 +131,36 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
protected Collection> getIdentifyingAnnotations() {
return Collections.singleton(Table.class);
}
+
+ /**
+ * A {@link RepositoryRegistrationAotProcessor} implementation that maintains aot repository setup.
+ *
+ * @since 3.0
+ */
+ public static class JdbcRepositoryRegistrationAotProcessor extends RepositoryRegistrationAotProcessor {
+
+ private static final String MODULE_NAME = "jdbc";
+
+ protected @Nullable JdbcRepositoryContributor contribute(AotRepositoryContext repositoryContext,
+ GenerationContext generationContext) {
+
+ // do some custom type registration here
+ super.contribute(repositoryContext, generationContext);
+
+ repositoryContext.getResolvedTypes().stream().forEach(type -> {
+ TypeContributor.contribute(type, it -> true, generationContext);
+ });
+
+ if (!repositoryContext.isGeneratedRepositoriesEnabled(MODULE_NAME)) {
+ return null;
+ }
+
+ ConfigurableListableBeanFactory beanFactory = repositoryContext.getBeanFactory();
+ JdbcDialect dialect = beanFactory.getBean(JdbcDialect.class);
+ JdbcConverter converter = beanFactory.getBean(JdbcConverter.class);
+
+ return new JdbcRepositoryContributor(repositoryContext, dialect, converter);
+ }
+
+ }
}
diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java
index 2331b3a6dd..d705b44453 100644
--- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java
+++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java
@@ -147,29 +147,5 @@ private JdbcQueryExecution createSingleReadingQueryExecution(ResultSetExt
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
}
- /**
- * Factory to create a {@link RowMapper} for a given class.
- *
- * @since 2.3
- * @deprecated Use {@link org.springframework.data.jdbc.repository.query.RowMapperFactory} instead
- */
- @Deprecated(forRemoval = true, since = "3.4.4")
- public interface RowMapperFactory extends org.springframework.data.jdbc.repository.query.RowMapperFactory {}
- /**
- * Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterwards into {@code Object}.
- *
- * @param
- * @since 2.3
- * @deprecated use {@link org.springframework.data.jdbc.repository.query.ConvertingRowMapper} instead
- */
- @Deprecated(forRemoval = true, since = "3.4.4")
- protected static class ConvertingRowMapper
- extends org.springframework.data.jdbc.repository.query.ConvertingRowMapper {
-
- @SuppressWarnings("unchecked")
- public ConvertingRowMapper(RowMapper delegate, Converter