diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
index 7b9d8a909e..5ae9462e97 100644
--- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java
@@ -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".
*
*
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
* will have been set to the primitive's default value instead of null.
*
@@ -78,6 +79,9 @@ public class BeanPropertyRowMapper implements RowMapper {
/** Whether we're strictly validating */
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 */
private Map mappedFields;
@@ -199,6 +203,22 @@ public class BeanPropertyRowMapper implements RowMapper {
return this.checkFullyPopulated;
}
+ /**
+ * Set whether we're defaulting Java primitives in the case of mapping a null value from corresponding
+ * database fields.
+ * Default is false
, 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.
@@ -229,11 +249,13 @@ public class BeanPropertyRowMapper implements RowMapper {
bw.setPropertyValue(pd.getName(), value);
}
catch (TypeMismatchException e) {
- logger.warn("Intercepted TypeMismatchException for row " + rowNumber +
- " and column '" + column + "' with value " + value +
- " when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
- " on object: " + mappedObject);
- if (value != null) {
+ if (value == null && primitivesDefaultedForNullValue) {
+ logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
+ " and column '" + column + "' with value " + value +
+ " when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
+ " on object: " + mappedObject);
+ }
+ else {
throw e;
}
}
diff --git a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/simple/ParameterizedBeanPropertyRowMapper.java b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/simple/ParameterizedBeanPropertyRowMapper.java
index 982ca8ea3e..037caf9482 100644
--- a/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/simple/ParameterizedBeanPropertyRowMapper.java
+++ b/org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/simple/ParameterizedBeanPropertyRowMapper.java
@@ -35,6 +35,10 @@ import org.springframework.jdbc.core.BeanPropertyRowMapper;
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
* float, Float, double, Double, BigDecimal, java.util.Date
, etc.
*
+ * 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)}
+ *
*
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".
*
@@ -55,8 +59,19 @@ public class ParameterizedBeanPropertyRowMapper extends BeanPropertyRowMapper
* @param mappedClass the class that each row should be mapped to
*/
public static ParameterizedBeanPropertyRowMapper newInstance(Class 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 ParameterizedBeanPropertyRowMapper newInstance(Class mappedClass, boolean primitivesDefaultedForNullValue) {
ParameterizedBeanPropertyRowMapper newInstance = new ParameterizedBeanPropertyRowMapper();
newInstance.setMappedClass(mappedClass);
+ newInstance.setPrimitivesDefaultedForNullValue(primitivesDefaultedForNullValue);
return newInstance;
}
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
index 271fadee7c..f31d3dd025 100644
--- a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java
@@ -46,6 +46,8 @@ public abstract class AbstractRowMapperTests extends TestCase {
protected MockControl conControl;
protected Connection con;
+ protected MockControl conControl2;
+ protected Connection con2;
protected MockControl rsmdControl;
protected ResultSetMetaData rsmd;
protected MockControl rsControl;
@@ -53,6 +55,13 @@ public abstract class AbstractRowMapperTests extends TestCase {
protected MockControl stmtControl;
protected Statement stmt;
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 {
conControl = MockControl.createControl(Connection.class);
@@ -110,13 +119,75 @@ public abstract class AbstractRowMapperTests extends TestCase {
stmt.close();
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();
stmtControl.replay();
+ conControl2.replay();
+ stmtControl2.replay();
jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(new SingleConnectionDataSource(con, false));
jdbcTemplate.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
jdbcTemplate.afterPropertiesSet();
+
+ jdbcTemplate2 = new JdbcTemplate();
+ jdbcTemplate2.setDataSource(new SingleConnectionDataSource(con2, false));
+ jdbcTemplate2.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
+ jdbcTemplate2.afterPropertiesSet();
}
protected void verifyPerson(Person bean) {
@@ -127,6 +198,17 @@ public abstract class AbstractRowMapperTests extends TestCase {
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) {
verify();
assertEquals("Bubba", bean.getName());
diff --git a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
index e877542898..718ddf4518 100644
--- a/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
+++ b/org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java
@@ -23,6 +23,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.test.ConcretePerson;
import org.springframework.jdbc.core.test.ExtendedPerson;
import org.springframework.jdbc.core.test.Person;
+import org.springframework.beans.TypeMismatchException;
/**
* @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);
+ }
+
}