From 6e1b89c46c190dcccfdca7dd00f147c46d558fce Mon Sep 17 00:00:00 2001 From: Victor Uria Valle Date: Tue, 21 Nov 2023 14:23:26 +0100 Subject: [PATCH] Add queryForOptional method in JdbcTemplate --- .../jdbc/core/JdbcTemplate.java | 22 +++++++++++++ .../jdbc/core/JdbcTemplateQueryTests.java | 33 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index 37bd61f2b3ac..6166342a2c5b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -34,6 +34,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Spliterator; import java.util.function.Consumer; import java.util.stream.Stream; @@ -912,6 +913,27 @@ public T queryForObject(String sql, Class requiredType, @Nullable Object. return queryForObject(sql, args, getSingleColumnRowMapper(requiredType)); } + /** + * Query with the specified SQL statement, RowMapper, and optional arguments, + * returning the result as an Optional object. This method serves as a convenient substitution + * for the boilerplate code typically required when using queryForObject, especially + * to handle cases where the query result is empty. + * + * @param The type of the result objects. + * @param sql The SQL statement to be executed. + * @param rowMapper The RowMapper implementation to map the query results to objects of type T. + * @param args Optional arguments to be bound to the SQL statement. + * @return An Optional containing the first result of the query, or an empty Optional if the + * query produced no results. + * @throws DataAccessException If an error occurs while accessing the data source. + * + * @see RowMapper + * @see Optional + */ + public Optional queryForOptional(String sql, RowMapper rowMapper, @Nullable Object... args) throws DataAccessException { + return query(sql, rowMapper, args).stream().findFirst(); + } + @Override public Map queryForMap(String sql, Object[] args, int[] argTypes) throws DataAccessException { return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper())); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java index 24da6eeb6718..864747fcdb63 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java @@ -25,6 +25,7 @@ import java.sql.Statement; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -436,4 +437,36 @@ public void testQueryForLongWithArgs() throws Exception { verify(this.connection).close(); } + @Test + public void testQueryForOptionalWithArgsAndRowMapper() throws Exception { + String sql = "SELECT AGE FROM CUSTMR WHERE ID = ?"; + given(this.resultSet.next()).willReturn(true, false); + given(this.resultSet.getLong(1)).willReturn(87L); + + Optional optionalResult = this.template.queryForOptional(sql, (rs, rowNum) -> rs.getLong(1), 1); + + assertThat(optionalResult).as("Optional result").isPresent(); + assertThat(optionalResult.get()).as("Correct result value").isEqualTo(87L); + + verify(this.preparedStatement).setObject(1, 1); + verify(this.resultSet).close(); + verify(this.preparedStatement).close(); + verify(this.connection).close(); + } + + @Test + public void testQueryForOptionalWithArgsAndRowMapperEmptyResult() throws Exception { + String sql = "SELECT AGE FROM CUSTMR WHERE ID = ?"; + given(this.resultSet.next()).willReturn(false); + + Optional optionalResult = this.template.queryForOptional(sql, (rs, rowNum) -> rs.getLong(1), 2); + + assertThat(optionalResult).as("Optional result").isEmpty(); + + verify(this.preparedStatement).setObject(1, 2); + verify(this.resultSet).close(); + verify(this.preparedStatement).close(); + verify(this.connection).close(); + } + }