Skip to content

Commit b1c657f

Browse files
committed
Consistent nullability in DataAccessUtils through nullableSingleResult
Issue: SPR-16225
1 parent a8323f6 commit b1c657f

File tree

6 files changed

+72
-35
lines changed

6 files changed

+72
-35
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ public interface JdbcOperations {
141141
* {@code null} as argument array.
142142
* @param sql SQL query to execute
143143
* @param rowMapper object that will map one object per row
144-
* @return the single mapped object
144+
* @return the single mapped object (may be {@code null} if the given
145+
* {@link RowMapper} returned {@code} null)
145146
* @throws IncorrectResultSizeDataAccessException if the query does not
146147
* return exactly one row
147148
* @throws DataAccessException if there is any problem executing the query
@@ -371,6 +372,7 @@ public interface JdbcOperations {
371372
* only the argument value but also the SQL type and optionally the scale
372373
* @return an arbitrary result object, as returned by the ResultSetExtractor
373374
* @throws DataAccessException if the query fails
375+
* @since 3.0.1
374376
*/
375377
@Nullable
376378
<T> T query(String sql, ResultSetExtractor<T> rse, @Nullable Object... args) throws DataAccessException;
@@ -441,6 +443,7 @@ public interface JdbcOperations {
441443
* may also contain {@link SqlParameterValue} objects which indicate not
442444
* only the argument value but also the SQL type and optionally the scale
443445
* @throws DataAccessException if the query fails
446+
* @since 3.0.1
444447
*/
445448
void query(String sql, RowCallbackHandler rch, @Nullable Object... args) throws DataAccessException;
446449

@@ -514,6 +517,7 @@ public interface JdbcOperations {
514517
* only the argument value but also the SQL type and optionally the scale
515518
* @return the result List, containing mapped objects
516519
* @throws DataAccessException if the query fails
520+
* @since 3.0.1
517521
*/
518522
<T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException;
519523

@@ -527,7 +531,8 @@ public interface JdbcOperations {
527531
* @param argTypes SQL types of the arguments
528532
* (constants from {@code java.sql.Types})
529533
* @param rowMapper object that will map one object per row
530-
* @return the single mapped object
534+
* @return the single mapped object (may be {@code null} if the given
535+
* {@link RowMapper} returned {@code} null)
531536
* @throws IncorrectResultSizeDataAccessException if the query does not
532537
* return exactly one row
533538
* @throws DataAccessException if the query fails
@@ -546,7 +551,8 @@ <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> row
546551
* may also contain {@link SqlParameterValue} objects which indicate not
547552
* only the argument value but also the SQL type and optionally the scale
548553
* @param rowMapper object that will map one object per row
549-
* @return the single mapped object
554+
* @return the single mapped object (may be {@code null} if the given
555+
* {@link RowMapper} returned {@code} null)
550556
* @throws IncorrectResultSizeDataAccessException if the query does not
551557
* return exactly one row
552558
* @throws DataAccessException if the query fails
@@ -564,10 +570,12 @@ <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> row
564570
* (leaving it to the PreparedStatement to guess the corresponding SQL type);
565571
* may also contain {@link SqlParameterValue} objects which indicate not
566572
* only the argument value but also the SQL type and optionally the scale
567-
* @return the single mapped object
573+
* @return the single mapped object (may be {@code null} if the given
574+
* {@link RowMapper} returned {@code} null)
568575
* @throws IncorrectResultSizeDataAccessException if the query does not
569576
* return exactly one row
570577
* @throws DataAccessException if the query fails
578+
* @since 3.0.1
571579
*/
572580
@Nullable
573581
<T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException;
@@ -628,6 +636,7 @@ <T> T queryForObject(String sql, Object[] args, int[] argTypes, Class<T> require
628636
* @throws IncorrectResultSizeDataAccessException if the query does not return
629637
* exactly one row, or does not return exactly one column in that row
630638
* @throws DataAccessException if the query fails
639+
* @since 3.0.1
631640
* @see #queryForObject(String, Class)
632641
*/
633642
@Nullable
@@ -728,6 +737,7 @@ <T>List<T> queryForList(String sql, Object[] args, int[] argTypes, Class<T> elem
728737
* only the argument value but also the SQL type and optionally the scale
729738
* @return a List of objects that match the specified element type
730739
* @throws DataAccessException if the query fails
740+
* @since 3.0.1
731741
* @see #queryForList(String, Class)
732742
* @see SingleColumnRowMapper
733743
*/

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ public Map<String, Object> queryForMap(String sql) throws DataAccessException {
465465
@Nullable
466466
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
467467
List<T> results = query(sql, rowMapper);
468-
return DataAccessUtils.requiredSingleResult(results);
468+
return DataAccessUtils.nullableSingleResult(results);
469469
}
470470

471471
@Override
@@ -762,21 +762,21 @@ public <T> T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper
762762
throws DataAccessException {
763763

764764
List<T> results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1));
765-
return DataAccessUtils.requiredSingleResult(results);
765+
return DataAccessUtils.nullableSingleResult(results);
766766
}
767767

768768
@Override
769769
@Nullable
770770
public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
771771
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
772-
return DataAccessUtils.requiredSingleResult(results);
772+
return DataAccessUtils.nullableSingleResult(results);
773773
}
774774

775775
@Override
776776
@Nullable
777777
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args) throws DataAccessException {
778778
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
779-
return DataAccessUtils.requiredSingleResult(results);
779+
return DataAccessUtils.nullableSingleResult(results);
780780
}
781781

782782
@Override

spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public interface RowMapper<T> {
5656
* the ResultSet; it is only supposed to map values of the current row.
5757
* @param rs the ResultSet to map (pre-initialized for the current row)
5858
* @param rowNum the number of the current row
59-
* @return the result object for the current row
59+
* @return the result object for the current row (may be {@code null})
6060
* @throws SQLException if a SQLException is encountered getting
6161
* column values (that is, there's no need to catch SQLException)
6262
*/

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -233,7 +233,8 @@ <T> List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
233233
* @param sql SQL query to execute
234234
* @param paramSource container of arguments to bind to the query
235235
* @param rowMapper object that will map one object per row
236-
* @return the single mapped object
236+
* @return the single mapped object (may be {@code null} if the given
237+
* {@link RowMapper} returned {@code} null)
237238
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
238239
* if the query does not return exactly one row, or does not return exactly
239240
* one column in that row
@@ -251,7 +252,8 @@ <T> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> ro
251252
* @param paramMap map of parameters to bind to the query
252253
* (leaving it to the PreparedStatement to guess the corresponding SQL type)
253254
* @param rowMapper object that will map one object per row
254-
* @return the single mapped object
255+
* @return the single mapped object (may be {@code null} if the given
256+
* {@link RowMapper} returned {@code} null)
255257
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
256258
* if the query does not return exactly one row, or does not return exactly
257259
* one column in that row

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ public <T> T queryForObject(String sql, SqlParameterSource paramSource, RowMappe
217217
throws DataAccessException {
218218

219219
List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
220-
return DataAccessUtils.requiredSingleResult(results);
220+
return DataAccessUtils.nullableSingleResult(results);
221221
}
222222

223223
@Override

spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -47,34 +47,57 @@ public abstract class DataAccessUtils {
4747
*/
4848
@Nullable
4949
public static <T> T singleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
50-
int size = (results != null ? results.size() : 0);
51-
if (size == 0) {
50+
if (CollectionUtils.isEmpty(results)) {
5251
return null;
5352
}
5453
if (results.size() > 1) {
55-
throw new IncorrectResultSizeDataAccessException(1, size);
54+
throw new IncorrectResultSizeDataAccessException(1, results.size());
5655
}
5756
return results.iterator().next();
5857
}
5958

6059
/**
6160
* Return a single result object from the given Collection.
6261
* <p>Throws an exception if 0 or more than 1 element found.
63-
* @param results the result Collection (can be {@code null})
62+
* @param results the result Collection (can be {@code null}
63+
* but is not expected to contain {@code null} elements)
6464
* @return the single result object
6565
* @throws IncorrectResultSizeDataAccessException if more than one
6666
* element has been found in the given Collection
6767
* @throws EmptyResultDataAccessException if no element at all
6868
* has been found in the given Collection
6969
*/
70-
@Nullable
7170
public static <T> T requiredSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
72-
int size = (results != null ? results.size() : 0);
73-
if (size == 0) {
71+
if (CollectionUtils.isEmpty(results)) {
7472
throw new EmptyResultDataAccessException(1);
7573
}
7674
if (results.size() > 1) {
77-
throw new IncorrectResultSizeDataAccessException(1, size);
75+
throw new IncorrectResultSizeDataAccessException(1, results.size());
76+
}
77+
return results.iterator().next();
78+
}
79+
80+
/**
81+
* Return a single result object from the given Collection.
82+
* <p>Throws an exception if 0 or more than 1 element found.
83+
* @param results the result Collection (can be {@code null}
84+
* and is also expected to contain {@code null} elements)
85+
* @return the single result object
86+
* @throws IncorrectResultSizeDataAccessException if more than one
87+
* element has been found in the given Collection
88+
* @throws EmptyResultDataAccessException if no element at all
89+
* has been found in the given Collection
90+
* @since 5.0.2
91+
*/
92+
@Nullable
93+
public static <T> T nullableSingleResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
94+
// This is identical to the requiredSingleResult implementation but differs in the
95+
// semantics of the incoming Collection (which we currently can't formally express)
96+
if (CollectionUtils.isEmpty(results)) {
97+
throw new EmptyResultDataAccessException(1);
98+
}
99+
if (results.size() > 1) {
100+
throw new IncorrectResultSizeDataAccessException(1, results.size());
78101
}
79102
return results.iterator().next();
80103
}
@@ -91,20 +114,20 @@ public static <T> T requiredSingleResult(@Nullable Collection<T> results) throws
91114
*/
92115
@Nullable
93116
public static <T> T uniqueResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
94-
int size = (results != null ? results.size() : 0);
95-
if (size == 0) {
117+
if (CollectionUtils.isEmpty(results)) {
96118
return null;
97119
}
98120
if (!CollectionUtils.hasUniqueObject(results)) {
99-
throw new IncorrectResultSizeDataAccessException(1, size);
121+
throw new IncorrectResultSizeDataAccessException(1, results.size());
100122
}
101123
return results.iterator().next();
102124
}
103125

104126
/**
105127
* Return a unique result object from the given Collection.
106128
* <p>Throws an exception if 0 or more than 1 instance found.
107-
* @param results the result Collection (can be {@code null})
129+
* @param results the result Collection (can be {@code null}
130+
* but is not expected to contain {@code null} elements)
108131
* @return the unique result object
109132
* @throws IncorrectResultSizeDataAccessException if more than one
110133
* result object has been found in the given Collection
@@ -113,12 +136,11 @@ public static <T> T uniqueResult(@Nullable Collection<T> results) throws Incorre
113136
* @see org.springframework.util.CollectionUtils#hasUniqueObject
114137
*/
115138
public static <T> T requiredUniqueResult(@Nullable Collection<T> results) throws IncorrectResultSizeDataAccessException {
116-
int size = (results != null ? results.size() : 0);
117-
if (size == 0) {
139+
if (CollectionUtils.isEmpty(results)) {
118140
throw new EmptyResultDataAccessException(1);
119141
}
120142
if (!CollectionUtils.hasUniqueObject(results)) {
121-
throw new IncorrectResultSizeDataAccessException(1, size);
143+
throw new IncorrectResultSizeDataAccessException(1, results.size());
122144
}
123145
return results.iterator().next();
124146
}
@@ -128,7 +150,8 @@ public static <T> T requiredUniqueResult(@Nullable Collection<T> results) throws
128150
* Throws an exception if 0 or more than 1 result objects found,
129151
* of if the unique result object is not convertible to the
130152
* specified required type.
131-
* @param results the result Collection (can be {@code null})
153+
* @param results the result Collection (can be {@code null}
154+
* but is not expected to contain {@code null} elements)
132155
* @return the unique result object
133156
* @throws IncorrectResultSizeDataAccessException if more than one
134157
* result object has been found in the given Collection
@@ -167,7 +190,8 @@ else if (Number.class.isAssignableFrom(requiredType) && Number.class.isInstance(
167190
* Return a unique int result from the given Collection.
168191
* Throws an exception if 0 or more than 1 result objects found,
169192
* of if the unique result object is not convertible to an int.
170-
* @param results the result Collection (can be {@code null})
193+
* @param results the result Collection (can be {@code null}
194+
* but is not expected to contain {@code null} elements)
171195
* @return the unique int result
172196
* @throws IncorrectResultSizeDataAccessException if more than one
173197
* result object has been found in the given Collection
@@ -186,7 +210,8 @@ public static int intResult(@Nullable Collection<?> results)
186210
* Return a unique long result from the given Collection.
187211
* Throws an exception if 0 or more than 1 result objects found,
188212
* of if the unique result object is not convertible to a long.
189-
* @param results the result Collection (can be {@code null})
213+
* @param results the result Collection (can be {@code null}
214+
* but is not expected to contain {@code null} elements)
190215
* @return the unique long result
191216
* @throws IncorrectResultSizeDataAccessException if more than one
192217
* result object has been found in the given Collection
@@ -204,11 +229,11 @@ public static long longResult(@Nullable Collection<?> results)
204229

205230
/**
206231
* Return a translated exception if this is appropriate,
207-
* otherwise return the input exception.
208-
* @param rawException exception we may wish to translate
232+
* otherwise return the given exception as-is.
233+
* @param rawException an exception that we may wish to translate
209234
* @param pet PersistenceExceptionTranslator to use to perform the translation
210-
* @return a translated exception if translation is possible, or
211-
* the raw exception if it is not
235+
* @return a translated persistence exception if translation is possible,
236+
* or the raw exception if it is not
212237
*/
213238
public static RuntimeException translateIfNecessary(
214239
RuntimeException rawException, PersistenceExceptionTranslator pet) {

0 commit comments

Comments
 (0)