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 a440fe85f06..503a64f93e2 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 @@ -25,6 +25,7 @@ import java.sql.Clob; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; @@ -84,7 +85,7 @@ public abstract class StatementCreatorUtils { private static final Log logger = LogFactory.getLog(StatementCreatorUtils.class); - private static final Map, Integer> javaTypeToSqlTypeMap = new HashMap<>(32); + private static final Map, Integer> javaTypeToSqlTypeMap = new HashMap<>(64); static { javaTypeToSqlTypeMap.put(boolean.class, Types.BOOLEAN); @@ -106,8 +107,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(OffsetDateTime.class, Types.TIMESTAMP_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); @@ -290,7 +291,19 @@ public abstract class StatementCreatorUtils { ps.setNull(paramIndex, sqlType, typeName); } else { - ps.setNull(paramIndex, sqlType); + // Fall back to generic setNull call. + try { + // Try generic setNull call with SQL type specified. + ps.setNull(paramIndex, sqlType); + } + catch (SQLFeatureNotSupportedException ex) { + if (sqlType == Types.NULL) { + throw ex; + } + // Fall back to generic setNull call without SQL type specified + // (e.g. for MySQL TIME_WITH_TIMEZONE / TIMESTAMP_WITH_TIMEZONE). + ps.setNull(paramIndex, Types.NULL); + } } } @@ -415,8 +428,16 @@ public abstract class StatementCreatorUtils { } } else { - // Fall back to generic setObject call with SQL type specified. - ps.setObject(paramIndex, inValue, sqlType); + // Fall back to generic setObject call. + try { + // Try generic setObject call with SQL type specified. + ps.setObject(paramIndex, inValue, sqlType); + } + catch (SQLFeatureNotSupportedException ex) { + // Fall back to generic setObject call without SQL type specified + // (e.g. for MySQL TIME_WITH_TIMEZONE / TIMESTAMP_WITH_TIMEZONE). + ps.setObject(paramIndex, inValue); + } } } 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 96d8f796e1e..a234449b826 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 @@ -21,10 +21,12 @@ import java.sql.DatabaseMetaData; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.GregorianCalendar; import java.util.stream.Stream; @@ -36,6 +38,7 @@ 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.doThrow; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -213,7 +216,6 @@ public class StatementCreatorUtilsTests { verify(preparedStatement).setTimestamp(1, new java.sql.Timestamp(cal.getTime().getTime()), cal); } - @ParameterizedTest @MethodSource("javaTimeTypes") public void testSetParameterValueWithJavaTimeTypes(Object o, int sqlType) throws SQLException { @@ -242,6 +244,23 @@ public class StatementCreatorUtilsTests { ); } + @Test // gh-30556 + public void testSetParameterValueWithOffsetDateTimeAndNotSupported() throws SQLException { + OffsetDateTime time = OffsetDateTime.now(); + doThrow(new SQLFeatureNotSupportedException()).when(preparedStatement).setObject(1, time, Types.TIMESTAMP_WITH_TIMEZONE); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.TIMESTAMP_WITH_TIMEZONE, null, time); + verify(preparedStatement).setObject(1, time, Types.TIMESTAMP_WITH_TIMEZONE); + verify(preparedStatement).setObject(1, time); + } + + @Test // gh-30556 + public void testSetParameterValueWithNullAndNotSupported() throws SQLException { + doThrow(new SQLFeatureNotSupportedException()).when(preparedStatement).setNull(1, Types.TIMESTAMP_WITH_TIMEZONE); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.TIMESTAMP_WITH_TIMEZONE, null, null); + verify(preparedStatement).setNull(1, Types.TIMESTAMP_WITH_TIMEZONE); + verify(preparedStatement).setNull(1, Types.NULL); + } + @Test // SPR-8571 public void testSetParameterValueWithStringAndVendorSpecificType() throws SQLException { Connection con = mock();