From a61b2979670821acf1db00461ac92630ca4d405e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 7 Jul 2025 14:11:29 +0200 Subject: [PATCH] Specify flexible generics nullness in spring-jdbc This commit leverages flexible generics nullness at method and type level when relevant in spring-jdbc. Due to https://github.com/uber/NullAway/issues/1075, some related `@SuppressWarnings("NullAway")` have been added. JdbcOperations Kotlin extensions have been refined accordingly. Closes gh-34911 --- .../springframework/util/CollectionUtils.java | 2 +- .../jdbc/core/CallableStatementCallback.java | 4 +- .../jdbc/core/ColumnMapRowMapper.java | 8 +- .../jdbc/core/ConnectionCallback.java | 4 +- .../jdbc/core/JdbcOperations.java | 74 +++++------ .../jdbc/core/JdbcTemplate.java | 116 ++++++++++-------- .../ParameterizedPreparedStatementSetter.java | 4 +- .../jdbc/core/PreparedStatementCallback.java | 4 +- .../jdbc/core/ResultSetExtractor.java | 4 +- .../springframework/jdbc/core/RowMapper.java | 4 +- .../core/RowMapperResultSetExtractor.java | 4 +- .../jdbc/core/SingleColumnRowMapper.java | 2 +- .../jdbc/core/StatementCallback.java | 4 +- .../NamedParameterJdbcOperations.java | 38 +++--- .../NamedParameterJdbcTemplate.java | 42 ++++--- .../jdbc/core/simple/AbstractJdbcCall.java | 8 +- .../jdbc/core/simple/AbstractJdbcInsert.java | 3 +- .../jdbc/core/simple/DefaultJdbcClient.java | 29 +++-- .../jdbc/core/simple/JdbcClient.java | 21 ++-- .../jdbc/core/simple/SimpleJdbcCall.java | 6 +- .../core/simple/SimpleJdbcCallOperations.java | 6 +- ...bstractLobStreamingResultSetExtractor.java | 2 +- .../jdbc/object/GenericSqlQuery.java | 2 +- .../jdbc/object/MappingSqlQuery.java | 6 +- .../object/MappingSqlQueryWithParameters.java | 6 +- .../jdbc/object/SqlFunction.java | 2 +- .../springframework/jdbc/object/SqlQuery.java | 2 +- .../jdbc/object/StoredProcedure.java | 10 +- .../support/DatabaseMetaDataCallback.java | 4 +- .../jdbc/support/JdbcUtils.java | 2 +- .../jdbc/core/JdbcOperationsExtensions.kt | 29 +++-- .../core/JdbcOperationsExtensionsTests.kt | 74 +++++++++-- .../dao/support/DataAccessUtils.java | 7 +- 33 files changed, 313 insertions(+), 220 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index 117042ba656..4a41a9c947e 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -110,7 +110,7 @@ public abstract class CollectionUtils { * @since 5.3 * @see #newHashMap(int) */ - public static LinkedHashMap newLinkedHashMap(int expectedSize) { + public static LinkedHashMap newLinkedHashMap(int expectedSize) { return new LinkedHashMap<>(computeInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java index d19caa8035b..5434dcca453 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/CallableStatementCallback.java @@ -44,7 +44,7 @@ import org.springframework.dao.DataAccessException; * @see JdbcTemplate#execute(CallableStatementCreator, CallableStatementCallback) */ @FunctionalInterface -public interface CallableStatementCallback { +public interface CallableStatementCallback { /** * Gets called by {@code JdbcTemplate.execute} with an active JDBC @@ -75,6 +75,6 @@ public interface CallableStatementCallback { * into a DataAccessException by an SQLExceptionTranslator * @throws DataAccessException in case of custom exceptions */ - @Nullable T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException; + T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java index d4e72869e00..8381831307e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java @@ -44,13 +44,13 @@ import org.springframework.util.LinkedCaseInsensitiveMap; * @see JdbcTemplate#queryForList(String) * @see JdbcTemplate#queryForMap(String) */ -public class ColumnMapRowMapper implements RowMapper> { +public class ColumnMapRowMapper implements RowMapper> { @Override - public Map mapRow(ResultSet rs, int rowNum) throws SQLException { + public Map mapRow(ResultSet rs, int rowNum) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); - Map mapOfColumnValues = createColumnMap(columnCount); + Map mapOfColumnValues = createColumnMap(columnCount); for (int i = 1; i <= columnCount; i++) { String column = JdbcUtils.lookupColumnName(rsmd, i); mapOfColumnValues.putIfAbsent(getColumnKey(column), getColumnValue(rs, i)); @@ -66,7 +66,7 @@ public class ColumnMapRowMapper implements RowMapper> { * @return the new Map instance * @see org.springframework.util.LinkedCaseInsensitiveMap */ - protected Map createColumnMap(int columnCount) { + protected Map createColumnMap(int columnCount) { return new LinkedCaseInsensitiveMap<>(columnCount); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java index 7adcedfc295..9824903f121 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java @@ -41,7 +41,7 @@ import org.springframework.dao.DataAccessException; * @see JdbcTemplate#update */ @FunctionalInterface -public interface ConnectionCallback { +public interface ConnectionCallback { /** * Gets called by {@code JdbcTemplate.execute} with an active JDBC @@ -65,6 +65,6 @@ public interface ConnectionCallback { * @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForRowSet(String) */ - @Nullable T doInConnection(Connection con) throws SQLException, DataAccessException; + T doInConnection(Connection con) throws SQLException, DataAccessException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index 37b1b55ebb4..be172510202 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -68,7 +68,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(ConnectionCallback action) throws DataAccessException; + T execute(ConnectionCallback action) throws DataAccessException; //------------------------------------------------------------------------- @@ -87,7 +87,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(StatementCallback action) throws DataAccessException; + T execute(StatementCallback action) throws DataAccessException; /** * Issue a single SQL execute, typically a DDL statement. @@ -108,7 +108,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem executing the query * @see #query(String, ResultSetExtractor, Object...) */ - @Nullable T query(String sql, ResultSetExtractor rse) throws DataAccessException; + T query(String sql, ResultSetExtractor rse) throws DataAccessException; /** * Execute a query given static SQL, reading the ResultSet on a per-row @@ -135,7 +135,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem executing the query * @see #query(String, RowMapper, Object...) */ - List query(String sql, RowMapper rowMapper) throws DataAccessException; + List query(String sql, RowMapper rowMapper) throws DataAccessException; /** * Execute a query given static SQL, mapping each row to a result object @@ -151,7 +151,7 @@ public interface JdbcOperations { * @since 5.3 * @see #queryForStream(String, RowMapper, Object...) */ - Stream queryForStream(String sql, RowMapper rowMapper) throws DataAccessException; + Stream queryForStream(String sql, RowMapper rowMapper) throws DataAccessException; /** * Execute a query given static SQL, mapping a single result row to a @@ -169,7 +169,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem executing the query * @see #queryForObject(String, RowMapper, Object...) */ - @Nullable T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException; + T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException; /** * Execute a query for a result object, given static SQL. @@ -208,7 +208,7 @@ public interface JdbcOperations { * @see #queryForMap(String, Object...) * @see ColumnMapRowMapper */ - Map queryForMap(String sql) throws DataAccessException; + Map queryForMap(String sql) throws DataAccessException; /** * Execute a query for a result list, given static SQL. @@ -225,7 +225,7 @@ public interface JdbcOperations { * @see #queryForList(String, Class, Object...) * @see SingleColumnRowMapper */ - List queryForList(String sql, Class elementType) throws DataAccessException; + List<@Nullable T> queryForList(String sql, Class elementType) throws DataAccessException; /** * Execute a query for a result list, given static SQL. @@ -241,7 +241,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem executing the query * @see #queryForList(String, Object...) */ - List> queryForList(String sql) throws DataAccessException; + List> queryForList(String sql) throws DataAccessException; /** * Execute a query for an SqlRowSet, given static SQL. @@ -299,7 +299,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException; + T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException; /** * Execute a JDBC data access operation, implemented as callback action @@ -314,7 +314,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(String sql, PreparedStatementCallback action) throws DataAccessException; + T execute(String sql, PreparedStatementCallback action) throws DataAccessException; /** * Query using a prepared statement, reading the ResultSet with a ResultSetExtractor. @@ -326,7 +326,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem * @see PreparedStatementCreatorFactory */ - @Nullable T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException; + T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException; /** * Query using a prepared statement, reading the ResultSet with a ResultSetExtractor. @@ -339,7 +339,7 @@ public interface JdbcOperations { * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if there is any problem */ - @Nullable T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor rse) + T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor rse) throws DataAccessException; /** @@ -354,7 +354,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @see java.sql.Types */ - @Nullable T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException; + T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of arguments @@ -370,7 +370,7 @@ public interface JdbcOperations { * @deprecated in favor of {@link #query(String, ResultSetExtractor, Object...)} */ @Deprecated(since = "5.3") - @Nullable T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor rse) throws DataAccessException; + T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor rse) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of arguments @@ -385,7 +385,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @since 3.0.1 */ - @Nullable T query(String sql, ResultSetExtractor rse, @Nullable Object @Nullable ... args) throws DataAccessException; + T query(String sql, ResultSetExtractor rse, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query using a prepared statement, reading the ResultSet on a per-row basis @@ -469,7 +469,7 @@ public interface JdbcOperations { * @throws DataAccessException if there is any problem * @see PreparedStatementCreatorFactory */ - List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; + List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a @@ -484,7 +484,7 @@ public interface JdbcOperations { * @return the result List, containing mapped objects * @throws DataAccessException if the query fails */ - List query(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) + List query(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException; /** @@ -500,7 +500,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @see java.sql.Types */ - List query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException; + List query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -517,7 +517,7 @@ public interface JdbcOperations { * @deprecated in favor of {@link #query(String, RowMapper, Object...)} */ @Deprecated(since = "5.3") - List query(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException; + List query(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -533,7 +533,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @since 3.0.1 */ - List query(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; + List query(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query using a prepared statement, mapping each row to a result object @@ -548,7 +548,7 @@ public interface JdbcOperations { * @see PreparedStatementCreatorFactory * @since 5.3 */ - Stream queryForStream(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; + Stream queryForStream(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a @@ -566,7 +566,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @since 5.3 */ - Stream queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) + Stream queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException; /** @@ -584,7 +584,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @since 5.3 */ - Stream queryForStream(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) + Stream queryForStream(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; /** @@ -603,7 +603,7 @@ public interface JdbcOperations { * if the query does not return exactly one row * @throws DataAccessException if the query fails */ - @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) + T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException; /** @@ -624,7 +624,7 @@ public interface JdbcOperations { * @deprecated in favor of {@link #queryForObject(String, RowMapper, Object...)} */ @Deprecated(since = "5.3") - @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException; + T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list @@ -643,7 +643,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @since 3.0.1 */ - @Nullable T queryForObject(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; + T queryForObject(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -729,7 +729,7 @@ public interface JdbcOperations { * @see ColumnMapRowMapper * @see java.sql.Types */ - Map queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; + Map queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -752,7 +752,7 @@ public interface JdbcOperations { * @see #queryForMap(String) * @see ColumnMapRowMapper */ - Map queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; + Map queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -770,7 +770,7 @@ public interface JdbcOperations { * @see #queryForList(String, Class) * @see SingleColumnRowMapper */ - List queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class elementType) + List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class elementType) throws DataAccessException; /** @@ -792,7 +792,7 @@ public interface JdbcOperations { * @deprecated in favor of {@link #queryForList(String, Class, Object...)} */ @Deprecated(since = "5.3") - List queryForList(String sql, @Nullable Object @Nullable [] args, Class elementType) throws DataAccessException; + List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, Class elementType) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -812,7 +812,7 @@ public interface JdbcOperations { * @see #queryForList(String, Class) * @see SingleColumnRowMapper */ - List queryForList(String sql, Class elementType, @Nullable Object @Nullable ... args) throws DataAccessException; + List<@Nullable T> queryForList(String sql, Class elementType, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -830,7 +830,7 @@ public interface JdbcOperations { * @see #queryForList(String) * @see java.sql.Types */ - List> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; + List> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -848,7 +848,7 @@ public interface JdbcOperations { * @throws DataAccessException if the query fails * @see #queryForList(String) */ - List> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; + List> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -1064,7 +1064,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(CallableStatementCreator csc, CallableStatementCallback action) throws DataAccessException; + T execute(CallableStatementCreator csc, CallableStatementCallback action) throws DataAccessException; /** * Execute a JDBC data access operation, implemented as callback action @@ -1079,7 +1079,7 @@ public interface JdbcOperations { * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ - @Nullable T execute(String callString, CallableStatementCallback action) throws DataAccessException; + T execute(String callString, CallableStatementCallback action) throws DataAccessException; /** * Execute an SQL call using a CallableStatementCreator to provide SQL and @@ -1089,7 +1089,7 @@ public interface JdbcOperations { * @return a Map of extracted out parameters * @throws DataAccessException if there is any problem issuing the update */ - Map call(CallableStatementCreator csc, List declaredParameters) + Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException; } 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 ea8f27d164a..734b217ab7c 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 @@ -357,7 +357,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { //------------------------------------------------------------------------- @Override - public @Nullable T execute(ConnectionCallback action) throws DataAccessException { + public T execute(ConnectionCallback action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(obtainDataSource()); @@ -402,7 +402,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { // Methods dealing with static SQL (java.sql.Statement) //------------------------------------------------------------------------- - private @Nullable T execute(StatementCallback action, boolean closeResources) throws DataAccessException { + private T execute(StatementCallback action, boolean closeResources) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(obtainDataSource()); @@ -436,18 +436,19 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public @Nullable T execute(StatementCallback action) throws DataAccessException { + public T execute(StatementCallback action) throws DataAccessException { return execute(action, true); } @Override + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 public void execute(String sql) throws DataAccessException { if (logger.isDebugEnabled()) { logger.debug("Executing SQL statement [" + sql + "]"); } // Callback to execute the statement. - class ExecuteStatementCallback implements StatementCallback, SqlProvider { + class ExecuteStatementCallback implements StatementCallback<@Nullable Object>, SqlProvider { @Override public @Nullable Object doInStatement(Statement stmt) throws SQLException { stmt.execute(sql); @@ -463,7 +464,8 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public @Nullable T query(String sql, ResultSetExtractor rse) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public T query(String sql, ResultSetExtractor rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (logger.isDebugEnabled()) { @@ -493,12 +495,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 public void query(String sql, RowCallbackHandler rch) throws DataAccessException { query(sql, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); } @Override - public List query(String sql, RowMapper rowMapper) throws DataAccessException { + public List query(String sql, RowMapper rowMapper) throws DataAccessException { return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @@ -525,12 +528,12 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public Map queryForMap(String sql) throws DataAccessException { + public Map queryForMap(String sql) throws DataAccessException { return result(queryForObject(sql, getColumnMapRowMapper())); } @Override - public @Nullable T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { + public T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { List results = query(sql, rowMapper); return DataAccessUtils.nullableSingleResult(results); } @@ -541,12 +544,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public List queryForList(String sql, Class elementType) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, Class elementType) throws DataAccessException { return query(sql, getSingleColumnRowMapper(elementType)); } @Override - public List> queryForList(String sql) throws DataAccessException { + public List> queryForList(String sql) throws DataAccessException { return query(sql, getColumnMapRowMapper()); } @@ -651,7 +655,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { // Methods dealing with prepared statements //------------------------------------------------------------------------- - private @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback action, boolean closeResources) + private T execute(PreparedStatementCreator psc, PreparedStatementCallback action, boolean closeResources) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); @@ -699,14 +703,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback action) + public T execute(PreparedStatementCreator psc, PreparedStatementCallback action) throws DataAccessException { return execute(psc, action, true); } @Override - public @Nullable T execute(String sql, PreparedStatementCallback action) throws DataAccessException { + public T execute(String sql, PreparedStatementCallback action) throws DataAccessException { return execute(new SimplePreparedStatementCreator(sql), action, true); } @@ -747,37 +751,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public @Nullable T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public T query(PreparedStatementCreator psc, ResultSetExtractor rse) throws DataAccessException { return query(psc, null, rse); } @Override - public @Nullable T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor rse) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor rse) throws DataAccessException { return query(new SimplePreparedStatementCreator(sql), pss, rse); } @Override - public @Nullable T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException { + public T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor rse) throws DataAccessException { return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse); } @Deprecated(since = "5.3") @Override - public @Nullable T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor rse) throws DataAccessException { + public T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor rse) throws DataAccessException { return query(sql, newArgPreparedStatementSetter(args), rse); } @Override - public @Nullable T query(String sql, ResultSetExtractor rse, @Nullable Object @Nullable ... args) throws DataAccessException { + public T query(String sql, ResultSetExtractor rse, @Nullable Object @Nullable ... args) throws DataAccessException { return query(sql, newArgPreparedStatementSetter(args), rse); } @Override + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException { query(psc, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); } @Override + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException { query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); } @@ -799,28 +807,28 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { + public List query(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @Override - public List query(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException { + public List query(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException { return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @Override - public List query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException { + public List query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException { return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @Deprecated(since = "5.3") @Override - public List query(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException { + public List query(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException { return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @Override - public List query(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { + public List query(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); } @@ -837,7 +845,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @throws DataAccessException if the query fails * @since 5.3 */ - public Stream queryForStream(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss, + public Stream queryForStream(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException { return result(execute(psc, ps -> { @@ -858,22 +866,22 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public Stream queryForStream(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { + public Stream queryForStream(PreparedStatementCreator psc, RowMapper rowMapper) throws DataAccessException { return queryForStream(psc, null, rowMapper); } @Override - public Stream queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException { + public Stream queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper rowMapper) throws DataAccessException { return queryForStream(new SimplePreparedStatementCreator(sql), pss, rowMapper); } @Override - public Stream queryForStream(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { + public Stream queryForStream(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { return queryForStream(new SimplePreparedStatementCreator(sql), newArgPreparedStatementSetter(args), rowMapper); } @Override - public @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) + public T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper rowMapper) throws DataAccessException { List results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1)); @@ -882,13 +890,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @Deprecated(since = "5.3") @Override - public @Nullable T queryForObject(String sql,@Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException { + public T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper rowMapper) throws DataAccessException { List results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1)); return DataAccessUtils.nullableSingleResult(results); } @Override - public @Nullable T queryForObject(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { + public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { List results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1)); return DataAccessUtils.nullableSingleResult(results); } @@ -912,38 +920,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public Map queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { + public Map queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper())); } @Override - public Map queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { + public Map queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { return result(queryForObject(sql, getColumnMapRowMapper(), args)); } @Override - public List queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class elementType) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class elementType) throws DataAccessException { return query(sql, args, argTypes, getSingleColumnRowMapper(elementType)); } @Deprecated(since = "5.3") @Override - public List queryForList(String sql, @Nullable Object @Nullable [] args, Class elementType) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, Class elementType) throws DataAccessException { return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType)); } @Override - public List queryForList(String sql, Class elementType, @Nullable Object @Nullable ... args) throws DataAccessException { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, Class elementType, @Nullable Object @Nullable ... args) throws DataAccessException { return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType)); } @Override - public List> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { + public List> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { return query(sql, args, argTypes, getColumnMapRowMapper()); } @Override - public List> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { + public List> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { return query(sql, newArgPreparedStatementSetter(args), getColumnMapRowMapper()); } @@ -1146,7 +1157,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { //------------------------------------------------------------------------- @Override - public @Nullable T execute(CallableStatementCreator csc, CallableStatementCallback action) + public T execute(CallableStatementCreator csc, CallableStatementCallback action) throws DataAccessException { Assert.notNull(csc, "CallableStatementCreator must not be null"); @@ -1192,12 +1203,12 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } @Override - public @Nullable T execute(String callString, CallableStatementCallback action) throws DataAccessException { + public T execute(String callString, CallableStatementCallback action) throws DataAccessException { return execute(new SimpleCallableStatementCreator(callString), action); } @Override - public Map call(CallableStatementCreator csc, List declaredParameters) + public Map call(CallableStatementCreator csc, List declaredParameters) throws DataAccessException { List updateCountParameters = new ArrayList<>(); @@ -1218,14 +1229,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { } } - Map result = execute(csc, cs -> { + Map result = execute(csc, cs -> { boolean retVal = cs.execute(); int updateCount = cs.getUpdateCount(); if (logger.isTraceEnabled()) { logger.trace("CallableStatement.execute() returned '" + retVal + "'"); logger.trace("CallableStatement.getUpdateCount() returned " + updateCount); } - Map resultsMap = createResultsMap(); + Map resultsMap = createResultsMap(); if (retVal || updateCount != -1) { resultsMap.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount)); } @@ -1244,11 +1255,11 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @param resultSetParameters the parameter list of declared resultSet parameters for the stored procedure * @return a Map that contains returned results */ - protected Map extractReturnedResults(CallableStatement cs, + protected Map extractReturnedResults(CallableStatement cs, @Nullable List updateCountParameters, @Nullable List resultSetParameters, int updateCount) throws SQLException { - Map results = new LinkedHashMap<>(4); + Map results = new LinkedHashMap<>(4); int rsIndex = 0; int updateIndex = 0; boolean moreResults; @@ -1307,10 +1318,10 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @param parameters parameter list for the stored procedure * @return a Map that contains returned results */ - protected Map extractOutputParameters(CallableStatement cs, List parameters) + protected Map extractOutputParameters(CallableStatement cs, List parameters) throws SQLException { - Map results = CollectionUtils.newLinkedHashMap(parameters.size()); + Map results = CollectionUtils.newLinkedHashMap(parameters.size()); int sqlColIndex = 1; for (SqlParameter param : parameters) { if (param instanceof SqlOutParameter outParam) { @@ -1353,13 +1364,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @param param the corresponding stored procedure parameter * @return a Map that contains returned results */ - protected Map processResultSet( + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/950 + protected Map<@Nullable String, @Nullable Object> processResultSet( @Nullable ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException { if (rs != null) { try { if (param.getRowMapper() != null) { - RowMapper rowMapper = param.getRowMapper(); + RowMapper rowMapper = param.getRowMapper(); Object data = (new RowMapperResultSetExtractor<>(rowMapper)).extractData(rs); return Collections.singletonMap(param.getName(), data); } @@ -1391,7 +1403,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @return the RowMapper to use * @see ColumnMapRowMapper */ - protected RowMapper> getColumnMapRowMapper() { + protected RowMapper> getColumnMapRowMapper() { return new ColumnMapRowMapper(); } @@ -1401,7 +1413,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @return the RowMapper to use * @see SingleColumnRowMapper */ - protected RowMapper getSingleColumnRowMapper(Class requiredType) { + protected RowMapper<@Nullable T> getSingleColumnRowMapper(Class requiredType) { return new SingleColumnRowMapper<>(requiredType); } @@ -1414,7 +1426,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { * @see #setResultsMapCaseInsensitive * @see #isResultsMapCaseInsensitive */ - protected Map createResultsMap() { + protected Map createResultsMap() { if (isResultsMapCaseInsensitive()) { return new LinkedCaseInsensitiveMap<>(); } @@ -1744,7 +1756,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { *

Uses a regular ResultSet, so we have to be careful when using it: * We don't use it for navigating since this could lead to unpredictable consequences. */ - private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor { + private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<@Nullable Object> { private final RowCallbackHandler rch; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ParameterizedPreparedStatementSetter.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ParameterizedPreparedStatementSetter.java index 9c7fe9f1059..1dd4caf3e6e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ParameterizedPreparedStatementSetter.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ParameterizedPreparedStatementSetter.java @@ -19,6 +19,8 @@ package org.springframework.jdbc.core; import java.sql.PreparedStatement; import java.sql.SQLException; +import org.jspecify.annotations.Nullable; + /** * Parameterized callback interface used by the {@link JdbcTemplate} class for * batch updates. @@ -47,6 +49,6 @@ public interface ParameterizedPreparedStatementSetter { * @param argument the object containing the values to be set * @throws SQLException if an SQLException is encountered (i.e. there is no need to catch SQLException) */ - void setValues(PreparedStatement ps, T argument) throws SQLException; + void setValues(PreparedStatement ps, @Nullable T argument) throws SQLException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java index 963fd4002dd..4fb5a1d0af9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCallback.java @@ -44,7 +44,7 @@ import org.springframework.dao.DataAccessException; * @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback) */ @FunctionalInterface -public interface PreparedStatementCallback { +public interface PreparedStatementCallback { /** * Gets called by {@code JdbcTemplate.execute} with an active JDBC @@ -75,6 +75,6 @@ public interface PreparedStatementCallback { * @see JdbcTemplate#queryForObject(String, Class, Object...) * @see JdbcTemplate#queryForList(String, Object...) */ - @Nullable T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException; + T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java index 329d62bc154..76c968095be 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/ResultSetExtractor.java @@ -50,7 +50,7 @@ import org.springframework.dao.DataAccessException; * @see org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor */ @FunctionalInterface -public interface ResultSetExtractor { +public interface ResultSetExtractor { /** * Implementations must implement this method to process the entire ResultSet. @@ -62,6 +62,6 @@ public interface ResultSetExtractor { * values or navigating (that is, there's no need to catch SQLException) * @throws DataAccessException in case of custom exceptions */ - @Nullable T extractData(ResultSet rs) throws SQLException, DataAccessException; + T extractData(ResultSet rs) throws SQLException, DataAccessException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java index 5d517765a6e..04308e4321d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java @@ -49,7 +49,7 @@ import org.jspecify.annotations.Nullable; * @see org.springframework.jdbc.object.MappingSqlQuery */ @FunctionalInterface -public interface RowMapper { +public interface RowMapper { /** * Implementations must implement this method to map each row of data in the @@ -61,6 +61,6 @@ public interface RowMapper { * @throws SQLException if an SQLException is encountered while getting * column values (that is, there's no need to catch SQLException) */ - @Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException; + T mapRow(ResultSet rs, int rowNum) throws SQLException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java index fbeac97dcb9..837139ae274 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapperResultSetExtractor.java @@ -21,6 +21,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; + import org.springframework.util.Assert; /** @@ -59,7 +61,7 @@ import org.springframework.util.Assert; * @see JdbcTemplate * @see org.springframework.jdbc.object.MappingSqlQuery */ -public class RowMapperResultSetExtractor implements ResultSetExtractor> { +public class RowMapperResultSetExtractor implements ResultSetExtractor> { private final RowMapper rowMapper; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java index 2dfcd3fd7b7..1aa73375668 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/SingleColumnRowMapper.java @@ -46,7 +46,7 @@ import org.springframework.util.NumberUtils; * @see JdbcTemplate#queryForList(String, Class) * @see JdbcTemplate#queryForObject(String, Class) */ -public class SingleColumnRowMapper implements RowMapper { +public class SingleColumnRowMapper implements RowMapper<@Nullable T> { private @Nullable Class requiredType; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java index 67cf9928bb6..e43834570ea 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCallback.java @@ -37,7 +37,7 @@ import org.springframework.dao.DataAccessException; * @see JdbcTemplate#execute(StatementCallback) */ @FunctionalInterface -public interface StatementCallback { +public interface StatementCallback { /** * Gets called by {@code JdbcTemplate.execute} with an active JDBC @@ -68,6 +68,6 @@ public interface StatementCallback { * @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForRowSet(String) */ - @Nullable T doInStatement(Statement stmt) throws SQLException, DataAccessException; + T doInStatement(Statement stmt) throws SQLException, DataAccessException; } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java index 6d633db2935..5ce2c1b1ed7 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java @@ -76,7 +76,7 @@ public interface NamedParameterJdbcOperations { * @return a result object returned by the action, or {@code null} * @throws DataAccessException if there is any problem */ - @Nullable T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback action) + T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback action) throws DataAccessException; /** @@ -94,7 +94,7 @@ public interface NamedParameterJdbcOperations { * @return a result object returned by the action, or {@code null} * @throws DataAccessException if there is any problem */ - @Nullable T execute(String sql, Map paramMap, PreparedStatementCallback action) + T execute(String sql, Map paramMap, PreparedStatementCallback action) throws DataAccessException; /** @@ -110,7 +110,7 @@ public interface NamedParameterJdbcOperations { * @return a result object returned by the action, or {@code null} * @throws DataAccessException if there is any problem */ - @Nullable T execute(String sql, PreparedStatementCallback action) throws DataAccessException; + T execute(String sql, PreparedStatementCallback action) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list @@ -122,7 +122,7 @@ public interface NamedParameterJdbcOperations { * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if the query fails */ - @Nullable T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) + T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) throws DataAccessException; /** @@ -136,7 +136,7 @@ public interface NamedParameterJdbcOperations { * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if the query fails */ - @Nullable T query(String sql, Map paramMap, ResultSetExtractor rse) + T query(String sql, Map paramMap, ResultSetExtractor rse) throws DataAccessException; /** @@ -150,7 +150,7 @@ public interface NamedParameterJdbcOperations { * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if the query fails */ - @Nullable T query(String sql, ResultSetExtractor rse) throws DataAccessException; + T query(String sql, ResultSetExtractor rse) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list of @@ -198,7 +198,7 @@ public interface NamedParameterJdbcOperations { * @return the result List, containing mapped objects * @throws DataAccessException if the query fails */ - List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException; /** @@ -212,7 +212,7 @@ public interface NamedParameterJdbcOperations { * @return the result List, containing mapped objects * @throws DataAccessException if the query fails */ - List query(String sql, Map paramMap, RowMapper rowMapper) + List query(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException; /** @@ -226,7 +226,7 @@ public interface NamedParameterJdbcOperations { * @return the result List, containing mapped objects * @throws DataAccessException if the query fails */ - List query(String sql, RowMapper rowMapper) throws DataAccessException; + List query(String sql, RowMapper rowMapper) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a list @@ -240,7 +240,7 @@ public interface NamedParameterJdbcOperations { * @throws DataAccessException if the query fails * @since 5.3 */ - Stream queryForStream(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + Stream queryForStream(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException; /** @@ -256,7 +256,7 @@ public interface NamedParameterJdbcOperations { * @throws DataAccessException if the query fails * @since 5.3 */ - Stream queryForStream(String sql, Map paramMap, RowMapper rowMapper) + Stream queryForStream(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException; /** @@ -272,7 +272,7 @@ public interface NamedParameterJdbcOperations { * if the query does not return exactly one row * @throws DataAccessException if the query fails */ - @Nullable T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException; /** @@ -289,7 +289,7 @@ public interface NamedParameterJdbcOperations { * if the query does not return exactly one row * @throws DataAccessException if the query fails */ - @Nullable T queryForObject(String sql, Map paramMap, RowMapper rowMapper) + T queryForObject(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException; /** @@ -346,7 +346,7 @@ public interface NamedParameterJdbcOperations { * @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String) * @see org.springframework.jdbc.core.ColumnMapRowMapper */ - Map queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException; + Map queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a @@ -366,7 +366,7 @@ public interface NamedParameterJdbcOperations { * @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String) * @see org.springframework.jdbc.core.ColumnMapRowMapper */ - Map queryForMap(String sql, Map paramMap) throws DataAccessException; + Map queryForMap(String sql, Map paramMap) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a @@ -382,7 +382,7 @@ public interface NamedParameterJdbcOperations { * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class) * @see org.springframework.jdbc.core.SingleColumnRowMapper */ - List queryForList(String sql, SqlParameterSource paramSource, Class elementType) + List<@Nullable T> queryForList(String sql, SqlParameterSource paramSource, Class elementType) throws DataAccessException; /** @@ -400,7 +400,7 @@ public interface NamedParameterJdbcOperations { * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class) * @see org.springframework.jdbc.core.SingleColumnRowMapper */ - List queryForList(String sql, Map paramMap, Class elementType) + List<@Nullable T> queryForList(String sql, Map paramMap, Class elementType) throws DataAccessException; /** @@ -416,7 +416,7 @@ public interface NamedParameterJdbcOperations { * @throws DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String) */ - List> queryForList(String sql, SqlParameterSource paramSource) throws DataAccessException; + List> queryForList(String sql, SqlParameterSource paramSource) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a @@ -432,7 +432,7 @@ public interface NamedParameterJdbcOperations { * @throws DataAccessException if the query fails * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String) */ - List> queryForList(String sql, Map paramMap) throws DataAccessException; + List> queryForList(String sql, Map paramMap) throws DataAccessException; /** * Query given SQL to create a prepared statement from SQL and a diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java index f41cd0eb7b1..fd7ae1996c6 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -161,40 +161,40 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations @Override - public @Nullable T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback action) + public T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback action) throws DataAccessException { return getJdbcOperations().execute(getPreparedStatementCreator(sql, paramSource), action); } @Override - public @Nullable T execute(String sql, Map paramMap, PreparedStatementCallback action) + public T execute(String sql, Map paramMap, PreparedStatementCallback action) throws DataAccessException { return execute(sql, new MapSqlParameterSource(paramMap), action); } @Override - public @Nullable T execute(String sql, PreparedStatementCallback action) throws DataAccessException { + public T execute(String sql, PreparedStatementCallback action) throws DataAccessException { return execute(sql, EmptySqlParameterSource.INSTANCE, action); } @Override - public @Nullable T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) + public T query(String sql, SqlParameterSource paramSource, ResultSetExtractor rse) throws DataAccessException { return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rse); } @Override - public @Nullable T query(String sql, Map paramMap, ResultSetExtractor rse) + public T query(String sql, Map paramMap, ResultSetExtractor rse) throws DataAccessException { return query(sql, new MapSqlParameterSource(paramMap), rse); } @Override - public @Nullable T query(String sql, ResultSetExtractor rse) throws DataAccessException { + public T query(String sql, ResultSetExtractor rse) throws DataAccessException { return query(sql, EmptySqlParameterSource.INSTANCE, rse); } @@ -218,14 +218,14 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations } @Override - public List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + public List query(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException { return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper); } @Override - public List query(String sql, Map paramMap, RowMapper rowMapper) + public List query(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException { return query(sql, new MapSqlParameterSource(paramMap), rowMapper); @@ -237,21 +237,21 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations } @Override - public Stream queryForStream(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + public Stream queryForStream(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException { return getJdbcOperations().queryForStream(getPreparedStatementCreator(sql, paramSource), rowMapper); } @Override - public Stream queryForStream(String sql, Map paramMap, RowMapper rowMapper) + public Stream queryForStream(String sql, Map paramMap, RowMapper rowMapper) throws DataAccessException { return queryForStream(sql, new MapSqlParameterSource(paramMap), rowMapper); } @Override - public @Nullable T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) + public T queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper) throws DataAccessException { List results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper); @@ -259,7 +259,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations } @Override - public @Nullable T queryForObject(String sql, Map paramMap, RowMapperrowMapper) + public T queryForObject(String sql, Map paramMap, RowMapperrowMapper) throws DataAccessException { return queryForObject(sql, new MapSqlParameterSource(paramMap), rowMapper); @@ -280,42 +280,44 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations } @Override - public Map queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException { - Map result = queryForObject(sql, paramSource, new ColumnMapRowMapper()); + public Map queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException { + Map result = queryForObject(sql, paramSource, new ColumnMapRowMapper()); Assert.state(result != null, "No result map"); return result; } @Override - public Map queryForMap(String sql, Map paramMap) throws DataAccessException { - Map result = queryForObject(sql, paramMap, new ColumnMapRowMapper()); + public Map queryForMap(String sql, Map paramMap) throws DataAccessException { + Map result = queryForObject(sql, paramMap, new ColumnMapRowMapper()); Assert.state(result != null, "No result map"); return result; } @Override - public List queryForList(String sql, SqlParameterSource paramSource, Class elementType) + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, SqlParameterSource paramSource, Class elementType) throws DataAccessException { return query(sql, paramSource, new SingleColumnRowMapper<>(elementType)); } @Override - public List queryForList(String sql, Map paramMap, Class elementType) + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable T> queryForList(String sql, Map paramMap, Class elementType) throws DataAccessException { return queryForList(sql, new MapSqlParameterSource(paramMap), elementType); } @Override - public List> queryForList(String sql, SqlParameterSource paramSource) + public List> queryForList(String sql, SqlParameterSource paramSource) throws DataAccessException { return query(sql, paramSource, new ColumnMapRowMapper()); } @Override - public List> queryForList(String sql, Map paramMap) + public List> queryForList(String sql, Map paramMap) throws DataAccessException { return queryForList(sql, new MapSqlParameterSource(paramMap)); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java index 140334683fc..b4569a7cb41 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcCall.java @@ -379,7 +379,7 @@ public abstract class AbstractJdbcCall { * @param parameterSource parameter names and values to be used in call * @return a Map of out parameters */ - protected Map doExecute(SqlParameterSource parameterSource) { + protected Map doExecute(SqlParameterSource parameterSource) { checkCompiled(); Map params = matchInParameterValuesWithCallParameters(parameterSource); return executeCallInternal(params); @@ -391,7 +391,7 @@ public abstract class AbstractJdbcCall { * declared for the stored procedure. * @return a Map of out parameters */ - protected Map doExecute(Object... args) { + protected Map doExecute(Object... args) { checkCompiled(); Map params = matchInParameterValuesWithCallParameters(args); return executeCallInternal(params); @@ -402,7 +402,7 @@ public abstract class AbstractJdbcCall { * @param args a Map of parameter name and values * @return a Map of out parameters */ - protected Map doExecute(Map args) { + protected Map doExecute(Map args) { checkCompiled(); Map params = matchInParameterValuesWithCallParameters(args); return executeCallInternal(params); @@ -411,7 +411,7 @@ public abstract class AbstractJdbcCall { /** * Delegate method to perform the actual call processing. */ - private Map executeCallInternal(Map args) { + private Map executeCallInternal(Map args) { CallableStatementCreator csc = getCallableStatementFactory().newCallableStatementCreator(args); if (logger.isDebugEnabled()) { logger.debug("The following parameters are used for call " + getCallString() + " with " + args); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java index 4d852eb54b9..485f2959a7d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/AbstractJdbcInsert.java @@ -448,6 +448,7 @@ public abstract class AbstractJdbcInsert { /** * Delegate method to execute the insert, generating any number of keys. */ + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 private KeyHolder executeInsertAndReturnKeyHolderInternal(List values) { if (logger.isDebugEnabled()) { logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values); @@ -496,7 +497,7 @@ public abstract class AbstractJdbcInsert { keyHolder.getKeyList().add(keys); } else { - getJdbcTemplate().execute((ConnectionCallback) con -> { + getJdbcTemplate().execute((ConnectionCallback<@Nullable Object>) con -> { // Do the insert PreparedStatement ps = null; try { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java index 8a91191b63b..c378c4e606f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/DefaultJdbcClient.java @@ -239,18 +239,18 @@ final class DefaultJdbcClient implements JdbcClient { new IndexedParamResultQuerySpec()); } - @SuppressWarnings("unchecked") @Override - public MappedQuerySpec query(Class mappedClass) { + @SuppressWarnings({"unchecked", "NullAway"}) // See https://github.com/uber/NullAway/issues/1075 + public MappedQuerySpec<@Nullable T> query(Class mappedClass) { RowMapper rowMapper = rowMapperCache.computeIfAbsent(mappedClass, key -> BeanUtils.isSimpleProperty(mappedClass) ? new SingleColumnRowMapper<>(mappedClass, conversionService) : new SimplePropertyRowMapper<>(mappedClass, conversionService)); - return query((RowMapper) rowMapper); + return query((RowMapper<@Nullable T>) rowMapper); } @Override - public MappedQuerySpec query(RowMapper rowMapper) { + public MappedQuerySpec query(RowMapper rowMapper) { return (useNamedParams() ? new NamedParamMappedQuerySpec<>(rowMapper) : new IndexedParamMappedQuerySpec<>(rowMapper)); @@ -267,7 +267,7 @@ final class DefaultJdbcClient implements JdbcClient { } @Override - public T query(ResultSetExtractor rse) { + public T query(ResultSetExtractor rse) { T result = (useNamedParams() ? this.namedParamOps.query(this.sql, this.namedParamSource, rse) : this.classicOps.query(statementCreatorForIndexedParams(), rse)); @@ -332,17 +332,18 @@ final class DefaultJdbcClient implements JdbcClient { } @Override - public List> listOfRows() { + public List> listOfRows() { return classicOps.queryForList(sql, indexedParams.toArray()); } @Override - public Map singleRow() { + public Map singleRow() { return classicOps.queryForMap(sql, indexedParams.toArray()); } @Override - public List singleColumn() { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable Object> singleColumn() { return classicOps.queryForList(sql, Object.class, indexedParams.toArray()); } } @@ -356,23 +357,25 @@ final class DefaultJdbcClient implements JdbcClient { } @Override - public List> listOfRows() { + public List> listOfRows() { return namedParamOps.queryForList(sql, namedParamSource); } @Override - public Map singleRow() { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public Map singleRow() { return namedParamOps.queryForMap(sql, namedParamSource); } @Override - public List singleColumn() { + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 + public List<@Nullable Object> singleColumn() { return namedParamOps.queryForList(sql, namedParamSource, Object.class); } } - private class IndexedParamMappedQuerySpec implements MappedQuerySpec { + private class IndexedParamMappedQuerySpec implements MappedQuerySpec { private final RowMapper rowMapper; @@ -392,7 +395,7 @@ final class DefaultJdbcClient implements JdbcClient { } - private class NamedParamMappedQuerySpec implements MappedQuerySpec { + private class NamedParamMappedQuerySpec implements MappedQuerySpec { private final RowMapper rowMapper; 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 7c512790e2a..e98b421bf2b 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 @@ -26,6 +26,7 @@ import java.util.stream.Stream; import javax.sql.DataSource; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionService; @@ -286,7 +287,7 @@ public interface JdbcClient { * @see org.springframework.jdbc.core.SingleColumnRowMapper * @see org.springframework.jdbc.core.SimplePropertyRowMapper */ - MappedQuerySpec query(Class mappedClass); + MappedQuerySpec<@Nullable T> query(Class mappedClass); /** * Proceed towards execution of a mapped query, with several options @@ -295,7 +296,7 @@ public interface JdbcClient { * @return the mapped query specification * @see java.sql.PreparedStatement#executeQuery() */ - MappedQuerySpec query(RowMapper rowMapper); + MappedQuerySpec query(RowMapper rowMapper); /** * Execute a query with the provided SQL statement, @@ -312,7 +313,7 @@ public interface JdbcClient { * @return the value returned by the ResultSetExtractor * @see java.sql.PreparedStatement#executeQuery() */ - T query(ResultSetExtractor rse); + T query(ResultSetExtractor rse); /** * Execute the provided SQL statement as an update. @@ -365,14 +366,14 @@ public interface JdbcClient { * with each result row represented as a map of * case-insensitive column names to column values */ - List> listOfRows(); + List> listOfRows(); /** * Retrieve a single row result. * @return the result row represented as a map of * case-insensitive column names to column values */ - Map singleRow(); + Map singleRow(); /** * Retrieve a single column result, @@ -380,7 +381,7 @@ public interface JdbcClient { * @return a (potentially empty) list of rows, with each * row represented as its single column value */ - List singleColumn(); + List<@Nullable Object> singleColumn(); /** * Retrieve a single value result. @@ -390,6 +391,7 @@ public interface JdbcClient { * @see #optionalValue() * @see DataAccessUtils#requiredSingleResult(Collection) */ + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 default Object singleValue() { return DataAccessUtils.requiredSingleResult(singleColumn()); } @@ -401,6 +403,7 @@ public interface JdbcClient { * @see #singleValue() * @see DataAccessUtils#optionalResult(Collection) */ + @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075 default Optional optionalValue() { return DataAccessUtils.optionalResult(singleColumn()); } @@ -412,7 +415,7 @@ public interface JdbcClient { * * @param the RowMapper-declared result type */ - interface MappedQuerySpec { + interface MappedQuerySpec { /** * Retrieve the result as a lazily resolved stream of mapped objects, @@ -447,7 +450,7 @@ public interface JdbcClient { * @see #optional() * @see DataAccessUtils#requiredSingleResult(Collection) */ - default T single() { + default @NonNull T single() { return DataAccessUtils.requiredSingleResult(list()); } @@ -457,7 +460,7 @@ public interface JdbcClient { * @see #single() * @see DataAccessUtils#optionalResult(Collection) */ - default Optional optional() { + default Optional<@NonNull T> optional() { return DataAccessUtils.optionalResult(list()); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java index 07b69bf4dc7..9ecfbd7171c 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCall.java @@ -186,17 +186,17 @@ public class SimpleJdbcCall extends AbstractJdbcCall implements SimpleJdbcCallOp } @Override - public Map execute(Object... args) { + public Map execute(Object... args) { return doExecute(args); } @Override - public Map execute(Map args) { + public Map execute(Map args) { return doExecute(args); } @Override - public Map execute(SqlParameterSource parameterSource) { + public Map execute(SqlParameterSource parameterSource) { return doExecute(parameterSource); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCallOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCallOperations.java index 9a932a7ece0..d589c4329eb 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCallOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/simple/SimpleJdbcCallOperations.java @@ -174,7 +174,7 @@ public interface SimpleJdbcCallOperations { * the stored procedure. * @return a Map of output params */ - Map execute(Object... args); + Map execute(Object... args); /** * Execute the stored procedure and return a map of output params, keyed by name @@ -182,7 +182,7 @@ public interface SimpleJdbcCallOperations { * @param args a Map containing the parameter values to be used in the call * @return a Map of output params */ - Map execute(Map args); + Map execute(Map args); /** * Execute the stored procedure and return a map of output params, keyed by name @@ -190,6 +190,6 @@ public interface SimpleJdbcCallOperations { * @param args the SqlParameterSource containing the parameter values to be used in the call * @return a Map of output params */ - Map execute(SqlParameterSource args); + Map execute(SqlParameterSource args); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobStreamingResultSetExtractor.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobStreamingResultSetExtractor.java index ea426a51288..d1a826d0b25 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobStreamingResultSetExtractor.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/support/AbstractLobStreamingResultSetExtractor.java @@ -59,7 +59,7 @@ import org.springframework.jdbc.core.ResultSetExtractor; * in favor of {@link ResultSet#getBinaryStream}/{@link ResultSet#getCharacterStream} usage */ @Deprecated(since = "6.2") -public abstract class AbstractLobStreamingResultSetExtractor implements ResultSetExtractor { +public abstract class AbstractLobStreamingResultSetExtractor implements ResultSetExtractor<@Nullable T> { /** * Delegates to handleNoRowFound, handleMultipleRowsFound and streamData, diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java index bbdf1f1fa23..3d920466e19 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/GenericSqlQuery.java @@ -35,7 +35,7 @@ import org.springframework.util.Assert; * @see #setRowMapper * @see #setRowMapperClass */ -public class GenericSqlQuery extends SqlQuery { +public class GenericSqlQuery extends SqlQuery { private @Nullable RowMapper rowMapper; diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/MappingSqlQuery.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/MappingSqlQuery.java index 70c6f8b1ef3..b11d53bc01a 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/MappingSqlQuery.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/MappingSqlQuery.java @@ -39,7 +39,7 @@ import org.jspecify.annotations.Nullable; * @param the result type * @see MappingSqlQueryWithParameters */ -public abstract class MappingSqlQuery extends MappingSqlQueryWithParameters { +public abstract class MappingSqlQuery extends MappingSqlQueryWithParameters { /** * Constructor that allows use as a JavaBean. @@ -63,7 +63,7 @@ public abstract class MappingSqlQuery extends MappingSqlQueryWithParameters context) + protected final T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map context) throws SQLException { return mapRow(rs, rowNum); @@ -82,6 +82,6 @@ public abstract class MappingSqlQuery extends MappingSqlQueryWithParameters extends SqlQuery { +public abstract class MappingSqlQueryWithParameters extends SqlQuery { /** * Constructor to allow use as a JavaBean. @@ -93,7 +93,7 @@ public abstract class MappingSqlQueryWithParameters extends SqlQuery { * Subclasses can simply not catch SQLExceptions, relying on the * framework to clean up. */ - protected abstract @Nullable T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map context) + protected abstract T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map context) throws SQLException; @@ -116,7 +116,7 @@ public abstract class MappingSqlQueryWithParameters extends SqlQuery { } @Override - public @Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException { + public T mapRow(ResultSet rs, int rowNum) throws SQLException { return MappingSqlQueryWithParameters.this.mapRow(rs, rowNum, this.params, this.context); } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java index 929b6303d64..beae01ead32 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlFunction.java @@ -51,7 +51,7 @@ import org.springframework.jdbc.core.SingleColumnRowMapper; * @param the result type * @see StoredProcedure */ -public class SqlFunction extends MappingSqlQuery { +public class SqlFunction extends MappingSqlQuery<@Nullable T> { private final SingleColumnRowMapper rowMapper = new SingleColumnRowMapper<>(); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlQuery.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlQuery.java index cfee2d0937c..f28629eb443 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlQuery.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/SqlQuery.java @@ -59,7 +59,7 @@ import org.springframework.jdbc.core.namedparam.ParsedSql; * @param the result type * @see SqlUpdate */ -public abstract class SqlQuery extends SqlOperation { +public abstract class SqlQuery extends SqlOperation { /** * Constructor to allow use as a JavaBean. diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java b/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java index 29ed7cacab6..bf374c8bbe2 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/object/StoredProcedure.java @@ -21,6 +21,8 @@ import java.util.Map; import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.DataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.core.JdbcTemplate; @@ -109,8 +111,8 @@ public abstract class StoredProcedure extends SqlCall { * Output parameters will appear here, with their values after the stored procedure * has been called. */ - public Map execute(Object... inParams) { - Map paramsToUse = new HashMap<>(); + public Map execute(Object... inParams) { + Map paramsToUse = new HashMap<>(); validateParameters(inParams); int i = 0; for (SqlParameter sqlParameter : getDeclaredParameters()) { @@ -135,7 +137,7 @@ public abstract class StoredProcedure extends SqlCall { * Output parameters will appear here, with their values after the * stored procedure has been called. */ - public Map execute(Map inParams) throws DataAccessException { + public Map execute(Map inParams) throws DataAccessException { validateParameters(inParams.values().toArray()); return getJdbcTemplate().call(newCallableStatementCreator(inParams), getDeclaredParameters()); } @@ -156,7 +158,7 @@ public abstract class StoredProcedure extends SqlCall { * Output parameters will appear here, with their values after the * stored procedure has been called. */ - public Map execute(ParameterMapper inParamMapper) throws DataAccessException { + public Map execute(ParameterMapper inParamMapper) throws DataAccessException { checkCompiled(); return getJdbcTemplate().call(newCallableStatementCreator(inParamMapper), getDeclaredParameters()); } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseMetaDataCallback.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseMetaDataCallback.java index 9612857b619..18a6852f2dd 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseMetaDataCallback.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/DatabaseMetaDataCallback.java @@ -19,6 +19,8 @@ package org.springframework.jdbc.support; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import org.jspecify.annotations.Nullable; + /** * A callback interface used by the JdbcUtils class. Implementations of this * interface perform the actual work of extracting database meta-data, but @@ -31,7 +33,7 @@ import java.sql.SQLException; * @see JdbcUtils#extractDatabaseMetaData(javax.sql.DataSource, DatabaseMetaDataCallback) */ @FunctionalInterface -public interface DatabaseMetaDataCallback { +public interface DatabaseMetaDataCallback { /** * Implementations must implement this method to process the meta-data diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java index b80a1681bb7..95f4f57e34f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java @@ -321,7 +321,7 @@ public abstract class JdbcUtils { * @throws MetaDataAccessException if meta-data access failed * @see java.sql.DatabaseMetaData */ - public static T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback action) + public static T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback action) throws MetaDataAccessException { Connection con = null; diff --git a/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt index dc2a597c88d..0e8f4aaf88d 100644 --- a/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt +++ b/spring-jdbc/src/main/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensions.kt @@ -35,7 +35,7 @@ inline fun JdbcOperations.queryForObject(sql: String): T = * @since 5.0 */ inline fun JdbcOperations.queryForObject(sql: String, vararg args: Any, crossinline function: (ResultSet, Int) -> T): T = - queryForObject(sql, { resultSet, i -> function(resultSet, i) }, *args) as T + queryForObject(sql, { resultSet, i -> function(resultSet, i) }, *args) /** * Extension for [JdbcOperations.queryForObject] providing a @@ -44,7 +44,7 @@ inline fun JdbcOperations.queryForObject(sql: String, vararg args: A * @author Mario Arias * @since 5.0 */ -inline fun JdbcOperations.queryForObject(sql: String, args: Array, argTypes: IntArray): T? = +inline fun JdbcOperations.queryForObject(sql: String, args: Array, argTypes: IntArray): T = queryForObject(sql, args, argTypes, T::class.java as Class<*>) as T /** @@ -54,7 +54,7 @@ inline fun JdbcOperations.queryForObject(sql: String, args: Array JdbcOperations.queryForObject(sql: String, args: Array): T? = +inline fun JdbcOperations.queryForObject(sql: String, args: Array): T = queryForObject(sql, T::class.java as Class<*>, args) as T /** @@ -63,8 +63,9 @@ inline fun JdbcOperations.queryForObject(sql: String, args: Array JdbcOperations.queryForList(sql: String): List = - queryForList(sql, T::class.java) +@Suppress("UNCHECKED_CAST") +inline fun JdbcOperations.queryForList(sql: String): List = + queryForList(sql, T::class.java) as List /** * Extension for [JdbcOperations.queryForList] providing a @@ -73,9 +74,10 @@ inline fun JdbcOperations.queryForList(sql: String): List = * @author Mario Arias * @since 5.0 */ -inline fun JdbcOperations.queryForList(sql: String, args: Array, - argTypes: IntArray): List = - queryForList(sql, args, argTypes, T::class.java) +@Suppress("UNCHECKED_CAST") +inline fun JdbcOperations.queryForList(sql: String, args: Array, + argTypes: IntArray): List = + queryForList(sql, args, argTypes, T::class.java) as List /** * Extension for [JdbcOperations.queryForList] providing a @@ -84,8 +86,9 @@ inline fun JdbcOperations.queryForList(sql: String, args: Arra * @author Mario Arias * @since 5.0 */ -inline fun JdbcOperations.queryForList(sql: String, args: Array): List = - queryForList(sql, T::class.java, args) +@Suppress("UNCHECKED_CAST") +inline fun JdbcOperations.queryForList(sql: String, args: Array): List = + queryForList(sql, T::class.java, args) as List /** @@ -95,9 +98,9 @@ inline fun JdbcOperations.queryForList(sql: String, args: Arra * @author Mario Arias * @since 5.0 */ -inline fun JdbcOperations.query(sql: String, vararg args: Any, - crossinline function: (ResultSet) -> T): T = - query(sql, ResultSetExtractor { function(it) }, *args) as T +fun JdbcOperations.query(sql: String, vararg args: Any, + function: (ResultSet) -> T): T = + query(sql, ResultSetExtractor { function(it) }, *args) /** * Extension for [JdbcOperations.query] providing a RowCallbackHandler-like function diff --git a/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt index 8d05f7b316b..d26f3ce63bd 100644 --- a/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt +++ b/spring-jdbc/src/test/kotlin/org/springframework/jdbc/core/JdbcOperationsExtensionsTests.kt @@ -43,6 +43,13 @@ class JdbcOperationsExtensionsTests { verify { template.queryForObject(sql, any>()) } } + @Test + fun `queryForObject with nullable reified type parameters`() { + every { template.queryForObject(sql, any>()) } returns null + assertThat(template.queryForObject(sql)).isNull() + verify { template.queryForObject(sql, any>()) } + } + @Test fun `queryForObject with RowMapper-like function`() { every { template.queryForObject(sql, any>(), any()) } returns 2 @@ -52,9 +59,9 @@ class JdbcOperationsExtensionsTests { @Test // gh-22682 fun `queryForObject with nullable RowMapper-like function`() { - every { template.queryForObject(sql, any>(), 3) } returns null + every { template.queryForObject(sql, any>(), 3) } returns null assertThat(template.queryForObject(sql, 3) { _, _ -> null }).isNull() - verify { template.queryForObject(eq(sql), any>(), eq(3)) } + verify { template.queryForObject(eq(sql), any>(), eq(3)) } } @Test @@ -66,6 +73,15 @@ class JdbcOperationsExtensionsTests { verify { template.queryForObject(sql, args, argTypes, any>()) } } + @Test + fun `queryForObject with nullable reified type parameters and argTypes`() { + val args = arrayOf(3) + val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber) + every { template.queryForObject(sql, args, argTypes, any>()) } returns null + assertThat(template.queryForObject(sql, args, argTypes)).isNull() + verify { template.queryForObject(sql, args, argTypes, any>()) } + } + @Test fun `queryForObject with reified type parameters and args`() { val args = arrayOf(3, 4) @@ -74,6 +90,14 @@ class JdbcOperationsExtensionsTests { verify { template.queryForObject(sql, any>(), args) } } + @Test + fun `queryForObject with nullable reified type parameters and args`() { + val args = arrayOf(3, 4) + every { template.queryForObject(sql, any>(), args) } returns null + assertThat(template.queryForObject(sql, args)).isNull() + verify { template.queryForObject(sql, any>(), args) } + } + @Test fun `queryForList with reified type parameters`() { val list = listOf(1, 2, 3) @@ -82,6 +106,14 @@ class JdbcOperationsExtensionsTests { verify { template.queryForList(sql, any>()) } } + @Test + fun `queryForList with nullable reified type parameters`() { + val list = listOf(1, null, 3) + every { template.queryForList(sql, any>()) } returns list + assertThat(template.queryForList(sql)).isEqualTo(list) + verify { template.queryForList(sql, any>()) } + } + @Test fun `queryForList with reified type parameters and argTypes`() { val list = listOf(1, 2, 3) @@ -92,6 +124,16 @@ class JdbcOperationsExtensionsTests { verify { template.queryForList(sql, args, argTypes, any>()) } } + @Test + fun `queryForList with nullable reified type parameters and argTypes`() { + val list = listOf(1, null, 3) + val args = arrayOf(3) + val argTypes = intArrayOf(JDBCType.INTEGER.vendorTypeNumber) + every { template.queryForList(sql, args, argTypes, any>()) } returns list + assertThat(template.queryForList(sql, args, argTypes)).isEqualTo(list) + verify { template.queryForList(sql, args, argTypes, any>()) } + } + @Test fun `queryForList with reified type parameters and args`() { val list = listOf(1, 2, 3) @@ -101,6 +143,15 @@ class JdbcOperationsExtensionsTests { verify { template.queryForList(sql, any>(), args) } } + @Test + fun `queryForList with nullable reified type parameters and args`() { + val list = listOf(1, null, 3) + val args = arrayOf(3, 4) + every { template.queryForList(sql, any>(), args) } returns list + template.queryForList(sql, args) + verify { template.queryForList(sql, any>(), args) } + } + @Test fun `query with ResultSetExtractor-like function`() { every { template.query(eq(sql), any>(), eq(3)) } returns 2 @@ -113,12 +164,11 @@ class JdbcOperationsExtensionsTests { @Test // gh-22682 fun `query with nullable ResultSetExtractor-like function`() { - every { template.query(eq(sql), any>(), eq(3)) } returns null + every { template.query(eq(sql), any>(), eq(3)) } returns null assertThat(template.query(sql, 3) { _ -> null }).isNull() - verify { template.query(eq(sql), any>(), eq(3)) } + verify { template.query(eq(sql), any>(), eq(3)) } } - @Suppress("RemoveExplicitTypeArguments") @Test fun `query with RowCallbackHandler-like function`() { every { template.query(sql, ofType(), 3) } returns Unit @@ -131,11 +181,21 @@ class JdbcOperationsExtensionsTests { @Test fun `query with RowMapper-like function`() { val list = mutableListOf(1, 2, 3) - every { template.query(sql, ofType>(), 3) } returns list + every { template.query(sql, ofType>(), 3) } returns list + assertThat(template.query(sql, 3) { rs, _ -> + rs.getInt(1) + }).isEqualTo(list) + verify { template.query(sql, ofType>(), 3) } + } + + @Test + fun `query with nullable RowMapper-like function`() { + val list = mutableListOf(1, null, 3) + every { template.query(sql, ofType>(), 3) } returns list assertThat(template.query(sql, 3) { rs, _ -> rs.getInt(1) }).isEqualTo(list) - verify { template.query(sql, ofType>(), 3) } + verify { template.query(sql, ofType>(), 3) } } } diff --git a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java index 0dd1266bd7b..9327c8cc9ff 100644 --- a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java +++ b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.springframework.dao.DataAccessException; @@ -115,7 +116,7 @@ public abstract class DataAccessUtils { * element has been found in the given Collection * @since 6.1 */ - public static Optional optionalResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { + public static Optional<@NonNull T> optionalResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { return Optional.ofNullable(singleResult(results)); } @@ -158,7 +159,7 @@ public abstract class DataAccessUtils { * @throws EmptyResultDataAccessException if no element at all * has been found in the given Collection */ - public static T requiredSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { + public static @NonNull T requiredSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { if (CollectionUtils.isEmpty(results)) { throw new EmptyResultDataAccessException(1); } @@ -184,7 +185,7 @@ public abstract class DataAccessUtils { * has been found in the given Collection * @since 5.0.2 */ - public static @Nullable T nullableSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { + public static T nullableSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { // This is identical to the requiredSingleResult implementation but differs in the // semantics of the incoming Collection (which we currently can't formally express) if (CollectionUtils.isEmpty(results)) {