added a config property to control defaulting of primitive property when receiving null value from result (SPR-5588)
This commit is contained in:
parent
476a0ed76e
commit
93c56f19df
|
|
@ -56,7 +56,8 @@ import org.springframework.util.Assert;
|
||||||
* try using column aliases in the SQL statement like "select fname as first_name from customer".
|
* try using column aliases in the SQL statement like "select fname as first_name from customer".
|
||||||
*
|
*
|
||||||
* <p>For 'null' values read from the databasem, we will attempt to call the setter, but in the case of
|
* <p>For 'null' values read from the databasem, we will attempt to call the setter, but in the case of
|
||||||
* primitives, this causes a TypeMismatchException. We will trap this exception and log a warning message.
|
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
|
||||||
|
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
|
||||||
* Be aware that if you use the values from the generated bean to update the database the primitive value
|
* Be aware that if you use the values from the generated bean to update the database the primitive value
|
||||||
* will have been set to the primitive's default value instead of null.
|
* will have been set to the primitive's default value instead of null.
|
||||||
*
|
*
|
||||||
|
|
@ -78,6 +79,9 @@ 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 */
|
||||||
|
private boolean primitivesDefaultedForNullValue = false;
|
||||||
|
|
||||||
/** Map of the fields we provide mapping for */
|
/** Map of the fields we provide mapping for */
|
||||||
private Map<String, PropertyDescriptor> mappedFields;
|
private Map<String, PropertyDescriptor> mappedFields;
|
||||||
|
|
||||||
|
|
@ -199,6 +203,22 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||||
return this.checkFullyPopulated;
|
return this.checkFullyPopulated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether we're defaulting Java primitives in the case of mapping a null value from corresponding
|
||||||
|
* database fields.
|
||||||
|
* <p>Default is <code>false</code>, throwing an exception when nulls are mapped to Java primitives.
|
||||||
|
*/
|
||||||
|
public boolean isPrimitivesDefaultedForNullValue() {
|
||||||
|
return primitivesDefaultedForNullValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether we're defaulting Java primitives in the case of mapping a null value from corresponding
|
||||||
|
* database fields.
|
||||||
|
*/
|
||||||
|
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
|
||||||
|
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the values for all columns in the current row.
|
* Extract the values for all columns in the current row.
|
||||||
|
|
@ -229,11 +249,13 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
|
||||||
bw.setPropertyValue(pd.getName(), value);
|
bw.setPropertyValue(pd.getName(), value);
|
||||||
}
|
}
|
||||||
catch (TypeMismatchException e) {
|
catch (TypeMismatchException e) {
|
||||||
logger.warn("Intercepted TypeMismatchException for row " + rowNumber +
|
if (value == null && primitivesDefaultedForNullValue) {
|
||||||
" and column '" + column + "' with value " + value +
|
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
|
||||||
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
|
" and column '" + column + "' with value " + value +
|
||||||
" on object: " + mappedObject);
|
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
|
||||||
if (value != null) {
|
" on object: " + mappedObject);
|
||||||
|
}
|
||||||
|
else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,10 @@ import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||||
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
|
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
|
||||||
* float, Float, double, Double, BigDecimal, <code>java.util.Date</code>, etc.
|
* float, Float, double, Double, BigDecimal, <code>java.util.Date</code>, etc.
|
||||||
*
|
*
|
||||||
|
* <p>The mapper can be configured to use the primitives default value when mapping null values by
|
||||||
|
* passing in 'true' for the 'primitivesDefaultedForNullValue' using the {@link #newInstance(Class, boolean)} method.
|
||||||
|
* Also see {@link BeanPropertyRowMapper#setPrimitivesDefaultedForNullValue(boolean)}
|
||||||
|
*
|
||||||
* <p>To facilitate mapping between columns and fields that don't have matching names,
|
* <p>To facilitate mapping between columns and fields that don't have matching names,
|
||||||
* try using column aliases in the SQL statement like "select fname as first_name from customer".
|
* try using column aliases in the SQL statement like "select fname as first_name from customer".
|
||||||
*
|
*
|
||||||
|
|
@ -55,8 +59,19 @@ public class ParameterizedBeanPropertyRowMapper<T> extends BeanPropertyRowMapper
|
||||||
* @param mappedClass the class that each row should be mapped to
|
* @param mappedClass the class that each row should be mapped to
|
||||||
*/
|
*/
|
||||||
public static <T> ParameterizedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) {
|
public static <T> ParameterizedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) {
|
||||||
|
return newInstance(mappedClass, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static factory method to create a new ParameterizedBeanPropertyRowMapper
|
||||||
|
* (with the mapped class specified only once).
|
||||||
|
* @param mappedClass the class that each row should be mapped to
|
||||||
|
* @param primitivesDefaultedForNullValue whether we're defaulting primitives when mapping a null value
|
||||||
|
*/
|
||||||
|
public static <T> ParameterizedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass, boolean primitivesDefaultedForNullValue) {
|
||||||
ParameterizedBeanPropertyRowMapper<T> newInstance = new ParameterizedBeanPropertyRowMapper<T>();
|
ParameterizedBeanPropertyRowMapper<T> newInstance = new ParameterizedBeanPropertyRowMapper<T>();
|
||||||
newInstance.setMappedClass(mappedClass);
|
newInstance.setMappedClass(mappedClass);
|
||||||
|
newInstance.setPrimitivesDefaultedForNullValue(primitivesDefaultedForNullValue);
|
||||||
return newInstance;
|
return newInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ public abstract class AbstractRowMapperTests extends TestCase {
|
||||||
|
|
||||||
protected MockControl conControl;
|
protected MockControl conControl;
|
||||||
protected Connection con;
|
protected Connection con;
|
||||||
|
protected MockControl conControl2;
|
||||||
|
protected Connection con2;
|
||||||
protected MockControl rsmdControl;
|
protected MockControl rsmdControl;
|
||||||
protected ResultSetMetaData rsmd;
|
protected ResultSetMetaData rsmd;
|
||||||
protected MockControl rsControl;
|
protected MockControl rsControl;
|
||||||
|
|
@ -53,6 +55,13 @@ public abstract class AbstractRowMapperTests extends TestCase {
|
||||||
protected MockControl stmtControl;
|
protected MockControl stmtControl;
|
||||||
protected Statement stmt;
|
protected Statement stmt;
|
||||||
protected JdbcTemplate jdbcTemplate;
|
protected JdbcTemplate jdbcTemplate;
|
||||||
|
protected MockControl rsmdControl2;
|
||||||
|
protected ResultSetMetaData rsmd2;
|
||||||
|
protected MockControl rsControl2;
|
||||||
|
protected ResultSet rs2;
|
||||||
|
protected MockControl stmtControl2;
|
||||||
|
protected Statement stmt2;
|
||||||
|
protected JdbcTemplate jdbcTemplate2;
|
||||||
|
|
||||||
protected void setUp() throws SQLException {
|
protected void setUp() throws SQLException {
|
||||||
conControl = MockControl.createControl(Connection.class);
|
conControl = MockControl.createControl(Connection.class);
|
||||||
|
|
@ -110,13 +119,75 @@ public abstract class AbstractRowMapperTests extends TestCase {
|
||||||
stmt.close();
|
stmt.close();
|
||||||
stmtControl.setVoidCallable(1);
|
stmtControl.setVoidCallable(1);
|
||||||
|
|
||||||
|
conControl2 = MockControl.createControl(Connection.class);
|
||||||
|
con2 = (Connection) conControl2.getMock();
|
||||||
|
con2.isClosed();
|
||||||
|
conControl2.setDefaultReturnValue(false);
|
||||||
|
|
||||||
|
rsmdControl2 = MockControl.createControl(ResultSetMetaData.class);
|
||||||
|
rsmd2 = (ResultSetMetaData)rsmdControl2.getMock();
|
||||||
|
rsmd2.getColumnCount();
|
||||||
|
rsmdControl2.setReturnValue(4, 2);
|
||||||
|
rsmd2.getColumnLabel(1);
|
||||||
|
rsmdControl2.setReturnValue("name", 2);
|
||||||
|
rsmd2.getColumnLabel(2);
|
||||||
|
rsmdControl2.setReturnValue("age", 2);
|
||||||
|
rsmd2.getColumnLabel(3);
|
||||||
|
rsmdControl2.setReturnValue("birth_date", 1);
|
||||||
|
rsmd2.getColumnLabel(4);
|
||||||
|
rsmdControl2.setReturnValue("balance", 1);
|
||||||
|
rsmdControl2.replay();
|
||||||
|
|
||||||
|
rsControl2 = MockControl.createControl(ResultSet.class);
|
||||||
|
rs2 = (ResultSet) rsControl2.getMock();
|
||||||
|
rs2.getMetaData();
|
||||||
|
rsControl2.setReturnValue(rsmd2, 2);
|
||||||
|
rs2.next();
|
||||||
|
rsControl2.setReturnValue(true, 2);
|
||||||
|
rs2.getString(1);
|
||||||
|
rsControl2.setReturnValue("Bubba", 2);
|
||||||
|
rs2.wasNull();
|
||||||
|
rsControl2.setReturnValue(true, 2);
|
||||||
|
rs2.getLong(2);
|
||||||
|
rsControl2.setReturnValue(0, 2);
|
||||||
|
rs2.getTimestamp(3);
|
||||||
|
rsControl2.setReturnValue(new Timestamp(1221222L), 1);
|
||||||
|
rs2.getBigDecimal(4);
|
||||||
|
rsControl2.setReturnValue(new BigDecimal("1234.56"), 1);
|
||||||
|
rs2.next();
|
||||||
|
rsControl2.setReturnValue(false, 1);
|
||||||
|
rs2.close();
|
||||||
|
rsControl2.setVoidCallable(2);
|
||||||
|
rsControl2.replay();
|
||||||
|
|
||||||
|
stmtControl2 = MockControl.createControl(Statement.class);
|
||||||
|
stmt2 = (Statement) stmtControl2.getMock();
|
||||||
|
|
||||||
|
con2.createStatement();
|
||||||
|
conControl2.setReturnValue(stmt2, 2);
|
||||||
|
stmt2.executeQuery("select name, null as age, birth_date, balance from people");
|
||||||
|
stmtControl2.setReturnValue(rs2, 2);
|
||||||
|
if (debugEnabled) {
|
||||||
|
stmt2.getWarnings();
|
||||||
|
stmtControl2.setReturnValue(null, 2);
|
||||||
|
}
|
||||||
|
stmt2.close();
|
||||||
|
stmtControl2.setVoidCallable(2);
|
||||||
|
|
||||||
conControl.replay();
|
conControl.replay();
|
||||||
stmtControl.replay();
|
stmtControl.replay();
|
||||||
|
conControl2.replay();
|
||||||
|
stmtControl2.replay();
|
||||||
|
|
||||||
jdbcTemplate = new JdbcTemplate();
|
jdbcTemplate = new JdbcTemplate();
|
||||||
jdbcTemplate.setDataSource(new SingleConnectionDataSource(con, false));
|
jdbcTemplate.setDataSource(new SingleConnectionDataSource(con, false));
|
||||||
jdbcTemplate.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
|
jdbcTemplate.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
|
||||||
jdbcTemplate.afterPropertiesSet();
|
jdbcTemplate.afterPropertiesSet();
|
||||||
|
|
||||||
|
jdbcTemplate2 = new JdbcTemplate();
|
||||||
|
jdbcTemplate2.setDataSource(new SingleConnectionDataSource(con2, false));
|
||||||
|
jdbcTemplate2.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
|
||||||
|
jdbcTemplate2.afterPropertiesSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void verifyPerson(Person bean) {
|
protected void verifyPerson(Person bean) {
|
||||||
|
|
@ -127,6 +198,17 @@ public abstract class AbstractRowMapperTests extends TestCase {
|
||||||
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
|
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void verifyPersonWithZeroAge(Person bean) {
|
||||||
|
conControl2.verify();
|
||||||
|
rsControl2.verify();
|
||||||
|
rsmdControl2.verify();
|
||||||
|
stmtControl2.verify();
|
||||||
|
assertEquals("Bubba", bean.getName());
|
||||||
|
assertEquals(0L, bean.getAge());
|
||||||
|
assertEquals(new java.util.Date(1221222L), bean.getBirth_date());
|
||||||
|
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
|
||||||
|
}
|
||||||
|
|
||||||
protected void verifyConcretePerson(ConcretePerson bean) {
|
protected void verifyConcretePerson(ConcretePerson bean) {
|
||||||
verify();
|
verify();
|
||||||
assertEquals("Bubba", bean.getName());
|
assertEquals("Bubba", bean.getName());
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
import org.springframework.jdbc.core.test.ConcretePerson;
|
import org.springframework.jdbc.core.test.ConcretePerson;
|
||||||
import org.springframework.jdbc.core.test.ExtendedPerson;
|
import org.springframework.jdbc.core.test.ExtendedPerson;
|
||||||
import org.springframework.jdbc.core.test.Person;
|
import org.springframework.jdbc.core.test.Person;
|
||||||
|
import org.springframework.beans.TypeMismatchException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
|
|
@ -89,4 +90,22 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testMappingNullValue() throws SQLException {
|
||||||
|
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
|
||||||
|
try {
|
||||||
|
List result1 = jdbcTemplate2.query("select name, null as age, birth_date, balance from people",
|
||||||
|
mapper);
|
||||||
|
fail("Should have thrown TypeMismatchException because of null value");
|
||||||
|
}
|
||||||
|
catch (TypeMismatchException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
mapper.setPrimitivesDefaultedForNullValue(true);
|
||||||
|
List result2 = jdbcTemplate2.query("select name, null as age, birth_date, balance from people",
|
||||||
|
mapper);
|
||||||
|
assertEquals(1, result2.size());
|
||||||
|
Person bean = (Person) result2.get(0);
|
||||||
|
verifyPersonWithZeroAge(bean);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue