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 5b8192c3175..417c257efc3 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
@@ -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.
*
- *
For {@code NULL} values read from the database, an attempt will be made to
+ *
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.
*
*
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 the result type
* @see DataClassRowMapper
@@ -96,7 +96,11 @@ public class BeanPropertyRowMapper implements RowMapper {
/** 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 implements RowMapper {
}
/**
- * Set whether we're defaulting Java primitives in the case of mapping a null value
- * from corresponding database fields.
- * 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.
+ *
Default is {@code false}, throwing an exception when nulls are mapped
+ * to Java primitives.
+ *
If this flag is set to {@code true} and you use an ignored
+ * 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 implements RowMapper {
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 {
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
index 5ef1f57f891..4d1d53e6367 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
@@ -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 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 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
diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/Person.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/Person.java
index e1d9149d512..b9be634d949 100644
--- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/Person.java
+++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/Person.java
@@ -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;