Skip to content

Commit 93c56f1

Browse files
author
Thomas Risberg
committedMar 25, 2009
added a config property to control defaulting of primitive property when receiving null value from result (SPR-5588)
1 parent 476a0ed commit 93c56f1

File tree

4 files changed

+144
-6
lines changed

4 files changed

+144
-6
lines changed
 

‎org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/BeanPropertyRowMapper.java

+28-6
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
* try using column aliases in the SQL statement like "select fname as first_name from customer".
5757
*
5858
* <p>For 'null' values read from the databasem, we will attempt to call the setter, but in the case of
59-
* primitives, this causes a TypeMismatchException. We will trap this exception and log a warning message.
59+
* Java primitives, this causes a TypeMismatchException. This class can be configured (using the
60+
* primitivesDefaultedForNullValue property) to trap this exception and use the primitives default value.
6061
* Be aware that if you use the values from the generated bean to update the database the primitive value
6162
* will have been set to the primitive's default value instead of null.
6263
*
@@ -78,6 +79,9 @@ public class BeanPropertyRowMapper<T> implements RowMapper<T> {
7879
/** Whether we're strictly validating */
7980
private boolean checkFullyPopulated = false;
8081

82+
/** Whether we're defaulting primitives when mapping a null value */
83+
private boolean primitivesDefaultedForNullValue = false;
84+
8185
/** Map of the fields we provide mapping for */
8286
private Map<String, PropertyDescriptor> mappedFields;
8387

@@ -199,6 +203,22 @@ public boolean isCheckFullyPopulated() {
199203
return this.checkFullyPopulated;
200204
}
201205

206+
/**
207+
* Set whether we're defaulting Java primitives in the case of mapping a null value from corresponding
208+
* database fields.
209+
* <p>Default is <code>false</code>, throwing an exception when nulls are mapped to Java primitives.
210+
*/
211+
public boolean isPrimitivesDefaultedForNullValue() {
212+
return primitivesDefaultedForNullValue;
213+
}
214+
215+
/**
216+
* Return whether we're defaulting Java primitives in the case of mapping a null value from corresponding
217+
* database fields.
218+
*/
219+
public void setPrimitivesDefaultedForNullValue(boolean primitivesDefaultedForNullValue) {
220+
this.primitivesDefaultedForNullValue = primitivesDefaultedForNullValue;
221+
}
202222

203223
/**
204224
* Extract the values for all columns in the current row.
@@ -229,11 +249,13 @@ public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
229249
bw.setPropertyValue(pd.getName(), value);
230250
}
231251
catch (TypeMismatchException e) {
232-
logger.warn("Intercepted TypeMismatchException for row " + rowNumber +
233-
" and column '" + column + "' with value " + value +
234-
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
235-
" on object: " + mappedObject);
236-
if (value != null) {
252+
if (value == null && primitivesDefaultedForNullValue) {
253+
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
254+
" and column '" + column + "' with value " + value +
255+
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
256+
" on object: " + mappedObject);
257+
}
258+
else {
237259
throw e;
238260
}
239261
}

‎org.springframework.jdbc/src/main/java/org/springframework/jdbc/core/simple/ParameterizedBeanPropertyRowMapper.java

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
* String, boolean, Boolean, byte, Byte, short, Short, int, Integer, long, Long,
3636
* float, Float, double, Double, BigDecimal, <code>java.util.Date</code>, etc.
3737
*
38+
* <p>The mapper can be configured to use the primitives default value when mapping null values by
39+
* passing in 'true' for the 'primitivesDefaultedForNullValue' using the {@link #newInstance(Class, boolean)} method.
40+
* Also see {@link BeanPropertyRowMapper#setPrimitivesDefaultedForNullValue(boolean)}
41+
*
3842
* <p>To facilitate mapping between columns and fields that don't have matching names,
3943
* try using column aliases in the SQL statement like "select fname as first_name from customer".
4044
*
@@ -55,8 +59,19 @@ public class ParameterizedBeanPropertyRowMapper<T> extends BeanPropertyRowMapper
5559
* @param mappedClass the class that each row should be mapped to
5660
*/
5761
public static <T> ParameterizedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) {
62+
return newInstance(mappedClass, false);
63+
}
64+
65+
/**
66+
* Static factory method to create a new ParameterizedBeanPropertyRowMapper
67+
* (with the mapped class specified only once).
68+
* @param mappedClass the class that each row should be mapped to
69+
* @param primitivesDefaultedForNullValue whether we're defaulting primitives when mapping a null value
70+
*/
71+
public static <T> ParameterizedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass, boolean primitivesDefaultedForNullValue) {
5872
ParameterizedBeanPropertyRowMapper<T> newInstance = new ParameterizedBeanPropertyRowMapper<T>();
5973
newInstance.setMappedClass(mappedClass);
74+
newInstance.setPrimitivesDefaultedForNullValue(primitivesDefaultedForNullValue);
6075
return newInstance;
6176
}
6277

‎org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java

