diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java index 56912f0fcb7..5e386aa4583 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java @@ -141,7 +141,8 @@ public interface JdbcOperations { * {@code null} as argument array. * @param sql SQL query to execute * @param rowMapper object that will map one object per row - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws IncorrectResultSizeDataAccessException if the query does not * return exactly one row * @throws DataAccessException if there is any problem executing the query @@ -371,6 +372,7 @@ public interface JdbcOperations { * only the argument value but also the SQL type and optionally the scale * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if the query fails + * @since 3.0.1 */ @Nullable T query(String sql, ResultSetExtractor rse, @Nullable Object... args) throws DataAccessException; @@ -441,6 +443,7 @@ public interface JdbcOperations { * may also contain {@link SqlParameterValue} objects which indicate not * only the argument value but also the SQL type and optionally the scale * @throws DataAccessException if the query fails + * @since 3.0.1 */ void query(String sql, RowCallbackHandler rch, @Nullable Object... args) throws DataAccessException; @@ -514,6 +517,7 @@ public interface JdbcOperations { * only the argument value but also the SQL type and optionally the scale * @return the result List, containing mapped objects * @throws DataAccessException if the query fails + * @since 3.0.1 */ List query(String sql, RowMapper rowMapper, @Nullable Object... args) throws DataAccessException; @@ -527,7 +531,8 @@ public interface JdbcOperations { * @param argTypes SQL types of the arguments * (constants from {@code java.sql.Types}) * @param rowMapper object that will map one object per row - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws IncorrectResultSizeDataAccessException if the query does not * return exactly one row * @throws DataAccessException if the query fails @@ -546,7 +551,8 @@ public interface JdbcOperations { * may also contain {@link SqlParameterValue} objects which indicate not * only the argument value but also the SQL type and optionally the scale * @param rowMapper object that will map one object per row - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws IncorrectResultSizeDataAccessException if the query does not * return exactly one row * @throws DataAccessException if the query fails @@ -564,10 +570,12 @@ public interface JdbcOperations { * (leaving it to the PreparedStatement to guess the corresponding SQL type); * may also contain {@link SqlParameterValue} objects which indicate not * only the argument value but also the SQL type and optionally the scale - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws IncorrectResultSizeDataAccessException if the query does not * return exactly one row * @throws DataAccessException if the query fails + * @since 3.0.1 */ @Nullable T queryForObject(String sql, RowMapper rowMapper, @Nullable Object... args) throws DataAccessException; @@ -628,6 +636,7 @@ public interface JdbcOperations { * @throws IncorrectResultSizeDataAccessException if the query does not return * exactly one row, or does not return exactly one column in that row * @throws DataAccessException if the query fails + * @since 3.0.1 * @see #queryForObject(String, Class) */ @Nullable @@ -728,6 +737,7 @@ public interface JdbcOperations { * only the argument value but also the SQL type and optionally the scale * @return a List of objects that match the specified element type * @throws DataAccessException if the query fails + * @since 3.0.1 * @see #queryForList(String, Class) * @see SingleColumnRowMapper */ diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java index a1744d695ec..a41eb1b7155 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java @@ -465,7 +465,7 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { @Nullable public T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { List results = query(sql, rowMapper); - return DataAccessUtils.requiredSingleResult(results); + return DataAccessUtils.nullableSingleResult(results); } @Override @@ -762,21 +762,21 @@ public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { throws DataAccessException { List results = query(sql, args, argTypes, new RowMapperResultSetExtractor<>(rowMapper, 1)); - return DataAccessUtils.requiredSingleResult(results); + return DataAccessUtils.nullableSingleResult(results); } @Override @Nullable public T queryForObject(String sql, @Nullable Object[] args, RowMapper rowMapper) throws DataAccessException { List results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1)); - return DataAccessUtils.requiredSingleResult(results); + return DataAccessUtils.nullableSingleResult(results); } @Override @Nullable public T queryForObject(String sql, RowMapper rowMapper, @Nullable Object... args) throws DataAccessException { List results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1)); - return DataAccessUtils.requiredSingleResult(results); + return DataAccessUtils.nullableSingleResult(results); } @Override diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java index be060e41d16..85277443413 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/RowMapper.java @@ -56,7 +56,7 @@ public interface RowMapper { * the ResultSet; it is only supposed to map values of the current row. * @param rs the ResultSet to map (pre-initialized for the current row) * @param rowNum the number of the current row - * @return the result object for the current row + * @return the result object for the current row (may be {@code null}) * @throws SQLException if a SQLException is encountered getting * column values (that is, there's no need to catch SQLException) */ diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java index 176aed75135..2e624de30da 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -233,7 +233,8 @@ public interface NamedParameterJdbcOperations { * @param sql SQL query to execute * @param paramSource container of arguments to bind to the query * @param rowMapper object that will map one object per row - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws org.springframework.dao.IncorrectResultSizeDataAccessException * if the query does not return exactly one row, or does not return exactly * one column in that row @@ -251,7 +252,8 @@ public interface NamedParameterJdbcOperations { * @param paramMap map of parameters to bind to the query * (leaving it to the PreparedStatement to guess the corresponding SQL type) * @param rowMapper object that will map one object per row - * @return the single mapped object + * @return the single mapped object (may be {@code null} if the given + * {@link RowMapper} returned {@code} null) * @throws org.springframework.dao.IncorrectResultSizeDataAccessException * if the query does not return exactly one row, or does not return exactly * one column in that row diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java index 810d08f0249..439eb08574b 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java @@ -217,7 +217,7 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations throws DataAccessException { List results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper); - return DataAccessUtils.requiredSingleResult(results); + return DataAccessUtils.nullableSingleResult(results); } @Override diff --git a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java index 52a24dbe27e..f908a5527ad 100644 --- a/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java +++ b/spring-tx/src/main/java/org/springframework/dao/support/DataAccessUtils.java @@ -47,12 +47,11 @@ public abstract class DataAccessUtils { */ @Nullable public static T singleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { - int size = (results != null ? results.size() : 0); - if (size == 0) { + if (CollectionUtils.isEmpty(results)) { return null; } if (results.size() > 1) { - throw new IncorrectResultSizeDataAccessException(1, size); + throw new IncorrectResultSizeDataAccessException(1, results.size()); } return results.iterator().next(); } @@ -60,21 +59,45 @@ public abstract class DataAccessUtils { /** * Return a single result object from the given Collection. *

Throws an exception if 0 or more than 1 element found. - * @param results the result Collection (can be {@code null}) + * @param results the result Collection (can be {@code null} + * but is not expected to contain {@code null} elements) * @return the single result object * @throws IncorrectResultSizeDataAccessException if more than one * element has been found in the given Collection * @throws EmptyResultDataAccessException if no element at all * has been found in the given Collection */ - @Nullable public static T requiredSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { - int size = (results != null ? results.size() : 0); - if (size == 0) { + if (CollectionUtils.isEmpty(results)) { throw new EmptyResultDataAccessException(1); } if (results.size() > 1) { - throw new IncorrectResultSizeDataAccessException(1, size); + throw new IncorrectResultSizeDataAccessException(1, results.size()); + } + return results.iterator().next(); + } + + /** + * Return a single result object from the given Collection. + *

Throws an exception if 0 or more than 1 element found. + * @param results the result Collection (can be {@code null} + * and is also expected to contain {@code null} elements) + * @return the single result object + * @throws IncorrectResultSizeDataAccessException if more than one + * element has been found in the given Collection + * @throws EmptyResultDataAccessException if no element at all + * has been found in the given Collection + * @since 5.0.2 + */ + @Nullable + public static T nullableSingleResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { + // This is identical to the requiredSingleResult implementation but differs in the + // semantics of the incoming Collection (which we currently can't formally express) + if (CollectionUtils.isEmpty(results)) { + throw new EmptyResultDataAccessException(1); + } + if (results.size() > 1) { + throw new IncorrectResultSizeDataAccessException(1, results.size()); } return results.iterator().next(); } @@ -91,12 +114,11 @@ public abstract class DataAccessUtils { */ @Nullable public static T uniqueResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { - int size = (results != null ? results.size() : 0); - if (size == 0) { + if (CollectionUtils.isEmpty(results)) { return null; } if (!CollectionUtils.hasUniqueObject(results)) { - throw new IncorrectResultSizeDataAccessException(1, size); + throw new IncorrectResultSizeDataAccessException(1, results.size()); } return results.iterator().next(); } @@ -104,7 +126,8 @@ public abstract class DataAccessUtils { /** * Return a unique result object from the given Collection. *

Throws an exception if 0 or more than 1 instance found. - * @param results the result Collection (can be {@code null}) + * @param results the result Collection (can be {@code null} + * but is not expected to contain {@code null} elements) * @return the unique result object * @throws IncorrectResultSizeDataAccessException if more than one * result object has been found in the given Collection @@ -113,12 +136,11 @@ public abstract class DataAccessUtils { * @see org.springframework.util.CollectionUtils#hasUniqueObject */ public static T requiredUniqueResult(@Nullable Collection results) throws IncorrectResultSizeDataAccessException { - int size = (results != null ? results.size() : 0); - if (size == 0) { + if (CollectionUtils.isEmpty(results)) { throw new EmptyResultDataAccessException(1); } if (!CollectionUtils.hasUniqueObject(results)) { - throw new IncorrectResultSizeDataAccessException(1, size); + throw new IncorrectResultSizeDataAccessException(1, results.size()); } return results.iterator().next(); } @@ -128,7 +150,8 @@ public abstract class DataAccessUtils { * Throws an exception if 0 or more than 1 result objects found, * of if the unique result object is not convertible to the * specified required type. - * @param results the result Collection (can be {@code null}) + * @param results the result Collection (can be {@code null} + * but is not expected to contain {@code null} elements) * @return the unique result object * @throws IncorrectResultSizeDataAccessException if more than one * result object has been found in the given Collection @@ -167,7 +190,8 @@ public abstract class DataAccessUtils { * Return a unique int result from the given Collection. * Throws an exception if 0 or more than 1 result objects found, * of if the unique result object is not convertible to an int. - * @param results the result Collection (can be {@code null}) + * @param results the result Collection (can be {@code null} + * but is not expected to contain {@code null} elements) * @return the unique int result * @throws IncorrectResultSizeDataAccessException if more than one * result object has been found in the given Collection @@ -186,7 +210,8 @@ public abstract class DataAccessUtils { * Return a unique long result from the given Collection. * Throws an exception if 0 or more than 1 result objects found, * of if the unique result object is not convertible to a long. - * @param results the result Collection (can be {@code null}) + * @param results the result Collection (can be {@code null} + * but is not expected to contain {@code null} elements) * @return the unique long result * @throws IncorrectResultSizeDataAccessException if more than one * result object has been found in the given Collection @@ -204,11 +229,11 @@ public abstract class DataAccessUtils { /** * Return a translated exception if this is appropriate, - * otherwise return the input exception. - * @param rawException exception we may wish to translate + * otherwise return the given exception as-is. + * @param rawException an exception that we may wish to translate * @param pet PersistenceExceptionTranslator to use to perform the translation - * @return a translated exception if translation is possible, or - * the raw exception if it is not + * @return a translated persistence exception if translation is possible, + * or the raw exception if it is not */ public static RuntimeException translateIfNecessary( RuntimeException rawException, PersistenceExceptionTranslator pet) {