JdbcUtils explicitly extracts SQL date/time for JSR-310 LocalDate/Time

Issue: SPR-14898
This commit is contained in:
Juergen Hoeller 2016-11-22 14:52:21 +01:00
parent 3b49aacb9f
commit a0fee4657d
5 changed files with 133 additions and 20 deletions

View File

@ -125,8 +125,10 @@ public abstract class JdbcUtils {
* @param rs is the ResultSet holding the data
* @param index is the column index
* @param requiredType the required value type (may be {@code null})
* @return the value object
* @return the value object (possibly not of the specified required type,
* with further conversion steps necessary)
* @throws SQLException if thrown by the JDBC API
* @see #getResultSetValue(ResultSet, int)
*/
public static Object getResultSetValue(ResultSet rs, int index, Class<?> requiredType) throws SQLException {
if (requiredType == null) {
@ -182,6 +184,7 @@ public abstract class JdbcUtils {
else if (Clob.class == requiredType) {
return rs.getClob(index);
}
else {
// Some unknown type desired -> rely on getObject.
try {
@ -196,7 +199,22 @@ public abstract class JdbcUtils {
catch (SQLException ex) {
logger.debug("JDBC driver has limited support for JDBC 4.1 'getObject(int, Class)' method", ex);
}
// Fall back to getObject without type specification...
// Corresponding SQL types for JSR-310 / Joda-Time types, left up
// to the caller to convert them (e.g. through a ConversionService).
String typeName = requiredType.getSimpleName();
if ("LocalDate".equals(typeName)) {
return rs.getDate(index);
}
else if ("LocalTime".equals(typeName)) {
return rs.getTime(index);
}
else if ("LocalDateTime".equals(typeName)) {
return rs.getTimestamp(index);
}
// Fall back to getObject without type specification, again
// left up to the caller to convert the value if necessary.
return getResultSetValue(rs, index);
}
@ -248,7 +266,7 @@ public abstract class JdbcUtils {
obj = rs.getDate(index);
}
}
else if (obj != null && obj instanceof java.sql.Date) {
else if (obj instanceof java.sql.Date) {
if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
obj = rs.getTimestamp(index);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2016 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.
@ -20,10 +20,12 @@ import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.sql.Timestamp;
import org.springframework.jdbc.core.test.ConcretePerson;
import org.springframework.jdbc.core.test.DatePerson;
import org.springframework.jdbc.core.test.Person;
import org.springframework.jdbc.core.test.SpacePerson;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
@ -47,21 +49,30 @@ public abstract class AbstractRowMapperTests {
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
}
protected void verifyConcretePerson(ConcretePerson bean) throws Exception {
protected void verifyPerson(ConcretePerson bean) throws Exception {
assertEquals("Bubba", bean.getName());
assertEquals(22L, bean.getAge());
assertEquals(new java.util.Date(1221222L), bean.getBirth_date());
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
}
protected void verifySpacePerson(SpacePerson bean) {
protected void verifyPerson(SpacePerson bean) {
assertEquals("Bubba", bean.getLastName());
assertEquals(22L, bean.getAge());
assertEquals(new java.util.Date(1221222L), bean.getBirthDate());
assertEquals(new java.sql.Timestamp(1221222L).toLocalDateTime(), bean.getBirthDate());
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
}
protected static enum MockType {ONE,TWO,THREE};
protected void verifyPerson(DatePerson bean) {
assertEquals("Bubba", bean.getLastName());
assertEquals(22L, bean.getAge());
assertEquals(new java.sql.Date(1221222L).toLocalDate(), bean.getBirthDate());
assertEquals(new BigDecimal("1234.56"), bean.getBalance());
}
protected enum MockType {ONE, TWO, THREE};
protected static class Mock {
@ -79,8 +90,7 @@ public abstract class AbstractRowMapperTests {
this(MockType.ONE);
}
public Mock(MockType type)
throws Exception {
public Mock(MockType type) throws Exception {
connection = mock(Connection.class);
statement = mock(Statement.class);
resultSet = mock(ResultSet.class);
@ -94,6 +104,8 @@ public abstract class AbstractRowMapperTests {
given(resultSet.getString(1)).willReturn("Bubba");
given(resultSet.getLong(2)).willReturn(22L);
given(resultSet.getTimestamp(3)).willReturn(new Timestamp(1221222L));
given(resultSet.getObject(anyInt(), any(Class.class))).willThrow(new SQLFeatureNotSupportedException());
given(resultSet.getDate(3)).willReturn(new java.sql.Date(1221222L));
given(resultSet.getBigDecimal(4)).willReturn(new BigDecimal("1234.56"));
given(resultSet.wasNull()).willReturn(type == MockType.TWO ? true : false);
@ -119,4 +131,5 @@ public abstract class AbstractRowMapperTests {
verify(statement).close();
}
}
}

View File

@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.test.ConcretePerson;
import org.springframework.jdbc.core.test.DatePerson;
import org.springframework.jdbc.core.test.ExtendedPerson;
import org.springframework.jdbc.core.test.Person;
import org.springframework.jdbc.core.test.SpacePerson;
@ -40,6 +41,7 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testOverridingDifferentClassDefinedForMapping() {
@ -72,7 +74,7 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class));
assertEquals(1, result.size());
verifyConcretePerson(result.get(0));
verifyPerson(result.get(0));
mock.verifyClosed();
}
@ -83,7 +85,7 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
"select name, age, birth_date, balance from people",
new BeanPropertyRowMapper<>(ConcretePerson.class, true));
assertEquals(1, result.size());
verifyConcretePerson(result.get(0));
verifyPerson(result.get(0));
mock.verifyClosed();
}
@ -95,7 +97,7 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
new BeanPropertyRowMapper<>(ExtendedPerson.class));
assertEquals(1, result.size());
ExtendedPerson bean = result.get(0);
verifyConcretePerson(bean);
verifyPerson(bean);
mock.verifyClosed();
}
@ -118,13 +120,25 @@ public class BeanPropertyRowMapperTests extends AbstractRowMapperTests {
}
@Test
public void testQueryWithSpaceInColumnName() throws Exception {
public void testQueryWithSpaceInColumnNameAndLocalDateTime() throws Exception {
Mock mock = new Mock(MockType.THREE);
List<SpacePerson> result = mock.getJdbcTemplate().query(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(SpacePerson.class));
assertEquals(1, result.size());
verifySpacePerson(result.get(0));
verifyPerson(result.get(0));
mock.verifyClosed();
}
@Test
public void testQueryWithSpaceInColumnNameAndLocalDate() throws Exception {
Mock mock = new Mock(MockType.THREE);
List<DatePerson> result = mock.getJdbcTemplate().query(
"select last_name as \"Last Name\", age, birth_date, balance from people",
new BeanPropertyRowMapper<>(DatePerson.class));
assertEquals(1, result.size());
verifyPerson(result.get(0));
mock.verifyClosed();
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.jdbc.core.test;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* @author Juergen Hoeller
*/
public class DatePerson {
private String lastName;
private long age;
private LocalDate birthDate;
private BigDecimal balance;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public long getAge() {
return age;
}
public void setAge(long age) {
this.age = age;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balanace) {
this.balance = balanace;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2016 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.
@ -17,6 +17,7 @@
package org.springframework.jdbc.core.test;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* @author Thomas Risberg
@ -27,7 +28,7 @@ public class SpacePerson {
private long age;
private java.util.Date birthDate;
private LocalDateTime birthDate;
private BigDecimal balance;
@ -47,11 +48,11 @@ public class SpacePerson {
this.age = age;
}
public java.util.Date getBirthDate() {
public LocalDateTime getBirthDate() {
return birthDate;
}
public void setBirth_date(java.util.Date birthDate) {
public void setBirthDate(LocalDateTime birthDate) {
this.birthDate = birthDate;
}