Spring 6.1 introduces RestClient, a blocking variant of WebClient. I would like a modern JDBC client like R2DBC's DatabaseClient as well.

Like RestTemplate, JdbcTemplate also has many overloaded method, making it difficult to add new functionality. (For example, Optional support https://github.com/spring-projects/spring-framework/issues/30927) Like RestClient for RestTemplate, JdbcClient for JdbcTemplate with fluent api would make it easier to add modern features.

Comment From: jhoeller

As per my comment on #https://github.com/spring-projects/spring-framework/issues/30927#issuecomment-1647458825, this is to be investigated for 6.1 now. If we are happy with an initial cut, I can see this being included in 6.1 M4 already.

Comment From: jhoeller

This is coming into 6.1 M4 as org.springframework.jdbc.core.simple.JdbcClient now, as a unified facade for JdbcTemplate and NamedParameterJdbcTemplate with flexible parameter options as well as flexible result retrieval options. For example:

Optional<Integer> value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = :id")
        .param("id", 3)
        .query((rs, rowNum) -> rs.getInt(1))
        .optional();

Comment From: jhoeller

For a first impression, here is the entire JdbcClient API: https://github.com/spring-projects/spring-framework/blob/main/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java

Comment From: making

I migrated 2 projects to JdbcClient. Both are in production :)

https://github.com/categolj/blog-api/commit/b2303dec597262cf221db0e3cfac884768c83fa9 https://github.com/categolj/note-api/commit/5d5ecc36dbf6a13fe5efca9ed2333ef60b65e2e9

The migration was very smooth and all tests passed without any changes. I love this client. Thank you for making this happen!

Comment From: jhoeller

A heads-up that the query variants have slightly changed along with #26594: There is a new query(Class) method with value and property mapping support now, as an alternative to specifying a custom RowMapper instance. Since this also covers typed column mappings, the singleColumn/singleValue methods are declared without a Class parameter now and just return whatever ResultSet.getObject provided (analogous to the existing singleRow method).

Note that singleColumn/singleValue typing only works for the actual result type now, there is no coercion/conversion anymore (like on all other methods on the query() result spec). So e.g. to get an Integer, do .query(Integer.class).single() instead of the former .query().singleValue(Integer.class).