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
This commit is contained in:
Sébastien Deleuze 2025-07-07 14:11:29 +02:00
parent 2b56b759af
commit a61b297967
33 changed files with 313 additions and 220 deletions

View File

@ -110,7 +110,7 @@ public abstract class CollectionUtils {
* @since 5.3 * @since 5.3
* @see #newHashMap(int) * @see #newHashMap(int)
*/ */
public static <K, V> LinkedHashMap<K, V> newLinkedHashMap(int expectedSize) { public static <K, V extends @Nullable Object> LinkedHashMap<K, V> newLinkedHashMap(int expectedSize) {
return new LinkedHashMap<>(computeInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR); return new LinkedHashMap<>(computeInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR);
} }

View File

@ -44,7 +44,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#execute(CallableStatementCreator, CallableStatementCallback) * @see JdbcTemplate#execute(CallableStatementCreator, CallableStatementCallback)
*/ */
@FunctionalInterface @FunctionalInterface
public interface CallableStatementCallback<T> { public interface CallableStatementCallback<T extends @Nullable Object> {
/** /**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC * Gets called by {@code JdbcTemplate.execute} with an active JDBC
@ -75,6 +75,6 @@ public interface CallableStatementCallback<T> {
* into a DataAccessException by an SQLExceptionTranslator * into a DataAccessException by an SQLExceptionTranslator
* @throws DataAccessException in case of custom exceptions * @throws DataAccessException in case of custom exceptions
*/ */
@Nullable T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException; T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException;
} }

View File

@ -44,13 +44,13 @@ import org.springframework.util.LinkedCaseInsensitiveMap;
* @see JdbcTemplate#queryForList(String) * @see JdbcTemplate#queryForList(String)
* @see JdbcTemplate#queryForMap(String) * @see JdbcTemplate#queryForMap(String)
*/ */
public class ColumnMapRowMapper implements RowMapper<Map<String, Object>> { public class ColumnMapRowMapper implements RowMapper<Map<String, @Nullable Object>> {
@Override @Override
public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException { public Map<String, @Nullable Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
ResultSetMetaData rsmd = rs.getMetaData(); ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount(); int columnCount = rsmd.getColumnCount();
Map<String, Object> mapOfColumnValues = createColumnMap(columnCount); Map<String, @Nullable Object> mapOfColumnValues = createColumnMap(columnCount);
for (int i = 1; i <= columnCount; i++) { for (int i = 1; i <= columnCount; i++) {
String column = JdbcUtils.lookupColumnName(rsmd, i); String column = JdbcUtils.lookupColumnName(rsmd, i);
mapOfColumnValues.putIfAbsent(getColumnKey(column), getColumnValue(rs, i)); mapOfColumnValues.putIfAbsent(getColumnKey(column), getColumnValue(rs, i));
@ -66,7 +66,7 @@ public class ColumnMapRowMapper implements RowMapper<Map<String, Object>> {
* @return the new Map instance * @return the new Map instance
* @see org.springframework.util.LinkedCaseInsensitiveMap * @see org.springframework.util.LinkedCaseInsensitiveMap
*/ */
protected Map<String, Object> createColumnMap(int columnCount) { protected Map<String, @Nullable Object> createColumnMap(int columnCount) {
return new LinkedCaseInsensitiveMap<>(columnCount); return new LinkedCaseInsensitiveMap<>(columnCount);
} }

View File

@ -41,7 +41,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#update * @see JdbcTemplate#update
*/ */
@FunctionalInterface @FunctionalInterface
public interface ConnectionCallback<T> { public interface ConnectionCallback<T extends @Nullable Object> {
/** /**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC * Gets called by {@code JdbcTemplate.execute} with an active JDBC
@ -65,6 +65,6 @@ public interface ConnectionCallback<T> {
* @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForObject(String, Class)
* @see JdbcTemplate#queryForRowSet(String) * @see JdbcTemplate#queryForRowSet(String)
*/ */
@Nullable T doInConnection(Connection con) throws SQLException, DataAccessException; T doInConnection(Connection con) throws SQLException, DataAccessException;
} }

View File

@ -68,7 +68,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(ConnectionCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(ConnectionCallback<T> action) throws DataAccessException;
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -87,7 +87,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(StatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(StatementCallback<T> action) throws DataAccessException;
/** /**
* Issue a single SQL execute, typically a DDL statement. * 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 * @throws DataAccessException if there is any problem executing the query
* @see #query(String, ResultSetExtractor, Object...) * @see #query(String, ResultSetExtractor, Object...)
*/ */
<T> @Nullable T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException; <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException;
/** /**
* Execute a query given static SQL, reading the ResultSet on a per-row * 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 * @throws DataAccessException if there is any problem executing the query
* @see #query(String, RowMapper, Object...) * @see #query(String, RowMapper, Object...)
*/ */
<T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Execute a query given static SQL, mapping each row to a result object * Execute a query given static SQL, mapping each row to a result object
@ -151,7 +151,7 @@ public interface JdbcOperations {
* @since 5.3 * @since 5.3
* @see #queryForStream(String, RowMapper, Object...) * @see #queryForStream(String, RowMapper, Object...)
*/ */
<T> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Execute a query given static SQL, mapping a single result row to a * 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 * @throws DataAccessException if there is any problem executing the query
* @see #queryForObject(String, RowMapper, Object...) * @see #queryForObject(String, RowMapper, Object...)
*/ */
<T> @Nullable T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Execute a query for a result object, given static SQL. * Execute a query for a result object, given static SQL.
@ -208,7 +208,7 @@ public interface JdbcOperations {
* @see #queryForMap(String, Object...) * @see #queryForMap(String, Object...)
* @see ColumnMapRowMapper * @see ColumnMapRowMapper
*/ */
Map<String, Object> queryForMap(String sql) throws DataAccessException; Map<String, @Nullable Object> queryForMap(String sql) throws DataAccessException;
/** /**
* Execute a query for a result list, given static SQL. * Execute a query for a result list, given static SQL.
@ -225,7 +225,7 @@ public interface JdbcOperations {
* @see #queryForList(String, Class, Object...) * @see #queryForList(String, Class, Object...)
* @see SingleColumnRowMapper * @see SingleColumnRowMapper
*/ */
<T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException; <T> List<@Nullable T> queryForList(String sql, Class<T> elementType) throws DataAccessException;
/** /**
* Execute a query for a result list, given static SQL. * 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 * @throws DataAccessException if there is any problem executing the query
* @see #queryForList(String, Object...) * @see #queryForList(String, Object...)
*/ */
List<Map<String, Object>> queryForList(String sql) throws DataAccessException; List<Map<String, @Nullable Object>> queryForList(String sql) throws DataAccessException;
/** /**
* Execute a query for an SqlRowSet, given static SQL. * 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 * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException;
/** /**
* Execute a JDBC data access operation, implemented as callback action * 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 * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException;
/** /**
* Query using a prepared statement, reading the ResultSet with a ResultSetExtractor. * 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 * @throws DataAccessException if there is any problem
* @see PreparedStatementCreatorFactory * @see PreparedStatementCreatorFactory
*/ */
<T> @Nullable T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException; <T extends @Nullable Object> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException;
/** /**
* Query using a prepared statement, reading the ResultSet with a ResultSetExtractor. * 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 * @return an arbitrary result object, as returned by the ResultSetExtractor
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) <T extends @Nullable Object> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse)
throws DataAccessException; throws DataAccessException;
/** /**
@ -354,7 +354,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
* @see java.sql.Types * @see java.sql.Types
*/ */
<T> @Nullable T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException; <T extends @Nullable Object> T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of arguments * 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 in favor of {@link #query(String, ResultSetExtractor, Object...)}
*/ */
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
<T> @Nullable T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor<T> rse) throws DataAccessException; <T extends @Nullable Object> T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor<T> rse) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of arguments * 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 * @throws DataAccessException if the query fails
* @since 3.0.1 * @since 3.0.1
*/ */
<T> @Nullable T query(String sql, ResultSetExtractor<T> rse, @Nullable Object @Nullable ... args) throws DataAccessException; <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query using a prepared statement, reading the ResultSet on a per-row basis * 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 * @throws DataAccessException if there is any problem
* @see PreparedStatementCreatorFactory * @see PreparedStatementCreatorFactory
*/ */
<T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * 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 * @return the result List, containing mapped objects
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) <T extends @Nullable Object> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -500,7 +500,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
* @see java.sql.Types * @see java.sql.Types
*/ */
<T> List<T> query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> List<T> query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 in favor of {@link #query(String, RowMapper, Object...)}
*/ */
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
<T> List<T> query(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> List<T> query(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 * @throws DataAccessException if the query fails
* @since 3.0.1 * @since 3.0.1
*/ */
<T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; <T extends @Nullable Object> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query using a prepared statement, mapping each row to a result object * Query using a prepared statement, mapping each row to a result object
@ -548,7 +548,7 @@ public interface JdbcOperations {
* @see PreparedStatementCreatorFactory * @see PreparedStatementCreatorFactory
* @since 5.3 * @since 5.3
*/ */
<T> Stream<T> queryForStream(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> Stream<T> queryForStream(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * 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 * @throws DataAccessException if the query fails
* @since 5.3 * @since 5.3
*/ */
<T> Stream<T> queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) <T extends @Nullable Object> Stream<T> queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -584,7 +584,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
* @since 5.3 * @since 5.3
*/ */
<T> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) <T extends @Nullable Object> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args)
throws DataAccessException; throws DataAccessException;
/** /**
@ -603,7 +603,7 @@ public interface JdbcOperations {
* if the query does not return exactly one row * if the query does not return exactly one row
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) <T extends @Nullable Object> T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -624,7 +624,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #queryForObject(String, RowMapper, Object...)} * @deprecated in favor of {@link #queryForObject(String, RowMapper, Object...)}
*/ */
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
<T> @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list * 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 * @throws DataAccessException if the query fails
* @since 3.0.1 * @since 3.0.1
*/ */
<T> @Nullable T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException; <T extends @Nullable Object> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * Query given SQL to create a prepared statement from SQL and a list of
@ -729,7 +729,7 @@ public interface JdbcOperations {
* @see ColumnMapRowMapper * @see ColumnMapRowMapper
* @see java.sql.Types * @see java.sql.Types
*/ */
Map<String, Object> queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; Map<String, @Nullable Object> 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 * 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 #queryForMap(String)
* @see ColumnMapRowMapper * @see ColumnMapRowMapper
*/ */
Map<String, Object> queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; Map<String, @Nullable Object> queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 #queryForList(String, Class)
* @see SingleColumnRowMapper * @see SingleColumnRowMapper
*/ */
<T> List<T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class<T> elementType) <T> List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class<T> elementType)
throws DataAccessException; throws DataAccessException;
/** /**
@ -792,7 +792,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #queryForList(String, Class, Object...)} * @deprecated in favor of {@link #queryForList(String, Class, Object...)}
*/ */
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
<T> List<T> queryForList(String sql, @Nullable Object @Nullable [] args, Class<T> elementType) throws DataAccessException; <T> List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, Class<T> elementType) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 #queryForList(String, Class)
* @see SingleColumnRowMapper * @see SingleColumnRowMapper
*/ */
<T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object @Nullable ... args) throws DataAccessException; <T> List<@Nullable T> queryForList(String sql, Class<T> elementType, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 #queryForList(String)
* @see java.sql.Types * @see java.sql.Types
*/ */
List<Map<String, Object>> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException; List<Map<String, @Nullable Object>> 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 * 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 * @throws DataAccessException if the query fails
* @see #queryForList(String) * @see #queryForList(String)
*/ */
List<Map<String, Object>> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException; List<Map<String, @Nullable Object>> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) throws DataAccessException;
/** /**
* Execute a JDBC data access operation, implemented as callback action * 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 * @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException;
/** /**
* Execute an SQL call using a CallableStatementCreator to provide SQL and * 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 * @return a Map of extracted out parameters
* @throws DataAccessException if there is any problem issuing the update * @throws DataAccessException if there is any problem issuing the update
*/ */
Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters) Map<String, @Nullable Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
throws DataAccessException; throws DataAccessException;
} }

View File

@ -357,7 +357,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@Override @Override
public <T> @Nullable T execute(ConnectionCallback<T> action) throws DataAccessException { public <T extends @Nullable Object> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null"); Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource()); Connection con = DataSourceUtils.getConnection(obtainDataSource());
@ -402,7 +402,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
// Methods dealing with static SQL (java.sql.Statement) // Methods dealing with static SQL (java.sql.Statement)
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
private <T> @Nullable T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException { private <T extends @Nullable Object> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null"); Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource()); Connection con = DataSourceUtils.getConnection(obtainDataSource());
@ -436,18 +436,19 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> @Nullable T execute(StatementCallback<T> action) throws DataAccessException { public <T extends @Nullable Object> T execute(StatementCallback<T> action) throws DataAccessException {
return execute(action, true); return execute(action, true);
} }
@Override @Override
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public void execute(String sql) throws DataAccessException { public void execute(String sql) throws DataAccessException {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]"); logger.debug("Executing SQL statement [" + sql + "]");
} }
// Callback to execute the statement. // Callback to execute the statement.
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider { class ExecuteStatementCallback implements StatementCallback<@Nullable Object>, SqlProvider {
@Override @Override
public @Nullable Object doInStatement(Statement stmt) throws SQLException { public @Nullable Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql); stmt.execute(sql);
@ -463,7 +464,8 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> @Nullable T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null"); Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -493,12 +495,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public void query(String sql, RowCallbackHandler rch) throws DataAccessException { public void query(String sql, RowCallbackHandler rch) throws DataAccessException {
query(sql, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); query(sql, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
} }
@Override @Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
} }
@ -525,12 +528,12 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public Map<String, Object> queryForMap(String sql) throws DataAccessException { public Map<String, @Nullable Object> queryForMap(String sql) throws DataAccessException {
return result(queryForObject(sql, getColumnMapRowMapper())); return result(queryForObject(sql, getColumnMapRowMapper()));
} }
@Override @Override
public <T> @Nullable T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper); List<T> results = query(sql, rowMapper);
return DataAccessUtils.nullableSingleResult(results); return DataAccessUtils.nullableSingleResult(results);
} }
@ -541,12 +544,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> List<T> queryForList(String sql, Class<T> elementType) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, Class<T> elementType) throws DataAccessException {
return query(sql, getSingleColumnRowMapper(elementType)); return query(sql, getSingleColumnRowMapper(elementType));
} }
@Override @Override
public List<Map<String, Object>> queryForList(String sql) throws DataAccessException { public List<Map<String, @Nullable Object>> queryForList(String sql) throws DataAccessException {
return query(sql, getColumnMapRowMapper()); return query(sql, getColumnMapRowMapper());
} }
@ -651,7 +655,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
// Methods dealing with prepared statements // Methods dealing with prepared statements
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
private <T> @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources) private <T extends @Nullable Object> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action, boolean closeResources)
throws DataAccessException { throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(psc, "PreparedStatementCreator must not be null");
@ -699,14 +703,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> @Nullable T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) public <T extends @Nullable Object> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException { throws DataAccessException {
return execute(psc, action, true); return execute(psc, action, true);
} }
@Override @Override
public <T> @Nullable T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException { public <T extends @Nullable Object> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action, true); return execute(new SimplePreparedStatementCreator(sql), action, true);
} }
@ -747,37 +751,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> @Nullable T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T extends @Nullable Object> T query(PreparedStatementCreator psc, ResultSetExtractor<T> rse) throws DataAccessException {
return query(psc, null, rse); return query(psc, null, rse);
} }
@Override @Override
public <T> @Nullable T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T extends @Nullable Object> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse); return query(new SimplePreparedStatementCreator(sql), pss, rse);
} }
@Override @Override
public <T> @Nullable T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException { public <T extends @Nullable Object> T query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse); return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse);
} }
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
@Override @Override
public <T> @Nullable T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor<T> rse) throws DataAccessException { public <T extends @Nullable Object> T query(String sql, @Nullable Object @Nullable [] args, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), rse); return query(sql, newArgPreparedStatementSetter(args), rse);
} }
@Override @Override
public <T> @Nullable T query(String sql, ResultSetExtractor<T> rse, @Nullable Object @Nullable ... args) throws DataAccessException { public <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object @Nullable ... args) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), rse); return query(sql, newArgPreparedStatementSetter(args), rse);
} }
@Override @Override
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException { public void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException {
query(psc, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); query(psc, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
} }
@Override @Override
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException { public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows)); query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch, this.maxRows));
} }
@ -799,28 +807,28 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); return result(query(psc, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
} }
@Override @Override
public <T> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> List<T> query(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); return result(query(sql, pss, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
} }
@Override @Override
public <T> List<T> query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> List<T> query(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); return result(query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
} }
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
@Override @Override
public <T> List<T> query(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> List<T> query(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows)));
} }
@Override @Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { public <T extends @Nullable Object> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException {
return result(query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 0, this.maxRows))); 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 * @throws DataAccessException if the query fails
* @since 5.3 * @since 5.3
*/ */
public <T> Stream<T> queryForStream(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss, public <T extends @Nullable Object> Stream<T> queryForStream(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss,
RowMapper<T> rowMapper) throws DataAccessException { RowMapper<T> rowMapper) throws DataAccessException {
return result(execute(psc, ps -> { return result(execute(psc, ps -> {
@ -858,22 +866,22 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> Stream<T> queryForStream(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> Stream<T> queryForStream(PreparedStatementCreator psc, RowMapper<T> rowMapper) throws DataAccessException {
return queryForStream(psc, null, rowMapper); return queryForStream(psc, null, rowMapper);
} }
@Override @Override
public <T> Stream<T> queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> Stream<T> queryForStream(String sql, @Nullable PreparedStatementSetter pss, RowMapper<T> rowMapper) throws DataAccessException {
return queryForStream(new SimplePreparedStatementCreator(sql), pss, rowMapper); return queryForStream(new SimplePreparedStatementCreator(sql), pss, rowMapper);
} }
@Override @Override
public <T> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { public <T extends @Nullable Object> Stream<T> queryForStream(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException {
return queryForStream(new SimplePreparedStatementCreator(sql), newArgPreparedStatementSetter(args), rowMapper); return queryForStream(new SimplePreparedStatementCreator(sql), newArgPreparedStatementSetter(args), rowMapper);
} }
@Override @Override
public <T> @Nullable T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper) public <T extends @Nullable Object> T queryForObject(String sql, @Nullable Object @Nullable [] args, int[] argTypes, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
List<T> results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1)); List<T> results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1));
@ -882,13 +890,13 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
@Override @Override
public <T> @Nullable T queryForObject(String sql,@Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException { public <T extends @Nullable Object> T queryForObject(String sql, @Nullable Object @Nullable [] args, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1)); List<T> results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results); return DataAccessUtils.nullableSingleResult(results);
} }
@Override @Override
public <T> @Nullable T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException { public <T extends @Nullable Object> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object @Nullable ... args) throws DataAccessException {
List<T> results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1)); List<T> results = query(sql, newArgPreparedStatementSetter(args), new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results); return DataAccessUtils.nullableSingleResult(results);
} }
@ -912,38 +920,41 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public Map<String, Object> queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { public Map<String, @Nullable Object> queryForMap(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException {
return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper())); return result(queryForObject(sql, args, argTypes, getColumnMapRowMapper()));
} }
@Override @Override
public Map<String, Object> queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { public Map<String, @Nullable Object> queryForMap(String sql, @Nullable Object @Nullable ... args) throws DataAccessException {
return result(queryForObject(sql, getColumnMapRowMapper(), args)); return result(queryForObject(sql, getColumnMapRowMapper(), args));
} }
@Override @Override
public <T> List<T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class<T> elementType) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes, Class<T> elementType) throws DataAccessException {
return query(sql, args, argTypes, getSingleColumnRowMapper(elementType)); return query(sql, args, argTypes, getSingleColumnRowMapper(elementType));
} }
@Deprecated(since = "5.3") @Deprecated(since = "5.3")
@Override @Override
public <T> List<T> queryForList(String sql, @Nullable Object @Nullable [] args, Class<T> elementType) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, @Nullable Object @Nullable [] args, Class<T> elementType) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType)); return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType));
} }
@Override @Override
public <T> List<T> queryForList(String sql, Class<T> elementType, @Nullable Object @Nullable ... args) throws DataAccessException { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, Class<T> elementType, @Nullable Object @Nullable ... args) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType)); return query(sql, newArgPreparedStatementSetter(args), getSingleColumnRowMapper(elementType));
} }
@Override @Override
public List<Map<String, Object>> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException { public List<Map<String, @Nullable Object>> queryForList(String sql, @Nullable Object @Nullable [] args, int[] argTypes) throws DataAccessException {
return query(sql, args, argTypes, getColumnMapRowMapper()); return query(sql, args, argTypes, getColumnMapRowMapper());
} }
@Override @Override
public List<Map<String, Object>> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException { public List<Map<String, @Nullable Object>> queryForList(String sql, @Nullable Object @Nullable ... args) throws DataAccessException {
return query(sql, newArgPreparedStatementSetter(args), getColumnMapRowMapper()); return query(sql, newArgPreparedStatementSetter(args), getColumnMapRowMapper());
} }
@ -1146,7 +1157,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@Override @Override
public <T> @Nullable T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) public <T extends @Nullable Object> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action)
throws DataAccessException { throws DataAccessException {
Assert.notNull(csc, "CallableStatementCreator must not be null"); Assert.notNull(csc, "CallableStatementCreator must not be null");
@ -1192,12 +1203,12 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
@Override @Override
public <T> @Nullable T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException { public <T extends @Nullable Object> T execute(String callString, CallableStatementCallback<T> action) throws DataAccessException {
return execute(new SimpleCallableStatementCreator(callString), action); return execute(new SimpleCallableStatementCreator(callString), action);
} }
@Override @Override
public Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters) public Map<String, @Nullable Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
throws DataAccessException { throws DataAccessException {
List<SqlParameter> updateCountParameters = new ArrayList<>(); List<SqlParameter> updateCountParameters = new ArrayList<>();
@ -1218,14 +1229,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
} }
} }
Map<String, Object> result = execute(csc, cs -> { Map<String, @Nullable Object> result = execute(csc, cs -> {
boolean retVal = cs.execute(); boolean retVal = cs.execute();
int updateCount = cs.getUpdateCount(); int updateCount = cs.getUpdateCount();
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("CallableStatement.execute() returned '" + retVal + "'"); logger.trace("CallableStatement.execute() returned '" + retVal + "'");
logger.trace("CallableStatement.getUpdateCount() returned " + updateCount); logger.trace("CallableStatement.getUpdateCount() returned " + updateCount);
} }
Map<String, Object> resultsMap = createResultsMap(); Map<String, @Nullable Object> resultsMap = createResultsMap();
if (retVal || updateCount != -1) { if (retVal || updateCount != -1) {
resultsMap.putAll(extractReturnedResults(cs, updateCountParameters, resultSetParameters, updateCount)); 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 * @param resultSetParameters the parameter list of declared resultSet parameters for the stored procedure
* @return a Map that contains returned results * @return a Map that contains returned results
*/ */
protected Map<String, Object> extractReturnedResults(CallableStatement cs, protected Map<String, @Nullable Object> extractReturnedResults(CallableStatement cs,
@Nullable List<SqlParameter> updateCountParameters, @Nullable List<SqlParameter> resultSetParameters, @Nullable List<SqlParameter> updateCountParameters, @Nullable List<SqlParameter> resultSetParameters,
int updateCount) throws SQLException { int updateCount) throws SQLException {
Map<String, Object> results = new LinkedHashMap<>(4); Map<String, @Nullable Object> results = new LinkedHashMap<>(4);
int rsIndex = 0; int rsIndex = 0;
int updateIndex = 0; int updateIndex = 0;
boolean moreResults; boolean moreResults;
@ -1307,10 +1318,10 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* @param parameters parameter list for the stored procedure * @param parameters parameter list for the stored procedure
* @return a Map that contains returned results * @return a Map that contains returned results
*/ */
protected Map<String, Object> extractOutputParameters(CallableStatement cs, List<SqlParameter> parameters) protected Map<String, @Nullable Object> extractOutputParameters(CallableStatement cs, List<SqlParameter> parameters)
throws SQLException { throws SQLException {
Map<String, Object> results = CollectionUtils.newLinkedHashMap(parameters.size()); Map<String, @Nullable Object> results = CollectionUtils.newLinkedHashMap(parameters.size());
int sqlColIndex = 1; int sqlColIndex = 1;
for (SqlParameter param : parameters) { for (SqlParameter param : parameters) {
if (param instanceof SqlOutParameter outParam) { if (param instanceof SqlOutParameter outParam) {
@ -1353,13 +1364,14 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* @param param the corresponding stored procedure parameter * @param param the corresponding stored procedure parameter
* @return a Map that contains returned results * @return a Map that contains returned results
*/ */
protected Map<String, Object> 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 { @Nullable ResultSet rs, ResultSetSupportingSqlParameter param) throws SQLException {
if (rs != null) { if (rs != null) {
try { try {
if (param.getRowMapper() != null) { if (param.getRowMapper() != null) {
RowMapper<?> rowMapper = param.getRowMapper(); RowMapper<? extends @Nullable Object> rowMapper = param.getRowMapper();
Object data = (new RowMapperResultSetExtractor<>(rowMapper)).extractData(rs); Object data = (new RowMapperResultSetExtractor<>(rowMapper)).extractData(rs);
return Collections.singletonMap(param.getName(), data); return Collections.singletonMap(param.getName(), data);
} }
@ -1391,7 +1403,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* @return the RowMapper to use * @return the RowMapper to use
* @see ColumnMapRowMapper * @see ColumnMapRowMapper
*/ */
protected RowMapper<Map<String, Object>> getColumnMapRowMapper() { protected RowMapper<Map<String, @Nullable Object>> getColumnMapRowMapper() {
return new ColumnMapRowMapper(); return new ColumnMapRowMapper();
} }
@ -1401,7 +1413,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* @return the RowMapper to use * @return the RowMapper to use
* @see SingleColumnRowMapper * @see SingleColumnRowMapper
*/ */
protected <T> RowMapper<T> getSingleColumnRowMapper(Class<T> requiredType) { protected <T> RowMapper<@Nullable T> getSingleColumnRowMapper(Class<T> requiredType) {
return new SingleColumnRowMapper<>(requiredType); return new SingleColumnRowMapper<>(requiredType);
} }
@ -1414,7 +1426,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* @see #setResultsMapCaseInsensitive * @see #setResultsMapCaseInsensitive
* @see #isResultsMapCaseInsensitive * @see #isResultsMapCaseInsensitive
*/ */
protected Map<String, Object> createResultsMap() { protected Map<String, @Nullable Object> createResultsMap() {
if (isResultsMapCaseInsensitive()) { if (isResultsMapCaseInsensitive()) {
return new LinkedCaseInsensitiveMap<>(); return new LinkedCaseInsensitiveMap<>();
} }
@ -1744,7 +1756,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
* <p>Uses a regular ResultSet, so we have to be careful when using it: * <p>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. * We don't use it for navigating since this could lead to unpredictable consequences.
*/ */
private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> { private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<@Nullable Object> {
private final RowCallbackHandler rch; private final RowCallbackHandler rch;

View File

@ -19,6 +19,8 @@ package org.springframework.jdbc.core;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import org.jspecify.annotations.Nullable;
/** /**
* Parameterized callback interface used by the {@link JdbcTemplate} class for * Parameterized callback interface used by the {@link JdbcTemplate} class for
* batch updates. * batch updates.
@ -47,6 +49,6 @@ public interface ParameterizedPreparedStatementSetter<T> {
* @param argument the object containing the values to be set * @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) * @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;
} }

View File

@ -44,7 +44,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback) * @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
*/ */
@FunctionalInterface @FunctionalInterface
public interface PreparedStatementCallback<T> { public interface PreparedStatementCallback<T extends @Nullable Object> {
/** /**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC * Gets called by {@code JdbcTemplate.execute} with an active JDBC
@ -75,6 +75,6 @@ public interface PreparedStatementCallback<T> {
* @see JdbcTemplate#queryForObject(String, Class, Object...) * @see JdbcTemplate#queryForObject(String, Class, Object...)
* @see JdbcTemplate#queryForList(String, Object...) * @see JdbcTemplate#queryForList(String, Object...)
*/ */
@Nullable T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException; T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException;
} }

View File

@ -50,7 +50,7 @@ import org.springframework.dao.DataAccessException;
* @see org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor * @see org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor
*/ */
@FunctionalInterface @FunctionalInterface
public interface ResultSetExtractor<T> { public interface ResultSetExtractor<T extends @Nullable Object> {
/** /**
* Implementations must implement this method to process the entire ResultSet. * Implementations must implement this method to process the entire ResultSet.
@ -62,6 +62,6 @@ public interface ResultSetExtractor<T> {
* values or navigating (that is, there's no need to catch SQLException) * values or navigating (that is, there's no need to catch SQLException)
* @throws DataAccessException in case of custom exceptions * @throws DataAccessException in case of custom exceptions
*/ */
@Nullable T extractData(ResultSet rs) throws SQLException, DataAccessException; T extractData(ResultSet rs) throws SQLException, DataAccessException;
} }

View File

@ -49,7 +49,7 @@ import org.jspecify.annotations.Nullable;
* @see org.springframework.jdbc.object.MappingSqlQuery * @see org.springframework.jdbc.object.MappingSqlQuery
*/ */
@FunctionalInterface @FunctionalInterface
public interface RowMapper<T> { public interface RowMapper<T extends @Nullable Object> {
/** /**
* Implementations must implement this method to map each row of data in the * Implementations must implement this method to map each row of data in the
@ -61,6 +61,6 @@ public interface RowMapper<T> {
* @throws SQLException if an SQLException is encountered while getting * @throws SQLException if an SQLException is encountered while getting
* column values (that is, there's no need to catch SQLException) * 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;
} }

View File

@ -21,6 +21,8 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -59,7 +61,7 @@ import org.springframework.util.Assert;
* @see JdbcTemplate * @see JdbcTemplate
* @see org.springframework.jdbc.object.MappingSqlQuery * @see org.springframework.jdbc.object.MappingSqlQuery
*/ */
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> { public class RowMapperResultSetExtractor<T extends @Nullable Object> implements ResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper; private final RowMapper<T> rowMapper;

View File

@ -46,7 +46,7 @@ import org.springframework.util.NumberUtils;
* @see JdbcTemplate#queryForList(String, Class) * @see JdbcTemplate#queryForList(String, Class)
* @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForObject(String, Class)
*/ */
public class SingleColumnRowMapper<T> implements RowMapper<T> { public class SingleColumnRowMapper<T> implements RowMapper<@Nullable T> {
private @Nullable Class<?> requiredType; private @Nullable Class<?> requiredType;

View File

@ -37,7 +37,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#execute(StatementCallback) * @see JdbcTemplate#execute(StatementCallback)
*/ */
@FunctionalInterface @FunctionalInterface
public interface StatementCallback<T> { public interface StatementCallback<T extends @Nullable Object> {
/** /**
* Gets called by {@code JdbcTemplate.execute} with an active JDBC * Gets called by {@code JdbcTemplate.execute} with an active JDBC
@ -68,6 +68,6 @@ public interface StatementCallback<T> {
* @see JdbcTemplate#queryForObject(String, Class) * @see JdbcTemplate#queryForObject(String, Class)
* @see JdbcTemplate#queryForRowSet(String) * @see JdbcTemplate#queryForRowSet(String)
*/ */
@Nullable T doInStatement(Statement stmt) throws SQLException, DataAccessException; T doInStatement(Statement stmt) throws SQLException, DataAccessException;
} }

View File

@ -76,7 +76,7 @@ public interface NamedParameterJdbcOperations {
* @return a result object returned by the action, or {@code null} * @return a result object returned by the action, or {@code null}
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback<T> action) <T extends @Nullable Object> T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback<T> action)
throws DataAccessException; throws DataAccessException;
/** /**
@ -94,7 +94,7 @@ public interface NamedParameterJdbcOperations {
* @return a result object returned by the action, or {@code null} * @return a result object returned by the action, or {@code null}
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(String sql, Map<String, ?> paramMap, PreparedStatementCallback<T> action) <T extends @Nullable Object> T execute(String sql, Map<String, ?> paramMap, PreparedStatementCallback<T> action)
throws DataAccessException; throws DataAccessException;
/** /**
@ -110,7 +110,7 @@ public interface NamedParameterJdbcOperations {
* @return a result object returned by the action, or {@code null} * @return a result object returned by the action, or {@code null}
* @throws DataAccessException if there is any problem * @throws DataAccessException if there is any problem
*/ */
<T> @Nullable T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException; <T extends @Nullable Object> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list * 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 * @return an arbitrary result object, as returned by the ResultSetExtractor
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T query(String sql, SqlParameterSource paramSource, ResultSetExtractor<T> rse) <T extends @Nullable Object> T query(String sql, SqlParameterSource paramSource, ResultSetExtractor<T> rse)
throws DataAccessException; throws DataAccessException;
/** /**
@ -136,7 +136,7 @@ public interface NamedParameterJdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor * @return an arbitrary result object, as returned by the ResultSetExtractor
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T query(String sql, Map<String, ?> paramMap, ResultSetExtractor<T> rse) <T extends @Nullable Object> T query(String sql, Map<String, ?> paramMap, ResultSetExtractor<T> rse)
throws DataAccessException; throws DataAccessException;
/** /**
@ -150,7 +150,7 @@ public interface NamedParameterJdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor * @return an arbitrary result object, as returned by the ResultSetExtractor
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException; <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list of * 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 * @return the result List, containing mapped objects
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) <T extends @Nullable Object> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -212,7 +212,7 @@ public interface NamedParameterJdbcOperations {
* @return the result List, containing mapped objects * @return the result List, containing mapped objects
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) <T extends @Nullable Object> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -226,7 +226,7 @@ public interface NamedParameterJdbcOperations {
* @return the result List, containing mapped objects * @return the result List, containing mapped objects
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException; <T extends @Nullable Object> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a list * 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 * @throws DataAccessException if the query fails
* @since 5.3 * @since 5.3
*/ */
<T> Stream<T> queryForStream(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) <T extends @Nullable Object> Stream<T> queryForStream(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -256,7 +256,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
* @since 5.3 * @since 5.3
*/ */
<T> Stream<T> queryForStream(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) <T extends @Nullable Object> Stream<T> queryForStream(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -272,7 +272,7 @@ public interface NamedParameterJdbcOperations {
* if the query does not return exactly one row * if the query does not return exactly one row
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) <T extends @Nullable Object> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -289,7 +289,7 @@ public interface NamedParameterJdbcOperations {
* if the query does not return exactly one row * if the query does not return exactly one row
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
*/ */
<T> @Nullable T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) <T extends @Nullable Object> T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
throws DataAccessException; throws DataAccessException;
/** /**
@ -346,7 +346,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String) * @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String)
* @see org.springframework.jdbc.core.ColumnMapRowMapper * @see org.springframework.jdbc.core.ColumnMapRowMapper
*/ */
Map<String, Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException; Map<String, @Nullable Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * 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.JdbcTemplate#queryForMap(String)
* @see org.springframework.jdbc.core.ColumnMapRowMapper * @see org.springframework.jdbc.core.ColumnMapRowMapper
*/ */
Map<String, Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException; Map<String, @Nullable Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * 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.JdbcTemplate#queryForList(String, Class)
* @see org.springframework.jdbc.core.SingleColumnRowMapper * @see org.springframework.jdbc.core.SingleColumnRowMapper
*/ */
<T> List<T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType) <T> List<@Nullable T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
throws DataAccessException; throws DataAccessException;
/** /**
@ -400,7 +400,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class) * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class)
* @see org.springframework.jdbc.core.SingleColumnRowMapper * @see org.springframework.jdbc.core.SingleColumnRowMapper
*/ */
<T> List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType) <T> List<@Nullable T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType)
throws DataAccessException; throws DataAccessException;
/** /**
@ -416,7 +416,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails * @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String) * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String)
*/ */
List<Map<String, Object>> queryForList(String sql, SqlParameterSource paramSource) throws DataAccessException; List<Map<String, @Nullable Object>> queryForList(String sql, SqlParameterSource paramSource) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * 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 * @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String) * @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String)
*/ */
List<Map<String, Object>> queryForList(String sql, Map<String, ?> paramMap) throws DataAccessException; List<Map<String, @Nullable Object>> queryForList(String sql, Map<String, ?> paramMap) throws DataAccessException;
/** /**
* Query given SQL to create a prepared statement from SQL and a * Query given SQL to create a prepared statement from SQL and a

View File

@ -161,40 +161,40 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
@Override @Override
public <T> @Nullable T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback<T> action) public <T extends @Nullable Object> T execute(String sql, SqlParameterSource paramSource, PreparedStatementCallback<T> action)
throws DataAccessException { throws DataAccessException {
return getJdbcOperations().execute(getPreparedStatementCreator(sql, paramSource), action); return getJdbcOperations().execute(getPreparedStatementCreator(sql, paramSource), action);
} }
@Override @Override
public <T> @Nullable T execute(String sql, Map<String, ?> paramMap, PreparedStatementCallback<T> action) public <T extends @Nullable Object> T execute(String sql, Map<String, ?> paramMap, PreparedStatementCallback<T> action)
throws DataAccessException { throws DataAccessException {
return execute(sql, new MapSqlParameterSource(paramMap), action); return execute(sql, new MapSqlParameterSource(paramMap), action);
} }
@Override @Override
public <T> @Nullable T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException { public <T extends @Nullable Object> T execute(String sql, PreparedStatementCallback<T> action) throws DataAccessException {
return execute(sql, EmptySqlParameterSource.INSTANCE, action); return execute(sql, EmptySqlParameterSource.INSTANCE, action);
} }
@Override @Override
public <T> @Nullable T query(String sql, SqlParameterSource paramSource, ResultSetExtractor<T> rse) public <T extends @Nullable Object> T query(String sql, SqlParameterSource paramSource, ResultSetExtractor<T> rse)
throws DataAccessException { throws DataAccessException {
return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rse); return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rse);
} }
@Override @Override
public <T> @Nullable T query(String sql, Map<String, ?> paramMap, ResultSetExtractor<T> rse) public <T extends @Nullable Object> T query(String sql, Map<String, ?> paramMap, ResultSetExtractor<T> rse)
throws DataAccessException { throws DataAccessException {
return query(sql, new MapSqlParameterSource(paramMap), rse); return query(sql, new MapSqlParameterSource(paramMap), rse);
} }
@Override @Override
public <T> @Nullable T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException { public <T extends @Nullable Object> T query(String sql, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, EmptySqlParameterSource.INSTANCE, rse); return query(sql, EmptySqlParameterSource.INSTANCE, rse);
} }
@ -218,14 +218,14 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
} }
@Override @Override
public <T> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) public <T extends @Nullable Object> List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper); return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
} }
@Override @Override
public <T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) public <T extends @Nullable Object> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
return query(sql, new MapSqlParameterSource(paramMap), rowMapper); return query(sql, new MapSqlParameterSource(paramMap), rowMapper);
@ -237,21 +237,21 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
} }
@Override @Override
public <T> Stream<T> queryForStream(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) public <T extends @Nullable Object> Stream<T> queryForStream(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
return getJdbcOperations().queryForStream(getPreparedStatementCreator(sql, paramSource), rowMapper); return getJdbcOperations().queryForStream(getPreparedStatementCreator(sql, paramSource), rowMapper);
} }
@Override @Override
public <T> Stream<T> queryForStream(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) public <T extends @Nullable Object> Stream<T> queryForStream(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
return queryForStream(sql, new MapSqlParameterSource(paramMap), rowMapper); return queryForStream(sql, new MapSqlParameterSource(paramMap), rowMapper);
} }
@Override @Override
public <T> @Nullable T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) public <T extends @Nullable Object> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
throws DataAccessException { throws DataAccessException {
List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper); List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
@ -259,7 +259,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
} }
@Override @Override
public <T> @Nullable T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T>rowMapper) public <T extends @Nullable Object> T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T>rowMapper)
throws DataAccessException { throws DataAccessException {
return queryForObject(sql, new MapSqlParameterSource(paramMap), rowMapper); return queryForObject(sql, new MapSqlParameterSource(paramMap), rowMapper);
@ -280,42 +280,44 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
} }
@Override @Override
public Map<String, Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException { public Map<String, @Nullable Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException {
Map<String, Object> result = queryForObject(sql, paramSource, new ColumnMapRowMapper()); Map<String, @Nullable Object> result = queryForObject(sql, paramSource, new ColumnMapRowMapper());
Assert.state(result != null, "No result map"); Assert.state(result != null, "No result map");
return result; return result;
} }
@Override @Override
public Map<String, Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException { public Map<String, @Nullable Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException {
Map<String, Object> result = queryForObject(sql, paramMap, new ColumnMapRowMapper()); Map<String, @Nullable Object> result = queryForObject(sql, paramMap, new ColumnMapRowMapper());
Assert.state(result != null, "No result map"); Assert.state(result != null, "No result map");
return result; return result;
} }
@Override @Override
public <T> List<T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType) @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
throws DataAccessException { throws DataAccessException {
return query(sql, paramSource, new SingleColumnRowMapper<>(elementType)); return query(sql, paramSource, new SingleColumnRowMapper<>(elementType));
} }
@Override @Override
public <T> List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType) @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public <T> List<@Nullable T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType)
throws DataAccessException { throws DataAccessException {
return queryForList(sql, new MapSqlParameterSource(paramMap), elementType); return queryForList(sql, new MapSqlParameterSource(paramMap), elementType);
} }
@Override @Override
public List<Map<String, Object>> queryForList(String sql, SqlParameterSource paramSource) public List<Map<String, @Nullable Object>> queryForList(String sql, SqlParameterSource paramSource)
throws DataAccessException { throws DataAccessException {
return query(sql, paramSource, new ColumnMapRowMapper()); return query(sql, paramSource, new ColumnMapRowMapper());
} }
@Override @Override
public List<Map<String, Object>> queryForList(String sql, Map<String, ?> paramMap) public List<Map<String, @Nullable Object>> queryForList(String sql, Map<String, ?> paramMap)
throws DataAccessException { throws DataAccessException {
return queryForList(sql, new MapSqlParameterSource(paramMap)); return queryForList(sql, new MapSqlParameterSource(paramMap));

View File

@ -379,7 +379,7 @@ public abstract class AbstractJdbcCall {
* @param parameterSource parameter names and values to be used in call * @param parameterSource parameter names and values to be used in call
* @return a Map of out parameters * @return a Map of out parameters
*/ */
protected Map<String, Object> doExecute(SqlParameterSource parameterSource) { protected Map<String, @Nullable Object> doExecute(SqlParameterSource parameterSource) {
checkCompiled(); checkCompiled();
Map<String, Object> params = matchInParameterValuesWithCallParameters(parameterSource); Map<String, Object> params = matchInParameterValuesWithCallParameters(parameterSource);
return executeCallInternal(params); return executeCallInternal(params);
@ -391,7 +391,7 @@ public abstract class AbstractJdbcCall {
* declared for the stored procedure. * declared for the stored procedure.
* @return a Map of out parameters * @return a Map of out parameters
*/ */
protected Map<String, Object> doExecute(Object... args) { protected Map<String, @Nullable Object> doExecute(Object... args) {
checkCompiled(); checkCompiled();
Map<String, ?> params = matchInParameterValuesWithCallParameters(args); Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
return executeCallInternal(params); return executeCallInternal(params);
@ -402,7 +402,7 @@ public abstract class AbstractJdbcCall {
* @param args a Map of parameter name and values * @param args a Map of parameter name and values
* @return a Map of out parameters * @return a Map of out parameters
*/ */
protected Map<String, Object> doExecute(Map<String, ?> args) { protected Map<String, @Nullable Object> doExecute(Map<String, ?> args) {
checkCompiled(); checkCompiled();
Map<String, ?> params = matchInParameterValuesWithCallParameters(args); Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
return executeCallInternal(params); return executeCallInternal(params);
@ -411,7 +411,7 @@ public abstract class AbstractJdbcCall {
/** /**
* Delegate method to perform the actual call processing. * Delegate method to perform the actual call processing.
*/ */
private Map<String, Object> executeCallInternal(Map<String, ?> args) { private Map<String, @Nullable Object> executeCallInternal(Map<String, ?> args) {
CallableStatementCreator csc = getCallableStatementFactory().newCallableStatementCreator(args); CallableStatementCreator csc = getCallableStatementFactory().newCallableStatementCreator(args);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("The following parameters are used for call " + getCallString() + " with " + args); logger.debug("The following parameters are used for call " + getCallString() + " with " + args);

View File

@ -448,6 +448,7 @@ public abstract class AbstractJdbcInsert {
/** /**
* Delegate method to execute the insert, generating any number of keys. * 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) { private KeyHolder executeInsertAndReturnKeyHolderInternal(List<?> values) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values); logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values);
@ -496,7 +497,7 @@ public abstract class AbstractJdbcInsert {
keyHolder.getKeyList().add(keys); keyHolder.getKeyList().add(keys);
} }
else { else {
getJdbcTemplate().execute((ConnectionCallback<Object>) con -> { getJdbcTemplate().execute((ConnectionCallback<@Nullable Object>) con -> {
// Do the insert // Do the insert
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {

View File

@ -239,18 +239,18 @@ final class DefaultJdbcClient implements JdbcClient {
new IndexedParamResultQuerySpec()); new IndexedParamResultQuerySpec());
} }
@SuppressWarnings("unchecked")
@Override @Override
public <T> MappedQuerySpec<T> query(Class<T> mappedClass) { @SuppressWarnings({"unchecked", "NullAway"}) // See https://github.com/uber/NullAway/issues/1075
public <T> MappedQuerySpec<@Nullable T> query(Class<T> mappedClass) {
RowMapper<?> rowMapper = rowMapperCache.computeIfAbsent(mappedClass, key -> RowMapper<?> rowMapper = rowMapperCache.computeIfAbsent(mappedClass, key ->
BeanUtils.isSimpleProperty(mappedClass) ? BeanUtils.isSimpleProperty(mappedClass) ?
new SingleColumnRowMapper<>(mappedClass, conversionService) : new SingleColumnRowMapper<>(mappedClass, conversionService) :
new SimplePropertyRowMapper<>(mappedClass, conversionService)); new SimplePropertyRowMapper<>(mappedClass, conversionService));
return query((RowMapper<T>) rowMapper); return query((RowMapper<@Nullable T>) rowMapper);
} }
@Override @Override
public <T> MappedQuerySpec<T> query(RowMapper<T> rowMapper) { public <T extends @Nullable Object> MappedQuerySpec<T> query(RowMapper<T> rowMapper) {
return (useNamedParams() ? return (useNamedParams() ?
new NamedParamMappedQuerySpec<>(rowMapper) : new NamedParamMappedQuerySpec<>(rowMapper) :
new IndexedParamMappedQuerySpec<>(rowMapper)); new IndexedParamMappedQuerySpec<>(rowMapper));
@ -267,7 +267,7 @@ final class DefaultJdbcClient implements JdbcClient {
} }
@Override @Override
public <T> T query(ResultSetExtractor<T> rse) { public <T extends @Nullable Object> T query(ResultSetExtractor<T> rse) {
T result = (useNamedParams() ? T result = (useNamedParams() ?
this.namedParamOps.query(this.sql, this.namedParamSource, rse) : this.namedParamOps.query(this.sql, this.namedParamSource, rse) :
this.classicOps.query(statementCreatorForIndexedParams(), rse)); this.classicOps.query(statementCreatorForIndexedParams(), rse));
@ -332,17 +332,18 @@ final class DefaultJdbcClient implements JdbcClient {
} }
@Override @Override
public List<Map<String, Object>> listOfRows() { public List<Map<String, @Nullable Object>> listOfRows() {
return classicOps.queryForList(sql, indexedParams.toArray()); return classicOps.queryForList(sql, indexedParams.toArray());
} }
@Override @Override
public Map<String, Object> singleRow() { public Map<String, @Nullable Object> singleRow() {
return classicOps.queryForMap(sql, indexedParams.toArray()); return classicOps.queryForMap(sql, indexedParams.toArray());
} }
@Override @Override
public List<Object> singleColumn() { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public List<@Nullable Object> singleColumn() {
return classicOps.queryForList(sql, Object.class, indexedParams.toArray()); return classicOps.queryForList(sql, Object.class, indexedParams.toArray());
} }
} }
@ -356,23 +357,25 @@ final class DefaultJdbcClient implements JdbcClient {
} }
@Override @Override
public List<Map<String, Object>> listOfRows() { public List<Map<String, @Nullable Object>> listOfRows() {
return namedParamOps.queryForList(sql, namedParamSource); return namedParamOps.queryForList(sql, namedParamSource);
} }
@Override @Override
public Map<String, Object> singleRow() { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public Map<String, @Nullable Object> singleRow() {
return namedParamOps.queryForMap(sql, namedParamSource); return namedParamOps.queryForMap(sql, namedParamSource);
} }
@Override @Override
public List<Object> singleColumn() { @SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
public List<@Nullable Object> singleColumn() {
return namedParamOps.queryForList(sql, namedParamSource, Object.class); return namedParamOps.queryForList(sql, namedParamSource, Object.class);
} }
} }
private class IndexedParamMappedQuerySpec<T> implements MappedQuerySpec<T> { private class IndexedParamMappedQuerySpec<T extends @Nullable Object> implements MappedQuerySpec<T> {
private final RowMapper<T> rowMapper; private final RowMapper<T> rowMapper;
@ -392,7 +395,7 @@ final class DefaultJdbcClient implements JdbcClient {
} }
private class NamedParamMappedQuerySpec<T> implements MappedQuerySpec<T> { private class NamedParamMappedQuerySpec<T extends @Nullable Object> implements MappedQuerySpec<T> {
private final RowMapper<T> rowMapper; private final RowMapper<T> rowMapper;

View File

@ -26,6 +26,7 @@ import java.util.stream.Stream;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@ -286,7 +287,7 @@ public interface JdbcClient {
* @see org.springframework.jdbc.core.SingleColumnRowMapper * @see org.springframework.jdbc.core.SingleColumnRowMapper
* @see org.springframework.jdbc.core.SimplePropertyRowMapper * @see org.springframework.jdbc.core.SimplePropertyRowMapper
*/ */
<T> MappedQuerySpec<T> query(Class<T> mappedClass); <T> MappedQuerySpec<@Nullable T> query(Class<T> mappedClass);
/** /**
* Proceed towards execution of a mapped query, with several options * Proceed towards execution of a mapped query, with several options
@ -295,7 +296,7 @@ public interface JdbcClient {
* @return the mapped query specification * @return the mapped query specification
* @see java.sql.PreparedStatement#executeQuery() * @see java.sql.PreparedStatement#executeQuery()
*/ */
<T> MappedQuerySpec<T> query(RowMapper<T> rowMapper); <T extends @Nullable Object> MappedQuerySpec<T> query(RowMapper<T> rowMapper);
/** /**
* Execute a query with the provided SQL statement, * Execute a query with the provided SQL statement,
@ -312,7 +313,7 @@ public interface JdbcClient {
* @return the value returned by the ResultSetExtractor * @return the value returned by the ResultSetExtractor
* @see java.sql.PreparedStatement#executeQuery() * @see java.sql.PreparedStatement#executeQuery()
*/ */
<T> T query(ResultSetExtractor<T> rse); <T extends @Nullable Object> T query(ResultSetExtractor<T> rse);
/** /**
* Execute the provided SQL statement as an update. * Execute the provided SQL statement as an update.
@ -365,14 +366,14 @@ public interface JdbcClient {
* with each result row represented as a map of * with each result row represented as a map of
* case-insensitive column names to column values * case-insensitive column names to column values
*/ */
List<Map<String, Object>> listOfRows(); List<Map<String, @Nullable Object>> listOfRows();
/** /**
* Retrieve a single row result. * Retrieve a single row result.
* @return the result row represented as a map of * @return the result row represented as a map of
* case-insensitive column names to column values * case-insensitive column names to column values
*/ */
Map<String, Object> singleRow(); Map<String, @Nullable Object> singleRow();
/** /**
* Retrieve a single column result, * Retrieve a single column result,
@ -380,7 +381,7 @@ public interface JdbcClient {
* @return a (potentially empty) list of rows, with each * @return a (potentially empty) list of rows, with each
* row represented as its single column value * row represented as its single column value
*/ */
List<Object> singleColumn(); List<@Nullable Object> singleColumn();
/** /**
* Retrieve a single value result. * Retrieve a single value result.
@ -390,6 +391,7 @@ public interface JdbcClient {
* @see #optionalValue() * @see #optionalValue()
* @see DataAccessUtils#requiredSingleResult(Collection) * @see DataAccessUtils#requiredSingleResult(Collection)
*/ */
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
default Object singleValue() { default Object singleValue() {
return DataAccessUtils.requiredSingleResult(singleColumn()); return DataAccessUtils.requiredSingleResult(singleColumn());
} }
@ -401,6 +403,7 @@ public interface JdbcClient {
* @see #singleValue() * @see #singleValue()
* @see DataAccessUtils#optionalResult(Collection) * @see DataAccessUtils#optionalResult(Collection)
*/ */
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
default Optional<Object> optionalValue() { default Optional<Object> optionalValue() {
return DataAccessUtils.optionalResult(singleColumn()); return DataAccessUtils.optionalResult(singleColumn());
} }
@ -412,7 +415,7 @@ public interface JdbcClient {
* *
* @param <T> the RowMapper-declared result type * @param <T> the RowMapper-declared result type
*/ */
interface MappedQuerySpec<T> { interface MappedQuerySpec<T extends @Nullable Object> {
/** /**
* Retrieve the result as a lazily resolved stream of mapped objects, * Retrieve the result as a lazily resolved stream of mapped objects,
@ -447,7 +450,7 @@ public interface JdbcClient {
* @see #optional() * @see #optional()
* @see DataAccessUtils#requiredSingleResult(Collection) * @see DataAccessUtils#requiredSingleResult(Collection)
*/ */
default T single() { default @NonNull T single() {
return DataAccessUtils.requiredSingleResult(list()); return DataAccessUtils.requiredSingleResult(list());
} }
@ -457,7 +460,7 @@ public interface JdbcClient {
* @see #single() * @see #single()
* @see DataAccessUtils#optionalResult(Collection) * @see DataAccessUtils#optionalResult(Collection)
*/ */
default Optional<T> optional() { default Optional<@NonNull T> optional() {
return DataAccessUtils.optionalResult(list()); return DataAccessUtils.optionalResult(list());
} }
} }

View File

@ -186,17 +186,17 @@ public class SimpleJdbcCall extends AbstractJdbcCall implements SimpleJdbcCallOp
} }
@Override @Override
public Map<String, Object> execute(Object... args) { public Map<String, @Nullable Object> execute(Object... args) {
return doExecute(args); return doExecute(args);
} }
@Override @Override
public Map<String, Object> execute(Map<String, ?> args) { public Map<String, @Nullable Object> execute(Map<String, ?> args) {
return doExecute(args); return doExecute(args);
} }
@Override @Override
public Map<String, Object> execute(SqlParameterSource parameterSource) { public Map<String, @Nullable Object> execute(SqlParameterSource parameterSource) {
return doExecute(parameterSource); return doExecute(parameterSource);
} }

View File

@ -174,7 +174,7 @@ public interface SimpleJdbcCallOperations {
* the stored procedure. * the stored procedure.
* @return a Map of output params * @return a Map of output params
*/ */
Map<String, Object> execute(Object... args); Map<String, @Nullable Object> execute(Object... args);
/** /**
* Execute the stored procedure and return a map of output params, keyed by name * 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 * @param args a Map containing the parameter values to be used in the call
* @return a Map of output params * @return a Map of output params
*/ */
Map<String, Object> execute(Map<String, ?> args); Map<String, @Nullable Object> execute(Map<String, ?> args);
/** /**
* Execute the stored procedure and return a map of output params, keyed by name * 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 * @param args the SqlParameterSource containing the parameter values to be used in the call
* @return a Map of output params * @return a Map of output params
*/ */
Map<String, Object> execute(SqlParameterSource args); Map<String, @Nullable Object> execute(SqlParameterSource args);
} }

View File

@ -59,7 +59,7 @@ import org.springframework.jdbc.core.ResultSetExtractor;
* in favor of {@link ResultSet#getBinaryStream}/{@link ResultSet#getCharacterStream} usage * in favor of {@link ResultSet#getBinaryStream}/{@link ResultSet#getCharacterStream} usage
*/ */
@Deprecated(since = "6.2") @Deprecated(since = "6.2")
public abstract class AbstractLobStreamingResultSetExtractor<T> implements ResultSetExtractor<T> { public abstract class AbstractLobStreamingResultSetExtractor<T> implements ResultSetExtractor<@Nullable T> {
/** /**
* Delegates to handleNoRowFound, handleMultipleRowsFound and streamData, * Delegates to handleNoRowFound, handleMultipleRowsFound and streamData,

View File

@ -35,7 +35,7 @@ import org.springframework.util.Assert;
* @see #setRowMapper * @see #setRowMapper
* @see #setRowMapperClass * @see #setRowMapperClass
*/ */
public class GenericSqlQuery<T> extends SqlQuery<T> { public class GenericSqlQuery<T extends @Nullable Object> extends SqlQuery<T> {
private @Nullable RowMapper<T> rowMapper; private @Nullable RowMapper<T> rowMapper;

View File

@ -39,7 +39,7 @@ import org.jspecify.annotations.Nullable;
* @param <T> the result type * @param <T> the result type
* @see MappingSqlQueryWithParameters * @see MappingSqlQueryWithParameters
*/ */
public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T> { public abstract class MappingSqlQuery<T extends @Nullable Object> extends MappingSqlQueryWithParameters<T> {
/** /**
* Constructor that allows use as a JavaBean. * Constructor that allows use as a JavaBean.
@ -63,7 +63,7 @@ public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T
* @see #mapRow(ResultSet, int) * @see #mapRow(ResultSet, int)
*/ */
@Override @Override
protected final @Nullable T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map<?, ?> context) protected final T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map<?, ?> context)
throws SQLException { throws SQLException {
return mapRow(rs, rowNum); return mapRow(rs, rowNum);
@ -82,6 +82,6 @@ public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T
* Subclasses can simply not catch SQLExceptions, relying on the * Subclasses can simply not catch SQLExceptions, relying on the
* framework to clean up. * framework to clean up.
*/ */
protected abstract @Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException; protected abstract T mapRow(ResultSet rs, int rowNum) throws SQLException;
} }

View File

@ -51,7 +51,7 @@ import org.springframework.jdbc.core.RowMapper;
* @see org.springframework.jdbc.object.MappingSqlQuery * @see org.springframework.jdbc.object.MappingSqlQuery
* @see org.springframework.jdbc.object.SqlQuery * @see org.springframework.jdbc.object.SqlQuery
*/ */
public abstract class MappingSqlQueryWithParameters<T> extends SqlQuery<T> { public abstract class MappingSqlQueryWithParameters<T extends @Nullable Object> extends SqlQuery<T> {
/** /**
* Constructor to allow use as a JavaBean. * Constructor to allow use as a JavaBean.
@ -93,7 +93,7 @@ public abstract class MappingSqlQueryWithParameters<T> extends SqlQuery<T> {
* Subclasses can simply not catch SQLExceptions, relying on the * Subclasses can simply not catch SQLExceptions, relying on the
* framework to clean up. * 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; throws SQLException;
@ -116,7 +116,7 @@ public abstract class MappingSqlQueryWithParameters<T> extends SqlQuery<T> {
} }
@Override @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); return MappingSqlQueryWithParameters.this.mapRow(rs, rowNum, this.params, this.context);
} }
} }

View File

@ -51,7 +51,7 @@ import org.springframework.jdbc.core.SingleColumnRowMapper;
* @param <T> the result type * @param <T> the result type
* @see StoredProcedure * @see StoredProcedure
*/ */
public class SqlFunction<T> extends MappingSqlQuery<T> { public class SqlFunction<T> extends MappingSqlQuery<@Nullable T> {
private final SingleColumnRowMapper<T> rowMapper = new SingleColumnRowMapper<>(); private final SingleColumnRowMapper<T> rowMapper = new SingleColumnRowMapper<>();

View File

@ -59,7 +59,7 @@ import org.springframework.jdbc.core.namedparam.ParsedSql;
* @param <T> the result type * @param <T> the result type
* @see SqlUpdate * @see SqlUpdate
*/ */
public abstract class SqlQuery<T> extends SqlOperation { public abstract class SqlQuery<T extends @Nullable Object> extends SqlOperation {
/** /**
* Constructor to allow use as a JavaBean. * Constructor to allow use as a JavaBean.

View File

@ -21,6 +21,8 @@ import java.util.Map;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.JdbcTemplate; 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 * Output parameters will appear here, with their values after the stored procedure
* has been called. * has been called.
*/ */
public Map<String, Object> execute(Object... inParams) { public Map<String, @Nullable Object> execute(Object... inParams) {
Map<String, Object> paramsToUse = new HashMap<>(); Map<String, @Nullable Object> paramsToUse = new HashMap<>();
validateParameters(inParams); validateParameters(inParams);
int i = 0; int i = 0;
for (SqlParameter sqlParameter : getDeclaredParameters()) { for (SqlParameter sqlParameter : getDeclaredParameters()) {
@ -135,7 +137,7 @@ public abstract class StoredProcedure extends SqlCall {
* Output parameters will appear here, with their values after the * Output parameters will appear here, with their values after the
* stored procedure has been called. * stored procedure has been called.
*/ */
public Map<String, Object> execute(Map<String, ?> inParams) throws DataAccessException { public Map<String, @Nullable Object> execute(Map<String, ?> inParams) throws DataAccessException {
validateParameters(inParams.values().toArray()); validateParameters(inParams.values().toArray());
return getJdbcTemplate().call(newCallableStatementCreator(inParams), getDeclaredParameters()); 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 * Output parameters will appear here, with their values after the
* stored procedure has been called. * stored procedure has been called.
*/ */
public Map<String, Object> execute(ParameterMapper inParamMapper) throws DataAccessException { public Map<String, @Nullable Object> execute(ParameterMapper inParamMapper) throws DataAccessException {
checkCompiled(); checkCompiled();
return getJdbcTemplate().call(newCallableStatementCreator(inParamMapper), getDeclaredParameters()); return getJdbcTemplate().call(newCallableStatementCreator(inParamMapper), getDeclaredParameters());
} }

View File

@ -19,6 +19,8 @@ package org.springframework.jdbc.support;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import org.jspecify.annotations.Nullable;
/** /**
* A callback interface used by the JdbcUtils class. Implementations of this * A callback interface used by the JdbcUtils class. Implementations of this
* interface perform the actual work of extracting database meta-data, but * 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) * @see JdbcUtils#extractDatabaseMetaData(javax.sql.DataSource, DatabaseMetaDataCallback)
*/ */
@FunctionalInterface @FunctionalInterface
public interface DatabaseMetaDataCallback<T> { public interface DatabaseMetaDataCallback<T extends @Nullable Object> {
/** /**
* Implementations must implement this method to process the meta-data * Implementations must implement this method to process the meta-data

View File

@ -321,7 +321,7 @@ public abstract class JdbcUtils {
* @throws MetaDataAccessException if meta-data access failed * @throws MetaDataAccessException if meta-data access failed
* @see java.sql.DatabaseMetaData * @see java.sql.DatabaseMetaData
*/ */
public static <T> T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback<T> action) public static <T extends @Nullable Object> T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback<T> action)
throws MetaDataAccessException { throws MetaDataAccessException {
Connection con = null; Connection con = null;

View File

@ -35,7 +35,7 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String): T =
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: Any, crossinline function: (ResultSet, Int) -> T): T = inline fun <reified T> 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 * Extension for [JdbcOperations.queryForObject] providing a
@ -44,7 +44,7 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, vararg args: A
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T? = inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>, argTypes: IntArray): T =
queryForObject(sql, args, argTypes, T::class.java as Class<*>) as T queryForObject(sql, args, argTypes, T::class.java as Class<*>) as T
/** /**
@ -54,7 +54,7 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T? = inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<out Any>): T =
queryForObject(sql, T::class.java as Class<*>, args) as T queryForObject(sql, T::class.java as Class<*>, args) as T
/** /**
@ -63,8 +63,9 @@ inline fun <reified T> JdbcOperations.queryForObject(sql: String, args: Array<ou
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T> = @Suppress("UNCHECKED_CAST")
queryForList(sql, T::class.java) inline fun <reified T> JdbcOperations.queryForList(sql: String): List<T> =
queryForList(sql, T::class.java) as List<T>
/** /**
* Extension for [JdbcOperations.queryForList] providing a * Extension for [JdbcOperations.queryForList] providing a
@ -73,9 +74,10 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String): List<T> =
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any>, @Suppress("UNCHECKED_CAST")
inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>,
argTypes: IntArray): List<T> = argTypes: IntArray): List<T> =
queryForList(sql, args, argTypes, T::class.java) queryForList(sql, args, argTypes, T::class.java) as List<T>
/** /**
* Extension for [JdbcOperations.queryForList] providing a * Extension for [JdbcOperations.queryForList] providing a
@ -84,8 +86,9 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Arra
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> = @Suppress("UNCHECKED_CAST")
queryForList(sql, T::class.java, args) inline fun <reified T> JdbcOperations.queryForList(sql: String, args: Array<out Any>): List<T> =
queryForList(sql, T::class.java, args) as List<T>
/** /**
@ -95,9 +98,9 @@ inline fun <reified T : Any> JdbcOperations.queryForList(sql: String, args: Arra
* @author Mario Arias * @author Mario Arias
* @since 5.0 * @since 5.0
*/ */
inline fun <reified T> JdbcOperations.query(sql: String, vararg args: Any, fun <T> JdbcOperations.query(sql: String, vararg args: Any,
crossinline function: (ResultSet) -> T): T = function: (ResultSet) -> T): T =
query(sql, ResultSetExtractor { function(it) }, *args) as T query(sql, ResultSetExtractor { function(it) }, *args)
/** /**
* Extension for [JdbcOperations.query] providing a RowCallbackHandler-like function * Extension for [JdbcOperations.query] providing a RowCallbackHandler-like function

View File

@ -43,6 +43,13 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForObject(sql, any<Class<Int>>()) } verify { template.queryForObject(sql, any<Class<Int>>()) }
} }
@Test
fun `queryForObject with nullable reified type parameters`() {
every { template.queryForObject(sql, any<Class<Int>>()) } returns null
assertThat(template.queryForObject<Int?>(sql)).isNull()
verify { template.queryForObject(sql, any<Class<Int>>()) }
}
@Test @Test
fun `queryForObject with RowMapper-like function`() { fun `queryForObject with RowMapper-like function`() {
every { template.queryForObject(sql, any<RowMapper<Int>>(), any<Int>()) } returns 2 every { template.queryForObject(sql, any<RowMapper<Int>>(), any<Int>()) } returns 2
@ -52,9 +59,9 @@ class JdbcOperationsExtensionsTests {
@Test // gh-22682 @Test // gh-22682
fun `queryForObject with nullable RowMapper-like function`() { fun `queryForObject with nullable RowMapper-like function`() {
every { template.queryForObject(sql, any<RowMapper<Int>>(), 3) } returns null every { template.queryForObject(sql, any<RowMapper<Int?>>(), 3) } returns null
assertThat(template.queryForObject<Int?>(sql, 3) { _, _ -> null }).isNull() assertThat(template.queryForObject<Int?>(sql, 3) { _, _ -> null }).isNull()
verify { template.queryForObject(eq(sql), any<RowMapper<Int>>(), eq(3)) } verify { template.queryForObject(eq(sql), any<RowMapper<Int?>>(), eq(3)) }
} }
@Test @Test
@ -66,6 +73,15 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) } verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
} }
@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<Class<Int>>()) } returns null
assertThat(template.queryForObject<Int?>(sql, args, argTypes)).isNull()
verify { template.queryForObject(sql, args, argTypes, any<Class<Int>>()) }
}
@Test @Test
fun `queryForObject with reified type parameters and args`() { fun `queryForObject with reified type parameters and args`() {
val args = arrayOf(3, 4) val args = arrayOf(3, 4)
@ -74,6 +90,14 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForObject(sql, any<Class<Int>>(), args) } verify { template.queryForObject(sql, any<Class<Int>>(), args) }
} }
@Test
fun `queryForObject with nullable reified type parameters and args`() {
val args = arrayOf(3, 4)
every { template.queryForObject(sql, any<Class<Int>>(), args) } returns null
assertThat(template.queryForObject<Int?>(sql, args)).isNull()
verify { template.queryForObject(sql, any<Class<Int>>(), args) }
}
@Test @Test
fun `queryForList with reified type parameters`() { fun `queryForList with reified type parameters`() {
val list = listOf(1, 2, 3) val list = listOf(1, 2, 3)
@ -82,6 +106,14 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForList(sql, any<Class<Int>>()) } verify { template.queryForList(sql, any<Class<Int>>()) }
} }
@Test
fun `queryForList with nullable reified type parameters`() {
val list = listOf(1, null, 3)
every { template.queryForList(sql, any<Class<Int>>()) } returns list
assertThat(template.queryForList<Int?>(sql)).isEqualTo(list)
verify { template.queryForList(sql, any<Class<Int>>()) }
}
@Test @Test
fun `queryForList with reified type parameters and argTypes`() { fun `queryForList with reified type parameters and argTypes`() {
val list = listOf(1, 2, 3) val list = listOf(1, 2, 3)
@ -92,6 +124,16 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) } verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
} }
@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<Class<Int>>()) } returns list
assertThat(template.queryForList<Int?>(sql, args, argTypes)).isEqualTo(list)
verify { template.queryForList(sql, args, argTypes, any<Class<Int>>()) }
}
@Test @Test
fun `queryForList with reified type parameters and args`() { fun `queryForList with reified type parameters and args`() {
val list = listOf(1, 2, 3) val list = listOf(1, 2, 3)
@ -101,6 +143,15 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForList(sql, any<Class<Int>>(), args) } verify { template.queryForList(sql, any<Class<Int>>(), 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<Class<Int>>(), args) } returns list
template.queryForList<Int?>(sql, args)
verify { template.queryForList(sql, any<Class<Int>>(), args) }
}
@Test @Test
fun `query with ResultSetExtractor-like function`() { fun `query with ResultSetExtractor-like function`() {
every { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } returns 2 every { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } returns 2
@ -113,12 +164,11 @@ class JdbcOperationsExtensionsTests {
@Test // gh-22682 @Test // gh-22682
fun `query with nullable ResultSetExtractor-like function`() { fun `query with nullable ResultSetExtractor-like function`() {
every { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } returns null every { template.query(eq(sql), any<ResultSetExtractor<Int?>>(), eq(3)) } returns null
assertThat(template.query<Int?>(sql, 3) { _ -> null }).isNull() assertThat(template.query<Int?>(sql, 3) { _ -> null }).isNull()
verify { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } verify { template.query(eq(sql), any<ResultSetExtractor<Int?>>(), eq(3)) }
} }
@Suppress("RemoveExplicitTypeArguments")
@Test @Test
fun `query with RowCallbackHandler-like function`() { fun `query with RowCallbackHandler-like function`() {
every { template.query(sql, ofType<RowCallbackHandler>(), 3) } returns Unit every { template.query(sql, ofType<RowCallbackHandler>(), 3) } returns Unit
@ -131,11 +181,21 @@ class JdbcOperationsExtensionsTests {
@Test @Test
fun `query with RowMapper-like function`() { fun `query with RowMapper-like function`() {
val list = mutableListOf(1, 2, 3) val list = mutableListOf(1, 2, 3)
every { template.query(sql, ofType<RowMapper<*>>(), 3) } returns list every { template.query(sql, ofType<RowMapper<Int>>(), 3) } returns list
assertThat(template.query(sql, 3) { rs, _ -> assertThat(template.query(sql, 3) { rs, _ ->
rs.getInt(1) rs.getInt(1)
}).isEqualTo(list) }).isEqualTo(list)
verify { template.query(sql, ofType<RowMapper<*>>(), 3) } verify { template.query(sql, ofType<RowMapper<Int>>(), 3) }
}
@Test
fun `query with nullable RowMapper-like function`() {
val list = mutableListOf(1, null, 3)
every { template.query(sql, ofType<RowMapper<Int?>>(), 3) } returns list
assertThat(template.query(sql, 3) { rs, _ ->
rs.getInt(1)
}).isEqualTo(list)
verify { template.query(sql, ofType<RowMapper<Int?>>(), 3) }
} }
} }

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
@ -115,7 +116,7 @@ public abstract class DataAccessUtils {
* element has been found in the given Collection * element has been found in the given Collection
* @since 6.1 * @since 6.1
*/ */
public static <T> Optional<T> optionalResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException { public static <T extends @Nullable Object> Optional<@NonNull T> optionalResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
return Optional.ofNullable(singleResult(results)); return Optional.ofNullable(singleResult(results));
} }
@ -158,7 +159,7 @@ public abstract class DataAccessUtils {
* @throws EmptyResultDataAccessException if no element at all * @throws EmptyResultDataAccessException if no element at all
* has been found in the given Collection * has been found in the given Collection
*/ */
public static <T> T requiredSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException { public static <T extends @Nullable Object> @NonNull T requiredSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
if (CollectionUtils.isEmpty(results)) { if (CollectionUtils.isEmpty(results)) {
throw new EmptyResultDataAccessException(1); throw new EmptyResultDataAccessException(1);
} }
@ -184,7 +185,7 @@ public abstract class DataAccessUtils {
* has been found in the given Collection * has been found in the given Collection
* @since 5.0.2 * @since 5.0.2
*/ */
public static <T> @Nullable T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException { public static <T extends @Nullable Object> T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
// This is identical to the requiredSingleResult implementation but differs in the // This is identical to the requiredSingleResult implementation but differs in the
// semantics of the incoming Collection (which we currently can't formally express) // semantics of the incoming Collection (which we currently can't formally express)
if (CollectionUtils.isEmpty(results)) { if (CollectionUtils.isEmpty(results)) {