From a0fee4657daf22ccdb55fdb1f2731cfcd7001bdd Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 22 Nov 2016 14:52:21 +0100 Subject: [PATCH] JdbcUtils explicitly extracts SQL date/time for JSR-310 LocalDate/Time Issue: SPR-14898 --- .../jdbc/support/JdbcUtils.java | 24 ++++++- .../jdbc/core/AbstractRowMapperTests.java | 27 ++++++-- .../jdbc/core/BeanPropertyRowMapperTests.java | 24 +++++-- .../jdbc/core/test/DatePerson.java | 67 +++++++++++++++++++ .../jdbc/core/test/SpacePerson.java | 11 +-- 5 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 spring-jdbc/src/test/java/org/springframework/jdbc/core/test/DatePerson.java diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java index dc64a2cae13..a8ffad62c67 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/support/JdbcUtils.java @@ -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); } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java index e6e2121c8d1..2ad750cfea1 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/AbstractRowMapperTests.java @@ -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(); } } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java index 078350629ad..90478f66e6b 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/BeanPropertyRowMapperTests.java @@ -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 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 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(); + } + } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/DatePerson.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/DatePerson.java new file mode 100644 index 00000000000..d465ea4ab62 --- /dev/null +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/DatePerson.java @@ -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; + } + +} diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java index b6ad9f9da3b..bd2c2eb77cf 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/test/SpacePerson.java @@ -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; } @@ -63,4 +64,4 @@ public class SpacePerson { this.balance = balanace; } -} \ No newline at end of file +}