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
* @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);
}

View File

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

View File

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

View File

@ -41,7 +41,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#update
*/
@FunctionalInterface
public interface ConnectionCallback<T> {
public interface ConnectionCallback<T extends @Nullable Object> {
/**
* 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#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
* @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
* @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.
@ -108,7 +108,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem executing the query
* @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
@ -135,7 +135,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem executing the query
* @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
@ -151,7 +151,7 @@ public interface JdbcOperations {
* @since 5.3
* @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
@ -169,7 +169,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem executing the query
* @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.
@ -208,7 +208,7 @@ public interface JdbcOperations {
* @see #queryForMap(String, Object...)
* @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.
@ -225,7 +225,7 @@ public interface JdbcOperations {
* @see #queryForList(String, Class, Object...)
* @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.
@ -241,7 +241,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem executing the query
* @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.
@ -299,7 +299,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem
*/
<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
@ -314,7 +314,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem
*/
<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.
@ -326,7 +326,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem
* @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.
@ -339,7 +339,7 @@ public interface JdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor
* @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;
/**
@ -354,7 +354,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -370,7 +370,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #query(String, ResultSetExtractor, Object...)}
*/
@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
@ -385,7 +385,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -469,7 +469,7 @@ public interface JdbcOperations {
* @throws DataAccessException if there is any problem
* @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
@ -484,7 +484,7 @@ public interface JdbcOperations {
* @return the result List, containing mapped objects
* @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;
/**
@ -500,7 +500,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -517,7 +517,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #query(String, RowMapper, Object...)}
*/
@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
@ -533,7 +533,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -548,7 +548,7 @@ public interface JdbcOperations {
* @see PreparedStatementCreatorFactory
* @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
@ -566,7 +566,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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;
/**
@ -584,7 +584,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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;
/**
@ -603,7 +603,7 @@ public interface JdbcOperations {
* if the query does not return exactly one row
* @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;
/**
@ -624,7 +624,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #queryForObject(String, RowMapper, Object...)}
*/
@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
@ -643,7 +643,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -729,7 +729,7 @@ public interface JdbcOperations {
* @see ColumnMapRowMapper
* @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
@ -752,7 +752,7 @@ public interface JdbcOperations {
* @see #queryForMap(String)
* @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
@ -770,7 +770,7 @@ public interface JdbcOperations {
* @see #queryForList(String, Class)
* @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;
/**
@ -792,7 +792,7 @@ public interface JdbcOperations {
* @deprecated in favor of {@link #queryForList(String, Class, Object...)}
*/
@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
@ -812,7 +812,7 @@ public interface JdbcOperations {
* @see #queryForList(String, Class)
* @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
@ -830,7 +830,7 @@ public interface JdbcOperations {
* @see #queryForList(String)
* @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
@ -848,7 +848,7 @@ public interface JdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -1064,7 +1064,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem
*/
<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
@ -1079,7 +1079,7 @@ public interface JdbcOperations {
* @return a result object returned by the action, or {@code null} if none
* @throws DataAccessException if there is any problem
*/
<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
@ -1089,7 +1089,7 @@ public interface JdbcOperations {
* @return a Map of extracted out parameters
* @throws DataAccessException if there is any problem issuing the update
*/
Map<String, Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
Map<String, @Nullable Object> call(CallableStatementCreator csc, List<SqlParameter> declaredParameters)
throws DataAccessException;
}

View File

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

View File

@ -19,6 +19,8 @@ package org.springframework.jdbc.core;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.jspecify.annotations.Nullable;
/**
* Parameterized callback interface used by the {@link JdbcTemplate} class for
* batch updates.
@ -47,6 +49,6 @@ public interface ParameterizedPreparedStatementSetter<T> {
* @param argument the object containing the values to be set
* @throws SQLException if an SQLException is encountered (i.e. there is no need to catch SQLException)
*/
void setValues(PreparedStatement ps, T argument) throws SQLException;
void setValues(PreparedStatement ps, @Nullable T argument) throws SQLException;
}

View File

@ -44,7 +44,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#execute(PreparedStatementCreator, PreparedStatementCallback)
*/
@FunctionalInterface
public interface PreparedStatementCallback<T> {
public interface PreparedStatementCallback<T extends @Nullable Object> {
/**
* 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#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
*/
@FunctionalInterface
public interface ResultSetExtractor<T> {
public interface ResultSetExtractor<T extends @Nullable Object> {
/**
* 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)
* @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
*/
@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
@ -61,6 +61,6 @@ public interface RowMapper<T> {
* @throws SQLException if an SQLException is encountered while getting
* column values (that is, there's no need to catch SQLException)
*/
@Nullable T mapRow(ResultSet rs, int rowNum) throws SQLException;
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

View File

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

View File

@ -46,7 +46,7 @@ import org.springframework.util.NumberUtils;
* @see JdbcTemplate#queryForList(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;

View File

@ -37,7 +37,7 @@ import org.springframework.dao.DataAccessException;
* @see JdbcTemplate#execute(StatementCallback)
*/
@FunctionalInterface
public interface StatementCallback<T> {
public interface StatementCallback<T extends @Nullable Object> {
/**
* 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#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}
* @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;
/**
@ -94,7 +94,7 @@ public interface NamedParameterJdbcOperations {
* @return a result object returned by the action, or {@code null}
* @throws DataAccessException if there is any problem
*/
<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;
/**
@ -110,7 +110,7 @@ public interface NamedParameterJdbcOperations {
* @return a result object returned by the action, or {@code null}
* @throws DataAccessException if there is any problem
*/
<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
@ -122,7 +122,7 @@ public interface NamedParameterJdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor
* @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;
/**
@ -136,7 +136,7 @@ public interface NamedParameterJdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor
* @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;
/**
@ -150,7 +150,7 @@ public interface NamedParameterJdbcOperations {
* @return an arbitrary result object, as returned by the ResultSetExtractor
* @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
@ -198,7 +198,7 @@ public interface NamedParameterJdbcOperations {
* @return the result List, containing mapped objects
* @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;
/**
@ -212,7 +212,7 @@ public interface NamedParameterJdbcOperations {
* @return the result List, containing mapped objects
* @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;
/**
@ -226,7 +226,7 @@ public interface NamedParameterJdbcOperations {
* @return the result List, containing mapped objects
* @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
@ -240,7 +240,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails
* @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;
/**
@ -256,7 +256,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails
* @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;
/**
@ -272,7 +272,7 @@ public interface NamedParameterJdbcOperations {
* if the query does not return exactly one row
* @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;
/**
@ -289,7 +289,7 @@ public interface NamedParameterJdbcOperations {
* if the query does not return exactly one row
* @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;
/**
@ -346,7 +346,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String)
* @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
@ -366,7 +366,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForMap(String)
* @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
@ -382,7 +382,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class)
* @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;
/**
@ -400,7 +400,7 @@ public interface NamedParameterJdbcOperations {
* @see org.springframework.jdbc.core.JdbcTemplate#queryForList(String, Class)
* @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;
/**
@ -416,7 +416,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails
* @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
@ -432,7 +432,7 @@ public interface NamedParameterJdbcOperations {
* @throws DataAccessException if the query fails
* @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

View File

@ -161,40 +161,40 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
@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 {
return getJdbcOperations().execute(getPreparedStatementCreator(sql, paramSource), action);
}
@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 {
return execute(sql, new MapSqlParameterSource(paramMap), action);
}
@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);
}
@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 {
return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rse);
}
@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 {
return query(sql, new MapSqlParameterSource(paramMap), rse);
}
@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);
}
@ -218,14 +218,14 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
}
@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 {
return getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
}
@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 {
return query(sql, new MapSqlParameterSource(paramMap), rowMapper);
@ -237,21 +237,21 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
}
@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 {
return getJdbcOperations().queryForStream(getPreparedStatementCreator(sql, paramSource), rowMapper);
}
@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 {
return queryForStream(sql, new MapSqlParameterSource(paramMap), rowMapper);
}
@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 {
List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
@ -259,7 +259,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
}
@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 {
return queryForObject(sql, new MapSqlParameterSource(paramMap), rowMapper);
@ -280,42 +280,44 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
}
@Override
public Map<String, Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException {
Map<String, Object> result = queryForObject(sql, paramSource, new ColumnMapRowMapper());
public Map<String, @Nullable Object> queryForMap(String sql, SqlParameterSource paramSource) throws DataAccessException {
Map<String, @Nullable Object> result = queryForObject(sql, paramSource, new ColumnMapRowMapper());
Assert.state(result != null, "No result map");
return result;
}
@Override
public Map<String, Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException {
Map<String, Object> result = queryForObject(sql, paramMap, new ColumnMapRowMapper());
public Map<String, @Nullable Object> queryForMap(String sql, Map<String, ?> paramMap) throws DataAccessException {
Map<String, @Nullable Object> result = queryForObject(sql, paramMap, new ColumnMapRowMapper());
Assert.state(result != null, "No result map");
return result;
}
@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 {
return query(sql, paramSource, new SingleColumnRowMapper<>(elementType));
}
@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 {
return queryForList(sql, new MapSqlParameterSource(paramMap), elementType);
}
@Override
public List<Map<String, Object>> queryForList(String sql, SqlParameterSource paramSource)
public List<Map<String, @Nullable Object>> queryForList(String sql, SqlParameterSource paramSource)
throws DataAccessException {
return query(sql, paramSource, new ColumnMapRowMapper());
}
@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 {
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
* @return a Map of out parameters
*/
protected Map<String, Object> doExecute(SqlParameterSource parameterSource) {
protected Map<String, @Nullable Object> doExecute(SqlParameterSource parameterSource) {
checkCompiled();
Map<String, Object> params = matchInParameterValuesWithCallParameters(parameterSource);
return executeCallInternal(params);
@ -391,7 +391,7 @@ public abstract class AbstractJdbcCall {
* declared for the stored procedure.
* @return a Map of out parameters
*/
protected Map<String, Object> doExecute(Object... args) {
protected Map<String, @Nullable Object> doExecute(Object... args) {
checkCompiled();
Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
return executeCallInternal(params);
@ -402,7 +402,7 @@ public abstract class AbstractJdbcCall {
* @param args a Map of parameter name and values
* @return a Map of out parameters
*/
protected Map<String, Object> doExecute(Map<String, ?> args) {
protected Map<String, @Nullable Object> doExecute(Map<String, ?> args) {
checkCompiled();
Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
return executeCallInternal(params);
@ -411,7 +411,7 @@ public abstract class AbstractJdbcCall {
/**
* 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);
if (logger.isDebugEnabled()) {
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.
*/
@SuppressWarnings("NullAway") // See https://github.com/uber/NullAway/issues/1075
private KeyHolder executeInsertAndReturnKeyHolderInternal(List<?> values) {
if (logger.isDebugEnabled()) {
logger.debug("The following parameters are used for call " + getInsertString() + " with: " + values);
@ -496,7 +497,7 @@ public abstract class AbstractJdbcInsert {
keyHolder.getKeyList().add(keys);
}
else {
getJdbcTemplate().execute((ConnectionCallback<Object>) con -> {
getJdbcTemplate().execute((ConnectionCallback<@Nullable Object>) con -> {
// Do the insert
PreparedStatement ps = null;
try {

View File

@ -239,18 +239,18 @@ final class DefaultJdbcClient implements JdbcClient {
new IndexedParamResultQuerySpec());
}
@SuppressWarnings("unchecked")
@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 ->
BeanUtils.isSimpleProperty(mappedClass) ?
new SingleColumnRowMapper<>(mappedClass, conversionService) :
new SimplePropertyRowMapper<>(mappedClass, conversionService));
return query((RowMapper<T>) rowMapper);
return query((RowMapper<@Nullable T>) rowMapper);
}
@Override
public <T> MappedQuerySpec<T> query(RowMapper<T> rowMapper) {
public <T extends @Nullable Object> MappedQuerySpec<T> query(RowMapper<T> rowMapper) {
return (useNamedParams() ?
new NamedParamMappedQuerySpec<>(rowMapper) :
new IndexedParamMappedQuerySpec<>(rowMapper));
@ -267,7 +267,7 @@ final class DefaultJdbcClient implements JdbcClient {
}
@Override
public <T> T query(ResultSetExtractor<T> rse) {
public <T extends @Nullable Object> T query(ResultSetExtractor<T> rse) {
T result = (useNamedParams() ?
this.namedParamOps.query(this.sql, this.namedParamSource, rse) :
this.classicOps.query(statementCreatorForIndexedParams(), rse));
@ -332,17 +332,18 @@ final class DefaultJdbcClient implements JdbcClient {
}
@Override
public List<Map<String, Object>> listOfRows() {
public List<Map<String, @Nullable Object>> listOfRows() {
return classicOps.queryForList(sql, indexedParams.toArray());
}
@Override
public Map<String, Object> singleRow() {
public Map<String, @Nullable Object> singleRow() {
return classicOps.queryForMap(sql, indexedParams.toArray());
}
@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());
}
}
@ -356,23 +357,25 @@ final class DefaultJdbcClient implements JdbcClient {
}
@Override
public List<Map<String, Object>> listOfRows() {
public List<Map<String, @Nullable Object>> listOfRows() {
return namedParamOps.queryForList(sql, namedParamSource);
}
@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);
}
@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);
}
}
private class IndexedParamMappedQuerySpec<T> implements MappedQuerySpec<T> {
private class IndexedParamMappedQuerySpec<T extends @Nullable Object> implements MappedQuerySpec<T> {
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;

View File

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

View File

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

View File

@ -174,7 +174,7 @@ public interface SimpleJdbcCallOperations {
* the stored procedure.
* @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
@ -182,7 +182,7 @@ public interface SimpleJdbcCallOperations {
* @param args a Map containing the parameter values to be used in the call
* @return a Map of output params
*/
Map<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
@ -190,6 +190,6 @@ public interface SimpleJdbcCallOperations {
* @param args the SqlParameterSource containing the parameter values to be used in the call
* @return a Map of output params
*/
Map<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
*/
@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,

View File

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

View File

@ -39,7 +39,7 @@ import org.jspecify.annotations.Nullable;
* @param <T> the result type
* @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.
@ -63,7 +63,7 @@ public abstract class MappingSqlQuery<T> extends MappingSqlQueryWithParameters<T
* @see #mapRow(ResultSet, int)
*/
@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 {
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
* 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.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.
@ -93,7 +93,7 @@ public abstract class MappingSqlQueryWithParameters<T> extends SqlQuery<T> {
* Subclasses can simply not catch SQLExceptions, relying on the
* framework to clean up.
*/
protected abstract @Nullable T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map<?, ?> context)
protected abstract T mapRow(ResultSet rs, int rowNum, @Nullable Object @Nullable [] parameters, @Nullable Map<?, ?> context)
throws SQLException;
@ -116,7 +116,7 @@ public abstract class MappingSqlQueryWithParameters<T> extends SqlQuery<T> {
}
@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);
}
}

View File

@ -51,7 +51,7 @@ import org.springframework.jdbc.core.SingleColumnRowMapper;
* @param <T> the result type
* @see StoredProcedure
*/
public class SqlFunction<T> extends MappingSqlQuery<T> {
public class SqlFunction<T> extends MappingSqlQuery<@Nullable T> {
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
* @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.

View File

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

View File

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

View File

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

View File

@ -43,6 +43,13 @@ class JdbcOperationsExtensionsTests {
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
fun `queryForObject with RowMapper-like function`() {
every { template.queryForObject(sql, any<RowMapper<Int>>(), any<Int>()) } returns 2
@ -52,9 +59,9 @@ class JdbcOperationsExtensionsTests {
@Test // gh-22682
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()
verify { template.queryForObject(eq(sql), any<RowMapper<Int>>(), eq(3)) }
verify { template.queryForObject(eq(sql), any<RowMapper<Int?>>(), eq(3)) }
}
@Test
@ -66,6 +73,15 @@ class JdbcOperationsExtensionsTests {
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
fun `queryForObject with reified type parameters and args`() {
val args = arrayOf(3, 4)
@ -74,6 +90,14 @@ class JdbcOperationsExtensionsTests {
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
fun `queryForList with reified type parameters`() {
val list = listOf(1, 2, 3)
@ -82,6 +106,14 @@ class JdbcOperationsExtensionsTests {
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
fun `queryForList with reified type parameters and argTypes`() {
val list = listOf(1, 2, 3)
@ -92,6 +124,16 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForList(sql, args, argTypes, any<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
fun `queryForList with reified type parameters and args`() {
val list = listOf(1, 2, 3)
@ -101,6 +143,15 @@ class JdbcOperationsExtensionsTests {
verify { template.queryForList(sql, any<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
fun `query with ResultSetExtractor-like function`() {
every { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) } returns 2
@ -113,12 +164,11 @@ class JdbcOperationsExtensionsTests {
@Test // gh-22682
fun `query with nullable ResultSetExtractor-like function`() {
every { template.query(eq(sql), any<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()
verify { template.query(eq(sql), any<ResultSetExtractor<Int>>(), eq(3)) }
verify { template.query(eq(sql), any<ResultSetExtractor<Int?>>(), eq(3)) }
}
@Suppress("RemoveExplicitTypeArguments")
@Test
fun `query with RowCallbackHandler-like function`() {
every { template.query(sql, ofType<RowCallbackHandler>(), 3) } returns Unit
@ -131,11 +181,21 @@ class JdbcOperationsExtensionsTests {
@Test
fun `query with RowMapper-like function`() {
val list = mutableListOf(1, 2, 3)
every { template.query(sql, ofType<RowMapper<*>>(), 3) } returns list
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<*>>(), 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.stream.Stream;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException;
@ -115,7 +116,7 @@ public abstract class DataAccessUtils {
* element has been found in the given Collection
* @since 6.1
*/
public static <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));
}
@ -158,7 +159,7 @@ public abstract class DataAccessUtils {
* @throws EmptyResultDataAccessException if no element at all
* has been found in the given Collection
*/
public static <T> 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)) {
throw new EmptyResultDataAccessException(1);
}
@ -184,7 +185,7 @@ public abstract class DataAccessUtils {
* has been found in the given Collection
* @since 5.0.2
*/
public static <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
// semantics of the incoming Collection (which we currently can't formally express)
if (CollectionUtils.isEmpty(results)) {