Skip to content

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented Aug 21, 2025

We now provide AOT support to generate repository implementations during build-time for JDBC repository query methods.

Supported Features

  • Derived query methods, @Query and named query methods
  • @Modifying methods returning void, int, and long
  • Pagination, Slice, Stream, and Optional return types
  • DTO and Interface Projections
  • Value Expressions

Limitations

  • Methods accepting ScrollPosition (e.g. Keyset pagination) are not yet supported

Excluded methods

  • CrudRepository, Querydsl, Query by Example, and other base interface methods as their implementation is provided by the base class respective fragments
  • Methods whose implementation would be overly complex
    ** Methods accepting ScrollPosition (e.g. Keyset pagination)

Example repository:

interface UserRepository extends CrudRepository<User, Integer> {

	User findByFirstname(String name);

	Stream<User> streamByAgeGreaterThan(int age);

	@Query("SELECT * FROM MY_USER WHERE firstname = :name")
	User findByFirstnameAnnotated(String name);

	@Modifying
	@Query("delete from MY_USER where firstname = :firstname")
	void deleteWithoutResult(String firstname);

}

Generated fragment:

/**
 * AOT generated JDBC repository implementation for {@link UserRepository}.
 */
public class UserRepositoryImpl__AotRepository extends AotRepositoryFragmentSupport {
  private final RepositoryFactoryBeanSupport.FragmentCreationContext context;

  private final RowMapperFactory rowMapperFactory;

  private final JdbcAggregateOperations operations;

  public UserRepositoryImpl__AotRepository(RowMapperFactory rowMapperFactory,
      JdbcAggregateOperations operations,
      RepositoryFactoryBeanSupport.FragmentCreationContext context) {
    super(rowMapperFactory, operations, context);
    this.rowMapperFactory = rowMapperFactory;
    this.operations = operations;
    this.context = context;
  }

  public User findByFirstname(String name) {
    Criteria criteria = Criteria.where("firstname").is(name);
    StatementFactory.Selection selection = getStatementFactory().select(User.class);
    selection.filter(criteria);
    MapSqlParameterSource rawParameterSource = new MapSqlParameterSource();
    String query = selection.build(rawParameterSource);
    SqlParameterSource parameterSource = escapingParameterSource(rawParameterSource);

    RowMapper rowMapper = rowMapperFactory.create(User.class);
    Object result = queryForObject(query, parameterSource, rowMapper);
    return (User) convertOne(result, User.class);
  }

  public Stream<User> streamByAgeGreaterThan(int age) {
    Criteria criteria = Criteria.where("age").greaterThan(age);
    StatementFactory.Selection selection = getStatementFactory().select(User.class);
    selection.filter(criteria);
    MapSqlParameterSource rawParameterSource = new MapSqlParameterSource();
    String query = selection.build(rawParameterSource);
    SqlParameterSource parameterSource = escapingParameterSource(rawParameterSource);

    RowMapper rowMapper = rowMapperFactory.create(User.class);
    Stream result = getJdbcOperations().queryForStream(query, parameterSource, rowMapper);
    return (Stream<User>) convertMany(result, User.class);
  }

  public User findByFirstnameAnnotated(String name) {
    class ExpressionMarker{};
    String query = "SELECT * FROM MY_USER WHERE firstname = :name";
    MapSqlParameterSource parameterSource = new MapSqlParameterSource();
    getBindableValue(ExpressionMarker.class.getEnclosingMethod(), name, 0).bind("name", parameterSource);

    RowMapper rowMapper = rowMapperFactory.create(User.class);
    Object result = queryForObject(query, parameterSource, rowMapper);
    return (User) convertOne(result, User.class);
  }

  public void deleteWithoutResult(String firstname) {
    class ExpressionMarker{};
    String query = "delete from MY_USER where firstname = :firstname";
    MapSqlParameterSource parameterSource = new MapSqlParameterSource();
    getBindableValue(ExpressionMarker.class.getEnclosingMethod(), firstname, 0).bind("firstname", parameterSource);

    getJdbcOperations().update(query, parameterSource);
  }
}

Metadata (truncated):

{
  "name": "org.springframework.data.jdbc.repository.aot.UserRepository",
  "module": "JDBC",
  "type": "IMPERATIVE",
  "methods": [
    {
      "name": "findByFirstname",
      "signature": "public abstract org.springframework.data.jdbc.repository.aot.User org.springframework.data.jdbc.repository.aot.UserRepository.findByFirstname(java.lang.String)",
      "query": {
        "query": "SELECT \"MY_USER\".\"ID\" AS \"ID\", \"MY_USER\".\"AGE\" AS \"AGE\", \"MY_USER\".\"FIRSTNAME\" AS \"FIRSTNAME\" FROM \"MY_USER\" WHERE \"MY_USER\".\"FIRSTNAME\" = :firstname"
      }
    },
    {
      "name": "deleteWithoutResult",
      "signature": "public abstract void org.springframework.data.jdbc.repository.aot.UserRepository.deleteWithoutResult(java.lang.String)",
      "query": {
        "query": "delete from MY_USER where firstname = :firstname"
      }
    },
    {
      "name": "saveAll",
      "signature": "public abstract <S extends T> java.lang.Iterable<S> org.springframework.data.repository.CrudRepository.saveAll(java.lang.Iterable<S>)",
      "fragment": {
        "interface": "org.springframework.data.jdbc.repository.support.SimpleJdbcRepository",
        "fragment": "org.springframework.data.jdbc.repository.support.SimpleJdbcRepository"
      }
    }
  ]
}

@mp911de mp911de added type: enhancement A general enhancement theme: aot An issue related to Ahead-Of-Time processing labels Aug 21, 2025
@mp911de mp911de linked an issue Aug 21, 2025 that may be closed by this pull request
Retrieve a RowMapper for general usage from JdbcAggregateOperations instead of passing several independent dependencies across various components.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: aot An issue related to Ahead-Of-Time processing type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Explore support for Ahead of Time Repositories
1 participant