From 90b0f451f0fd1f7e50ba43e5f068a3981d78e340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Tue, 4 Apr 2023 16:51:24 +0200 Subject: [PATCH] Add a couple missing java.time types to StatementCreatorUtils This commit adds mapping for two types from the `java.time` package, complementing the types that are already translatable to Sql types TIME, DATE and TIMESTAMP: - `OffsetTime` maps to a `TIME_WITH_TIMEZONE` - `OffsetDateTime` maps to a `TIMESTAMP_WITH_TIMEZONE` This is in accordance with the B.4 table provided in the JDBC 4.2 specification. When preparing statements, these `java.time` types use the `setObject` method. Tests covering the 5 `java.time` classes have also been added. See gh-28778 See gh-28527 Closes gh-30123 --- .../jdbc/core/StatementCreatorUtils.java | 4 ++ .../jdbc/core/StatementCreatorUtilsTests.java | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java index 9dde7c0dab10..a440fe85f06e 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java @@ -29,6 +29,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; @@ -104,6 +106,8 @@ public abstract class StatementCreatorUtils { javaTypeToSqlTypeMap.put(LocalDate.class, Types.DATE); javaTypeToSqlTypeMap.put(LocalTime.class, Types.TIME); javaTypeToSqlTypeMap.put(LocalDateTime.class, Types.TIMESTAMP); + javaTypeToSqlTypeMap.put(OffsetDateTime.class, Types.TIMESTAMP_WITH_TIMEZONE); + javaTypeToSqlTypeMap.put(OffsetTime.class, Types.TIME_WITH_TIMEZONE); javaTypeToSqlTypeMap.put(java.sql.Date.class, Types.DATE); javaTypeToSqlTypeMap.put(java.sql.Time.class, Types.TIME); javaTypeToSqlTypeMap.put(java.sql.Timestamp.class, Types.TIMESTAMP); diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/StatementCreatorUtilsTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/StatementCreatorUtilsTests.java index 1038123e335e..96d8f796e1e3 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/StatementCreatorUtilsTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/StatementCreatorUtilsTests.java @@ -22,10 +22,20 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; import java.util.GregorianCalendar; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -203,6 +213,35 @@ public void testSetParameterValueWithCalendarAndUnknownType() throws SQLExceptio verify(preparedStatement).setTimestamp(1, new java.sql.Timestamp(cal.getTime().getTime()), cal); } + + @ParameterizedTest + @MethodSource("javaTimeTypes") + public void testSetParameterValueWithJavaTimeTypes(Object o, int sqlType) throws SQLException { + StatementCreatorUtils.setParameterValue(preparedStatement, 1, sqlType, null, o); + verify(preparedStatement).setObject(1, o, sqlType); + } + + @ParameterizedTest + @MethodSource("javaTimeTypes") + void javaTimeTypesToSqlParameterType(Object o, int expectedSqlType) { + assertThat(StatementCreatorUtils.javaTypeToSqlParameterType(o.getClass())) + .isEqualTo(expectedSqlType); + } + + static Stream javaTimeTypes() { + ZoneOffset PLUS_NINE = ZoneOffset.ofHours(9); + final LocalDateTime now = LocalDateTime.now(); + return Stream.of( + Arguments.of(named("LocalTime", LocalTime.NOON), named("TIME", Types.TIME)), + Arguments.of(named("LocalDate", LocalDate.EPOCH), named("DATE", Types.DATE)), + Arguments.of(named("LocalDateTime", now), named("TIMESTAMP", Types.TIMESTAMP)), + Arguments.of(named("OffsetTime", LocalTime.NOON.atOffset(PLUS_NINE)), + named("TIME_WITH_TIMEZONE", Types.TIME_WITH_TIMEZONE)), + Arguments.of(named("OffsetDateTime", now.atOffset(PLUS_NINE)), + named("TIMESTAMP_WITH_TIMEZONE", Types.TIMESTAMP_WITH_TIMEZONE)) + ); + } + @Test // SPR-8571 public void testSetParameterValueWithStringAndVendorSpecificType() throws SQLException { Connection con = mock();