+82
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,22 @@ public abstract class AbstractRowMapperTests extends TestCase {
4646

4747
protected MockControl conControl;
4848
protected Connection con;
49+
protected MockControl conControl2;
50+
protected Connection con2;
4951
protected MockControl rsmdControl;
5052
protected ResultSetMetaData rsmd;
5153
protected MockControl rsControl;
5254
protected ResultSet rs;
5355
protected MockControl stmtControl;
5456
protected Statement stmt;
5557
protected JdbcTemplate jdbcTemplate;
58+
protected MockControl rsmdControl2;
59+
protected ResultSetMetaData rsmd2;
60+
protected MockControl rsControl2;
61+
protected ResultSet rs2;
62+
protected MockControl stmtControl2;
63+
protected Statement stmt2;
64+
protected JdbcTemplate jdbcTemplate2;
5665

5766
protected void setUp() throws SQLException {
5867
conControl = MockControl.createControl(Connection.class);
@@ -110,13 +119,75 @@ protected void setUp() throws SQLException {
110119
stmt.close();
111120
stmtControl.setVoidCallable(1);
112121

122+
conControl2 = MockControl.createControl(Connection.class);
123+
con2 = (Connection) conControl2.getMock();
124+
con2.isClosed();
125+
conControl2.setDefaultReturnValue(false);
126+
127+
rsmdControl2 = MockControl.createControl(ResultSetMetaData.class);
128+
rsmd2 = (ResultSetMetaData)rsmdControl2.getMock();
129+
rsmd2.getColumnCount();
130+
rsmdControl2.setReturnValue(4, 2);
131+
rsmd2.getColumnLabel(1);
132+
rsmdControl2.setReturnValue("name", 2);
133+
rsmd2.getColumnLabel(2);
134+
rsmdControl2.setReturnValue("age", 2);
135+
rsmd2.getColumnLabel(3);
136+
rsmdControl2.setReturnValue("birth_date", 1);
137+
rsmd2.getColumnLabel(4);
138+
rsmdControl2.setReturnValue("balance", 1);
139+
rsmdControl2.replay();
140+
141+
rsControl2 = MockControl.createControl(ResultSet.class);
142+
rs2 = (ResultSet) rsControl2.getMock();
143+
rs2.getMetaData();
144+
rsControl2.setReturnValue(rsmd2, 2);
145+
rs2.next();
146+
rsControl2.setReturnValue(true, 2);
147+
rs2.getString(1);
148+
rsControl2.setReturnValue("Bubba", 2);
149+
rs2.wasNull();
150+
rsControl2.setReturnValue(true, 2);
151+
rs2.getLong(2);
152+
rsControl2.setReturnValue(0, 2);
153+
rs2.getTimestamp(3);
154+
rsControl2.setReturnValue(new Timestamp(1221222L), 1);
155+
rs2.getBigDecimal(4);
156+
rsControl2.setReturnValue(new BigDecimal("1234.56"), 1);
157+
rs2.next();
158+
rsControl2.setReturnValue(false, 1);
159+
rs2.close();
160+
rsControl2.setVoidCallable(2);
161+
rsControl2.replay();
162+
163+
stmtControl2 = MockControl.createControl(Statement.class);
164+
stmt2 = (Statement) stmtControl2.getMock();
165+
166+
con2.createStatement();
167+
conControl2.setReturnValue(stmt2, 2);
168+
stmt2.executeQuery("select name, null as age, birth_date, balance from people");
169+
stmtControl2.setReturnValue(rs2, 2);
170+
if (debugEnabled) {
171+
stmt2.getWarnings();
172+
stmtControl2.setReturnValue(null, 2);
173+
}
174+
stmt2.close();
175+
stmtControl2.setVoidCallable(2);
176+
113177
conControl.replay();
114178
stmtControl.replay();
179+
conControl2.replay();
180+
stmtControl2.replay();
115181

116182
jdbcTemplate = new JdbcTemplate();
117183
jdbcTemplate.setDataSource(new SingleConnectionDataSource(con, false));
118184
jdbcTemplate.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
119185
jdbcTemplate.afterPropertiesSet();
186+
187+
jdbcTemplate2 = new JdbcTemplate();
188+
jdbcTemplate2.setDataSource(new SingleConnectionDataSource(con2, false));
189+
jdbcTemplate2.setExceptionTranslator(new SQLStateSQLExceptionTranslator());
190+
jdbcTemplate2.afterPropertiesSet();
120191
}
121192

122193
protected void verifyPerson(Person bean) {
@@ -127,6 +198,17 @@ protected void verifyPerson(Person bean) {
127198
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
128199
}
129200

201+
protected void verifyPersonWithZeroAge(Person bean) {
202+
conControl2.verify();
203+
rsControl2.verify();
204+
rsmdControl2.verify();
205+
stmtControl2.verify();
206+
assertEquals("Bubba", bean.getName());
207+
assertEquals(0L, bean.getAge());
208+
assertEquals(new java.util.Date(1221222L), bean.getBirth_date());
209+
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
210+
}
211+
130212
protected void verifyConcretePerson(ConcretePerson bean) {
131213
verify();
132214
assertEquals("Bubba", bean.getName());

‎org.springframework.jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.jdbc.core.test.ConcretePerson;
2424
import org.springframework.jdbc.core.test.ExtendedPerson;
2525
import org.springframework.jdbc.core.test.Person;
26+
import org.springframework.beans.TypeMismatchException;
2627

2728
/**
2829
* @author Thomas Risberg
@@ -89,4 +90,22 @@ public void testMappingWithUnpopulatedFieldsNotAccepted() throws SQLException {
8990
}
9091
}
9192

93+
public void testMappingNullValue() throws SQLException {
94+
BeanPropertyRowMapper mapper = new BeanPropertyRowMapper(Person.class);
95+
try {
96+
List result1 = jdbcTemplate2.query("select name, null as age, birth_date, balance from people",
97+
mapper);
98+
fail("Should have thrown TypeMismatchException because of null value");
99+
}
100+
catch (TypeMismatchException ex) {
101+
// expected
102+
}
103+
mapper.setPrimitivesDefaultedForNullValue(true);
104+
List result2 = jdbcTemplate2.query("select name, null as age, birth_date, balance from people",
105+
mapper);
106+
assertEquals(1, result2.size());
107+
Person bean = (Person) result2.get(0);
108+
verifyPersonWithZeroAge(bean);
109+
}
110+
92111
}

0 commit comments

Comments
 (0)
Please sign in to comment.