Clarify semantics of primitivesDefaultedForNullValue in BeanPropertyRowMapper
Closes gh-29923
This commit is contained in:
parent
adbba712d6
commit
159a3e71f2
|
|
@ -65,14 +65,13 @@ import org.springframework.util.StringUtils;
|
|||
* {@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.
|
||||
*
|
||||
* <p>For {@code NULL} values read from the database, an attempt will be made to
|
||||
* <p>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}. This class
|
||||
* can be configured (via the {@link #setPrimitivesDefaultedForNullValue(boolean)
|
||||
* primitivesDefaultedForNullValue} property) to catch this exception and use the
|
||||
* primitive's default value. Be aware that if you use the values from the mapped
|
||||
* bean to update the database, the primitive value in the database will be
|
||||
* changed from {@code NULL} to the primitive's default value.
|
||||
* 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.
|
||||
*
|
||||
* <p>Please note that this class is designed to provide convenience rather than
|
||||
* high performance. For best performance, consider using a custom {@code RowMapper}
|
||||
|
|
@ -80,6 +79,7 @@ import org.springframework.util.StringUtils;
|
|||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 2.5
|
||||
* @param <T> the result type
|
||||
* @see DataClassRowMapper
|
||||
|
|
@ -96,7 +96,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
|||
/** Whether we're strictly validating. */
|
||||
private boolean checkFullyPopulated = false;
|
||||
|
||||
/** Whether we're defaulting primitives when mapping a null value. */
|
||||
/**
|
||||
* Whether {@code NULL} database values should be ignored for primitive
|
||||
* properties in the target class.
|
||||
* @see #setPrimitivesDefaultedForNullValue(boolean)
|
||||
*/
|
||||
private boolean primitivesDefaultedForNullValue = false;
|
||||
|
||||
/** ConversionService for binding JDBC values to bean properties. */
|
||||
|
|
@ -182,17 +186,26 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set whether we're defaulting Java primitives in the case of mapping a null value
|
||||
* from corresponding database fields.
|
||||
* <p>Default is {@code false}, throwing an exception when nulls are mapped to Java primitives.
|
||||
* Set whether a {@code NULL} database field value should be ignored when
|
||||
* mapping to a corresponding primitive property in the target class.
|
||||
* <p>Default is {@code false}, throwing an exception when nulls are mapped
|
||||
* to Java primitives.
|
||||
* <p>If this flag is set to {@code true} and you use an <em>ignored</em>
|
||||
* primitive property value from the mapped bean to update the database, the
|
||||
* value in the database will be changed from {@code NULL} to the current value
|
||||
* of that primitive property. That value may be the field's initial value
|
||||
* (potentially Java's default value for the respective primitive type), or
|
||||
* it may be some other value set for the property in the default constructor
|
||||
* (or initialization block) or as a side effect of setting some other property
|
||||
* in the mapped bean.
|
||||
*/
|
||||
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
|
||||
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether we're defaulting Java primitives in the case of mapping a null value
|
||||
* from corresponding database fields.
|
||||
* Get the value of the {@code primitivesDefaultedForNullValue} flag.
|
||||
* @see #setPrimitivesDefaultedForNullValue(boolean)
|
||||
*/
|
||||
public boolean isPrimitivesDefaultedForNullValue() {
|
||||
return this.primitivesDefaultedForNullValue;
|
||||
|
|
@ -328,11 +341,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
|||
catch (TypeMismatchException ex) {
|
||||
if (value == null && this.primitivesDefaultedForNullValue) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
|
||||
" and column '" + column + "' with null value when setting property '" +
|
||||
pd.getName() + "' of type '" +
|
||||
ClassUtils.getQualifiedName(pd.getPropertyType()) +
|
||||
"' on object: " + mappedObject, ex);
|
||||
String propertyType = ClassUtils.getQualifiedName(pd.getPropertyType());
|
||||
logger.debug("""
|
||||
Ignoring intercepted TypeMismatchException for row %d and column '%s' \
|
||||
with null value when setting property '%s' of type '%s' on object: %s"
|
||||
""".formatted(rowNumber, column, pd.getName(), propertyType, mappedObject), ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
|
|
@ -44,6 +44,9 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
|
|||
*/
|
||||
class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||
|
||||
private static final String SELECT_NULL_AS_AGE = "select null as age from people";
|
||||
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
void overridingDifferentClassDefinedForMapping() {
|
||||
|
|
@ -115,7 +118,17 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
|||
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
||||
Mock mock = new Mock(MockType.TWO);
|
||||
assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() ->
|
||||
mock.getJdbcTemplate().query("select name, null as age, birth_date, balance from people", mapper));
|
||||
mock.getJdbcTemplate().query(SELECT_NULL_AS_AGE, mapper));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mappingNullValueWithPrimitivesDefaultedForNullValue() throws Exception {
|
||||
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
||||
mapper.setPrimitivesDefaultedForNullValue(true);
|
||||
Mock mock = new Mock(MockType.TWO);
|
||||
Person person = mock.getJdbcTemplate().queryForObject(SELECT_NULL_AS_AGE, mapper);
|
||||
assertThat(person).extracting(Person::getAge).isEqualTo(42L);
|
||||
mock.verifyClosed();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
|
|
@ -31,6 +31,9 @@ public class Person {
|
|||
|
||||
private BigDecimal balance;
|
||||
|
||||
public Person() {
|
||||
this.age = 42; // custom "default" value for a primitive
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
|
|
|
|||
Loading…
Reference in New Issue