Browse Source

Clarify semantics of primitivesDefaultedForNullValue in BeanPropertyRowMapper

Closes gh-29923
pull/29928/head
Sam Brannen 3 years ago
parent
commit
159a3e71f2
  1. 49
      spring-jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
  2. 17
      spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
  3. 5
      spring-jdbc/src/test/java/org/springframework/jdbc/core/test/Person.java

49
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} * {@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 {

17
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"); * 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

5
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"); * 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;

Loading…
Cancel
Save