diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java index 99eb4e451b..9825977b37 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java @@ -274,7 +274,7 @@ public class BeanPropertyRowMapper implements RowMapper { /** * Convert the given name to lower case. - * By default, conversions will happen within the US locale. + *

By default, conversions will happen within the US locale. * @param name the original name * @return the converted name * @since 4.2 @@ -285,7 +285,7 @@ public class BeanPropertyRowMapper implements RowMapper { /** * Convert a name in camelCase to an underscored name in lower case. - * Any upper case letters are converted to lower case with a preceding underscore. + *

Any upper case letters are converted to lower case with a preceding underscore. * @param name the original name * @return the converted name * @since 4.2 @@ -390,7 +390,7 @@ public class BeanPropertyRowMapper implements RowMapper { /** * Initialize the given BeanWrapper to be used for row mapping. - * To be called for each row. + *

To be called for each row. *

The default implementation applies the configured {@link ConversionService}, * if any. Can be overridden in subclasses. * @param bw the BeanWrapper to initialize diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/BeanPropertyRowMapper.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/BeanPropertyRowMapper.java index 7636ce2e17..a40f213cbb 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/BeanPropertyRowMapper.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/BeanPropertyRowMapper.java @@ -48,47 +48,46 @@ import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** - * Mapping {@code Function} implementation that converts an R2DBC {@code Readable} - * (a {@code Row} or {@code OutParameters}) into a new instance of the specified mapped + * Mapping {@code Function} implementation that converts an R2DBC {@link Readable} + * (a {@link Row} or {@link OutParameters}) into a new instance of the specified mapped * target class. The mapped target class must be a top-level class or {@code static} * nested class, and it must have a default or no-arg constructor. * - *

- * Readable component values are mapped based on matching the name (as obtained from R2DBC - * meta-data) to public setters in the target class for the corresponding properties. The - * names are matched either directly or by transforming a name separating the parts with - * underscores to the same name using "camel" case. + *

{@code Readable} component values are mapped based on matching the column + * name (as obtained from R2DBC meta-data) to public setters in the target class + * for the corresponding properties. The names are matched either directly or by + * transforming a name separating the parts with underscores to the same name using + * "camel" case. * - *

- * Mapping is provided for properties in the target class for many common types — - * for example: String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, - * Long, float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc. + *

Mapping is provided for properties in the target class for many common types — + * for example: String, boolean, Boolean, byte, Byte, short, Short, int, Integer, + * long, Long, float, Float, double, Double, BigDecimal, {@code java.util.Date}, etc. * - *

- * To facilitate mapping between columns and properties that don't have matching names, - * try using column aliases in the SQL statement like - * {@code "select fname as first_name from customer"}, where {@code first_name} can be - * mapped to a {@code setFirstName(String)} method in the target class. + *

To facilitate mapping between columns and properties that don't have matching + * names, try using column aliases in the SQL statement like + * {@code "select fname as first_name from customer"}, where {@code first_name} + * can be mapped to a {@code setFirstName(String)} method in the target class. * - *

- * For a {@code NULL} value read from the database, an attempt will be made to call the - * corresponding setter method with {@code null}, but in the case of Java primitives this - * will result in a {@link TypeMismatchException} by default. To ignore {@code NULL} - * database values for all primitive properties in the target class, set the - * {@code primitivesDefaultedForNullValue} flag to {@code true}. See - * {@link #setPrimitivesDefaultedForNullValue(boolean)} for details. + *

For a {@code NULL} value read from the database, an attempt will be made to + * call the corresponding setter method with {@code null}, but in the case of + * Java primitives this will result in a {@link TypeMismatchException} by default. + * To ignore {@code NULL} database values for all primitive properties in the + * target class, set the {@code primitivesDefaultedForNullValue} flag to + * {@code true}. See {@link #setPrimitivesDefaultedForNullValue(boolean)} for + * details. * - *

- * If you need to map to a target class which has a data class constructor - * — for example, a Java {@code record} or a Kotlin {@code data} class — use - * {@link DataClassRowMapper} instead. + *

If you need to map to a target class which has a data class constructor + * — for example, a Java {@code record} or a Kotlin {@code data} class — + * use {@link DataClassRowMapper} instead. * - *

- * Please note that this class is designed to provide convenience rather than high - * performance. For best performance, consider using a custom mapping function + *

Please note that this class is designed to provide convenience rather than + * high performance. For best performance, consider using a custom mapping function * implementation. * * @author Simon Baslé + * @author Thomas Risberg + * @author Juergen Hoeller + * @author Sam Brannen * @since 6.1 * @param the result type * @see DataClassRowMapper @@ -254,7 +253,7 @@ public class BeanPropertyRowMapper implements Function { /** * Convert the given name to lower case. - * By default, conversions will happen within the US locale. + *

By default, conversions will happen within the US locale. * @param name the original name * @return the converted name */ @@ -264,7 +263,7 @@ public class BeanPropertyRowMapper implements Function { /** * Convert a name in camelCase to an underscored name in lower case. - * Any upper case letters are converted to lower case with a preceding underscore. + *

Any upper case letters are converted to lower case with a preceding underscore. * @param name the original name * @return the converted name * @see #lowerCaseName @@ -289,13 +288,11 @@ public class BeanPropertyRowMapper implements Function { } /** - * Extract the values for the current {@code Readable} : - * all columns in case of a {@code Row} or all parameters in - * case of an {@code OutParameters}. - *

Utilizes public setters and derives meta-data from the - * concrete type. - * @throws UnsupportedOperationException in case the concrete type - * is neither {@code Row} nor {@code OutParameters} + * Extract the values for the current {@link Readable}: all columns in case + * of a {@link Row} or all parameters in case of an {@link OutParameters}. + *

Utilizes public setters and derives meta-data from the concrete type. + * @throws IllegalArgumentException in case the concrete type is neither + * {@code Row} nor {@code OutParameters} * @see RowMetadata * @see OutParametersMetadata */ @@ -326,7 +323,7 @@ public class BeanPropertyRowMapper implements Function { PropertyDescriptor pd = (this.mappedProperties != null ? this.mappedProperties.get(property) : null); if (pd != null) { Object value = getItemValue(readable, itemIndex, pd); - //Implementation note: the JDBC mapper can log the column mapping details each time row 0 is encountered + // Implementation note: the JDBC mapper can log the column mapping details each time row 0 is encountered // but unfortunately this is not possible in R2DBC as row number is not provided. The BiFunction#apply // cannot be stateful as it could be applied to a different row set, e.g. when resubscribing. try { @@ -363,9 +360,8 @@ public class BeanPropertyRowMapper implements Function { /** * Construct an instance of the mapped class for the current {@code Readable}. - *

- * The default implementation simply instantiates the mapped class. Can be overridden - * in subclasses. + *

The default implementation simply instantiates the mapped class. Can be + * overridden in subclasses. * @param readable the {@code Readable} being mapped (a {@code Row} or {@code OutParameters}) * @param itemMetadatas the list of item {@code ReadableMetadata} (either * {@code ColumnMetadata} or {@code OutParameterMetadata}) @@ -380,7 +376,7 @@ public class BeanPropertyRowMapper implements Function { /** * Initialize the given BeanWrapper to be used for row mapping or outParameters * mapping. - * To be called for each Readable. + *

To be called for each Readable. *

The default implementation applies the configured {@link ConversionService}, * if any. Can be overridden in subclasses. * @param bw the BeanWrapper to initialize @@ -395,7 +391,7 @@ public class BeanPropertyRowMapper implements Function { } /** - * Retrieve a R2DBC object value for the specified item index (a column or an + * Retrieve an R2DBC object value for the specified item index (a column or an * out-parameter). *

The default implementation delegates to * {@link #getItemValue(Readable, int, Class)}. @@ -411,12 +407,12 @@ public class BeanPropertyRowMapper implements Function { } /** - * Retrieve a R2DBC object value for the specified item index (a column or + * Retrieve an R2DBC object value for the specified item index (a column or * an out-parameter). *

The default implementation calls {@link Readable#get(int, Class)} then * falls back to {@link Readable#get(int)} in case of an exception. * Subclasses may override this to check specific value types upfront, - * or to post-process values return from {@code get}. + * or to post-process values returned from {@code get}. * @param readable is the {@code Row} or {@code OutParameters} holding the data * @param itemIndex is the column index or out-parameter index * @param paramType the target parameter type diff --git a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DataClassRowMapper.java b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DataClassRowMapper.java index 87a67e7387..40e53c3c22 100644 --- a/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DataClassRowMapper.java +++ b/spring-r2dbc/src/main/java/org/springframework/r2dbc/core/DataClassRowMapper.java @@ -32,35 +32,34 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** - * Mapping {@code Function} implementation that converts an R2DBC {@code Readable} - * (a {@code Row} or {@code OutParameters}) into a new instance of the specified mapped - * target class. The mapped target class must be a top-level class or {@code static} - * nested class, and it may expose either a data class constructor with named - * parameters corresponding to column names or classic bean property setter methods - * with property names corresponding to column names (or even a combination of both). + * Mapping {@code Function} implementation that converts an R2DBC {@link Readable} + * (a {@link io.r2dbc.spi.Row Row} or {@link io.r2dbc.spi.OutParameters OutParameters}) + * into a new instance of the specified mapped target class. The mapped target class + * must be a top-level class or {@code static} nested class, and it may expose either + * a data class constructor with named parameters corresponding to column + * names or classic bean property setter methods with property names corresponding + * to column names (or even a combination of both). * - *

- * The term "data class" applies to Java records, Kotlin data classes, - * and any class which has a constructor with named parameters that are intended to be - * mapped to corresponding column names. + *

The term "data class" applies to Java records, Kotlin data + * classes, and any class which has a constructor with named parameters + * that are intended to be mapped to corresponding column names. * - *

- * When combining a data class constructor with setter methods, any property mapped - * successfully via a constructor argument will not be mapped additionally via a - * corresponding setter method. This means that constructor arguments take precedence over - * property setter methods. + *

When combining a data class constructor with setter methods, any property + * mapped successfully via a constructor argument will not be mapped additionally + * via a corresponding setter method. This means that constructor arguments take + * precedence over property setter methods. * - *

- * Note that this class extends {@link BeanPropertyRowMapper} and can therefore serve as a - * common choice for any mapped target class, flexibly adapting to constructor style - * versus setter methods in the mapped class. + *

Note that this class extends {@link BeanPropertyRowMapper} and can + * therefore serve as a common choice for any mapped target class, flexibly + * adapting to constructor style versus setter methods in the mapped class. * - *

- * Please note that this class is designed to provide convenience rather than high - * performance. For best performance, consider using a custom readable mapping + *

Please note that this class is designed to provide convenience rather than + * high performance. For best performance, consider using a custom readable mapping * {@code Function} implementation. * * @author Simon Baslé + * @author Juergen Hoeller + * @author Sam Brannen * @since 6.1 * @param the result type */ @@ -135,7 +134,7 @@ public class DataClassRowMapper extends BeanPropertyRowMapper { private int findIndex(Readable readable, List itemMetadatas, String name) { int index = 0; for (ReadableMetadata itemMetadata : itemMetadatas) { - //we use equalsIgnoreCase, similarly to RowMetadata#contains(String) + // we use equalsIgnoreCase, similar to RowMetadata#contains(String) if (itemMetadata.getName().equalsIgnoreCase(name)) { return index; } diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/BeanPropertyRowMapperTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcBeanPropertyRowMapperTests.java similarity index 86% rename from spring-r2dbc/src/test/java/org/springframework/r2dbc/core/BeanPropertyRowMapperTests.java rename to spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcBeanPropertyRowMapperTests.java index e72a457549..be8b0dab19 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/BeanPropertyRowMapperTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcBeanPropertyRowMapperTests.java @@ -34,25 +34,30 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatNoException; -class BeanPropertyRowMapperTests { +/** + * Tests for R2DBC-based {@link BeanPropertyRowMapper}. + * + * @since 6.1 + */ +class R2dbcBeanPropertyRowMapperTests { @Test void mappingUnknownReadableRejected() { - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); assertThatIllegalArgumentException().isThrownBy(() -> mapper.apply(Mockito.mock(Readable.class))) .withMessageStartingWith("Can only map Readable Row or OutParameters, got io.r2dbc.spi.Readable$MockitoMock$"); } @Test void mappingOutParametersAccepted() { - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); assertThatNoException().isThrownBy(() -> mapper.apply(MockOutParameters.empty())); } @Test void mappingRowSimpleObject() { MockRow mockRow = SIMPLE_PERSON_ROW; - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); Person result = mapper.apply(mockRow); @@ -64,7 +69,7 @@ class BeanPropertyRowMapperTests { @Test void mappingRowMissingAttributeAccepted() { MockRow mockRow = SIMPLE_PERSON_ROW; - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class); ExtendedPerson result = mapper.apply(mockRow); @@ -77,7 +82,7 @@ class BeanPropertyRowMapperTests { @Test void mappingRowWithDifferentName() { MockRow mockRow = EMAIL_PERSON_ROW; - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(EmailPerson.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(EmailPerson.class); EmailPerson result = mapper.apply(mockRow); @@ -89,21 +94,22 @@ class BeanPropertyRowMapperTests { @Test void mappingRowMissingAttributeRejected() { + Class mappedClass = ExtendedPerson.class; MockRow mockRow = SIMPLE_PERSON_ROW; - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(ExtendedPerson.class, true); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(mappedClass, true); assertThatExceptionOfType(InvalidDataAccessApiUsageException.class) .isThrownBy(() -> mapper.apply(mockRow)) - .withMessage("Given readable does not contain all items necessary to populate object of class org.springframework." - + "r2dbc.core.BeanPropertyRowMapperTests$ExtendedPerson: [firstName, lastName, address, age]"); + .withMessage("Given readable does not contain all items necessary to populate object of %s" + + ": [firstName, lastName, address, age]", mappedClass); } - //TODO cannot trigger a mapping of a read-only property, as mappedProperties don't include properties without a setter. + // TODO cannot trigger a mapping of a read-only property, as mappedProperties don't include properties without a setter. @Test void rowTypeAndMappingTypeMisaligned() { MockRow mockRow = EXTENDED_PERSON_ROW; - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(TypeMismatchExtendedPerson.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(TypeMismatchExtendedPerson.class); assertThatExceptionOfType(TypeMismatchException.class) .isThrownBy(() -> mapper.apply(mockRow)) @@ -124,7 +130,7 @@ class BeanPropertyRowMapperTests { .identified(2, int.class, null) .identified(3, String.class, "123 Sesame Street") .build(); - final BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); + BeanPropertyRowMapper mapper = new BeanPropertyRowMapper<>(Person.class); mapper.setPrimitivesDefaultedForNullValue(true); Person result = mapper.apply(mockRow); @@ -147,6 +153,7 @@ class BeanPropertyRowMapperTests { } + @SuppressWarnings("unused") private static class Person { String firstName; @@ -181,6 +188,7 @@ class BeanPropertyRowMapperTests { } + @SuppressWarnings("unused") private static class ExtendedPerson extends Person { String address; @@ -204,6 +212,7 @@ class BeanPropertyRowMapperTests { } + @SuppressWarnings("unused") private static class EmailPerson extends Person { String email; diff --git a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/DataClassRowMapperTests.java b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcDataClassRowMapperTests.java similarity index 87% rename from spring-r2dbc/src/test/java/org/springframework/r2dbc/core/DataClassRowMapperTests.java rename to spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcDataClassRowMapperTests.java index f0de42d27b..6f94b886f4 100644 --- a/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/DataClassRowMapperTests.java +++ b/spring-r2dbc/src/test/java/org/springframework/r2dbc/core/R2dbcDataClassRowMapperTests.java @@ -27,12 +27,17 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -class DataClassRowMapperTests { +/** + * Test for R2DBC-based {@link DataClassRowMapper}. + * + * @since 6.1 + */ +class R2dbcDataClassRowMapperTests { @Test void staticQueryWithDataClass() { MockRow mockRow = MOCK_ROW; // uses name, age, birth_date - final DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPerson.class); + DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPerson.class); ConstructorPerson person = mapper.apply(mockRow); @@ -44,8 +49,8 @@ class DataClassRowMapperTests { @Test void staticQueryWithDataClassAndGenerics() { MockRow mockRow = buildMockRow("birth_date", true); // uses name, age, birth_date, balance (as list) - //TODO validate actual R2DBC Row implementations would return something for balance if asking a List - final DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPersonWithGenerics.class); + // TODO validate actual R2DBC Row implementations would return something for balance if requesting a List + DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPersonWithGenerics.class); ConstructorPersonWithGenerics person = mapper.apply(mockRow); assertThat(person.name()).isEqualTo("Bubba"); @@ -57,7 +62,7 @@ class DataClassRowMapperTests { @Test void staticQueryWithDataRecord() { MockRow mockRow = MOCK_ROW; // uses name, age, birth_date, balance - final DataClassRowMapper mapper = new DataClassRowMapper<>(RecordPerson.class); + DataClassRowMapper mapper = new DataClassRowMapper<>(RecordPerson.class); RecordPerson person = mapper.apply(mockRow); assertThat(person.name()).isEqualTo("Bubba"); @@ -69,7 +74,7 @@ class DataClassRowMapperTests { @Test void staticQueryWithDataClassAndSetters() { MockRow mockRow = buildMockRow("birthdate", false); // uses name, age, birthdate (no underscore), balance - final DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPersonWithSetters.class); + DataClassRowMapper mapper = new DataClassRowMapper<>(ConstructorPersonWithSetters.class); ConstructorPersonWithSetters person = mapper.apply(mockRow); assertThat(person.name()).isEqualTo("BUBBA"); @@ -177,10 +182,10 @@ class DataClassRowMapperTests { } - static MockRow MOCK_ROW = buildMockRow("birth_date", false); + static final MockRow MOCK_ROW = buildMockRow("birth_date", false); private static MockRow buildMockRow(String birthDateColumnName, boolean balanceObjectIdentifier) { - final MockRow.Builder builder = MockRow.builder(); + MockRow.Builder builder = MockRow.builder(); builder.metadata(MockRowMetadata.builder() .columnMetadata(MockColumnMetadata.builder().name("name").javaType(String.class).build()) .columnMetadata(MockColumnMetadata.builder().name("age").javaType(long.class).build()) diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 8ceea888a8..f3ac6f9935 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -766,7 +766,7 @@ class RestTemplateTests { given(request.getHeaders()).willReturn(requestHeaders); } - @SuppressWarnings("deprecation") + @SuppressWarnings({ "deprecation", "removal" }) private void mockResponseStatus(HttpStatus responseStatus) throws Exception { given(request.execute()).willReturn(response); given(errorHandler.hasError(response)).willReturn(responseStatus.isError());