diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java index 326938df90ee..d8b4f7b3cd0b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/JdbcClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -343,12 +343,26 @@ interface ResultQuerySpec { /** * Retrieve a single value result. - * @return the single row represented as its single column value + *

Note: As of 6.2, this will enforce non-null result values + * as originally designed (just accidentally not enforced before). + * (never {@code null}) + * @see #optionalValue() * @see DataAccessUtils#requiredSingleResult(Collection) */ default Object singleValue() { return DataAccessUtils.requiredSingleResult(singleColumn()); } + + /** + * Retrieve a single value result, if available, as an {@link Optional} handle. + * @return an Optional handle with the single column value from the single row + * @since 6.2 + * @see #singleValue() + * @see DataAccessUtils#optionalResult(Collection) + */ + default Optional optionalValue() { + return DataAccessUtils.optionalResult(singleColumn()); + } } @@ -384,25 +398,27 @@ default Set set() { return new LinkedHashSet<>(list()); } - /** - * Retrieve a single result, if available, as an {@link Optional} handle. - * @return an Optional handle with a single result object or none - * @see #list() - * @see DataAccessUtils#optionalResult(Collection) - */ - default Optional optional() { - return DataAccessUtils.optionalResult(list()); - } - /** * Retrieve a single result as a required object instance. + *

Note: As of 6.2, this will enforce non-null result values + * as originally designed (just accidentally not enforced before). * @return the single result object (never {@code null}) - * @see #list() + * @see #optional() * @see DataAccessUtils#requiredSingleResult(Collection) */ default T single() { return DataAccessUtils.requiredSingleResult(list()); } + + /** + * Retrieve a single result, if available, as an {@link Optional} handle. + * @return an Optional handle with a single result object or none + * @see #single() + * @see DataAccessUtils#optionalResult(Collection) + */ + default Optional optional() { + return DataAccessUtils.optionalResult(list()); + } } } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientQueryTests.java index a4bbeaa3c653..a8212b4a6931 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/simple/JdbcClientQueryTests.java @@ -174,6 +174,40 @@ void queryForIntegerWithIndexedParamAndSingleValue() throws Exception { verify(connection).close(); } + @Test + void queryForIntegerWithIndexedParamAndOptionalValue() throws Exception { + given(resultSet.next()).willReturn(true, false); + given(resultSet.getObject(1)).willReturn(22); + + Optional value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") + .param(1, 3) + .query().optionalValue(); + + assertThat(value.isPresent()).isTrue(); + assertThat(value.get()).isEqualTo(22); + verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); + verify(preparedStatement).setObject(1, 3); + verify(resultSet).close(); + verify(preparedStatement).close(); + verify(connection).close(); + } + + @Test + void queryForIntegerWithIndexedParamAndNonExistingValue() throws Exception { + given(resultSet.next()).willReturn(false); + + Optional value = client.sql("SELECT AGE FROM CUSTMR WHERE ID = ?") + .param(1, 3) + .query().optionalValue(); + + assertThat(value.isPresent()).isFalse(); + verify(connection).prepareStatement("SELECT AGE FROM CUSTMR WHERE ID = ?"); + verify(preparedStatement).setObject(1, 3); + verify(resultSet).close(); + verify(preparedStatement).close(); + verify(connection).close(); + } + @Test void queryForIntegerWithIndexedParamAndRowMapper() throws Exception { given(resultSet.next()).willReturn(true, false);