Clarify semantics of primitivesDefaultedForNullValue in BeanPropertyRowMapper
Closes gh-29923
This commit is contained in:
@@ -65,14 +65,13 @@ import org.springframework.util.StringUtils;
|
|||||||
* {@code "select fname as first_name from customer"}, where {@code first_name}
|
* {@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.
|
* 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
|
* call the corresponding setter method with {@code null}, but in the case of
|
||||||
* Java primitives, this will result in a {@link TypeMismatchException}. This class
|
* Java primitives this will result in a {@link TypeMismatchException} by default.
|
||||||
* can be configured (via the {@link #setPrimitivesDefaultedForNullValue(boolean)
|
* To ignore {@code NULL} database values for all primitive properties in the
|
||||||
* primitivesDefaultedForNullValue} property) to catch this exception and use the
|
* target class, set the {@code primitivesDefaultedForNullValue} flag to
|
||||||
* primitive's default value. Be aware that if you use the values from the mapped
|
* {@code true}. See {@link #setPrimitivesDefaultedForNullValue(boolean)} for
|
||||||
* bean to update the database, the primitive value in the database will be
|
* details.
|
||||||
* changed from {@code NULL} to the primitive's default value.
|
|
||||||
*
|
*
|
||||||
* <p>Please note that this class is designed to provide convenience rather than
|
* <p>Please note that this class is designed to provide convenience rather than
|
||||||
* high performance. For best performance, consider using a custom {@code RowMapper}
|
* high performance. For best performance, consider using a custom {@code RowMapper}
|
||||||
@@ -80,6 +79,7 @@ import org.springframework.util.StringUtils;
|
|||||||
*
|
*
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 2.5
|
* @since 2.5
|
||||||
* @param <T> the result type
|
* @param <T> the result type
|
||||||
* @see DataClassRowMapper
|
* @see DataClassRowMapper
|
||||||
@@ -96,7 +96,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
|||||||
/** Whether we're strictly validating. */
|
/** Whether we're strictly validating. */
|
||||||
private boolean checkFullyPopulated = false;
|
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;
|
private boolean primitivesDefaultedForNullValue = false;
|
||||||
|
|
||||||
/** ConversionService for binding JDBC values to bean properties. */
|
/** 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
|
* Set whether a {@code NULL} database field value should be ignored when
|
||||||
* from corresponding database fields.
|
* 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>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) {
|
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
|
||||||
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
|
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether we're defaulting Java primitives in the case of mapping a null value
|
* Get the value of the {@code primitivesDefaultedForNullValue} flag.
|
||||||
* from corresponding database fields.
|
* @see #setPrimitivesDefaultedForNullValue(boolean)
|
||||||
*/
|
*/
|
||||||
public boolean isPrimitivesDefaultedForNullValue() {
|
public boolean isPrimitivesDefaultedForNullValue() {
|
||||||
return this.primitivesDefaultedForNullValue;
|
return this.primitivesDefaultedForNullValue;
|
||||||
@@ -328,11 +341,11 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
|||||||
catch (TypeMismatchException ex) {
|
catch (TypeMismatchException ex) {
|
||||||
if (value == null && this.primitivesDefaultedForNullValue) {
|
if (value == null && this.primitivesDefaultedForNullValue) {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
|
String propertyType = ClassUtils.getQualifiedName(pd.getPropertyType());
|
||||||
" and column '" + column + "' with null value when setting property '" +
|
logger.debug("""
|
||||||
pd.getName() + "' of type '" +
|
Ignoring intercepted TypeMismatchException for row %d and column '%s' \
|
||||||
ClassUtils.getQualifiedName(pd.getPropertyType()) +
|
with null value when setting property '%s' of type '%s' on object: %s"
|
||||||
"' on object: " + mappedObject, ex);
|
""".formatted(rowNumber, column, pd.getName(), propertyType, mappedObject), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||||
|
|
||||||
|
private static final String SELECT_NULL_AS_AGE = "select null as age from people";
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
void overridingDifferentClassDefinedForMapping() {
|
void overridingDifferentClassDefinedForMapping() {
|
||||||
@@ -115,7 +118,17 @@ class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
|||||||
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
BeanPropertyRowMapper<Person> mapper = new BeanPropertyRowMapper<>(Person.class);
|
||||||
Mock mock = new Mock(MockType.TWO);
|
Mock mock = new Mock(MockType.TWO);
|
||||||
assertThatExceptionOfType(TypeMismatchException.class).isThrownBy(() ->
|
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
|
@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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -31,6 +31,9 @@ public class Person {
|
|||||||
|
|
||||||
private BigDecimal balance;
|
private BigDecimal balance;
|
||||||
|
|
||||||
|
public Person() {
|
||||||
|
this.age = 42; // custom "default" value for a primitive
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
Reference in New Issue
Block a user