From 37679384e82554250c87918ad042e7cfb3825813 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 10 Apr 2014 15:51:44 +0200 Subject: [PATCH] JdbcUtils uses JDBC 4.1 getObject(int, Class) for unknown ResultSet value types Comes with general JDBC 3.0+ baseline upgrade, removing defensive measures. Issue: SPR-11600 --- .../core/PreparedStatementCreatorFactory.java | 18 ++--- .../jdbc/core/StatementCreatorUtils.java | 6 +- .../JdbcTransactionObjectSupport.java | 14 +--- .../jdbc/support/JdbcUtils.java | 71 +++++++++---------- .../jdbc/core/JdbcTemplateQueryTests.java | 6 +- 5 files changed, 48 insertions(+), 67 deletions(-) diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java index 95ae0d9c191..e5474fd6a09 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -30,7 +30,6 @@ import java.util.List; import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor; import org.springframework.util.Assert; @@ -228,18 +227,11 @@ public class PreparedStatementCreatorFactory { public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps; if (generatedKeysColumnNames != null || returnGeneratedKeys) { - try { - if (generatedKeysColumnNames != null) { - ps = con.prepareStatement(this.actualSql, generatedKeysColumnNames); - } - else { - ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS); - } + if (generatedKeysColumnNames != null) { + ps = con.prepareStatement(this.actualSql, generatedKeysColumnNames); } - catch (AbstractMethodError err) { - throw new InvalidDataAccessResourceUsageException( - "Your JDBC driver is not compliant with JDBC 3.0 - " + - "it does not support retrieval of auto-generated keys", err); + else { + ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS); } } else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) { 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 1c3ada01378..496531df3f7 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 @@ -87,10 +87,8 @@ public abstract class StatementCreatorUtils { private static final Map, Integer> javaTypeToSqlTypeMap = new HashMap, Integer>(32); static { - /* JDBC 3.0 only - not compatible with e.g. MySQL at present - javaTypeToSqlTypeMap.put(boolean.class, new Integer(Types.BOOLEAN)); - javaTypeToSqlTypeMap.put(Boolean.class, new Integer(Types.BOOLEAN)); - */ + javaTypeToSqlTypeMap.put(boolean.class, Types.BOOLEAN); + javaTypeToSqlTypeMap.put(Boolean.class, Types.BOOLEAN); javaTypeToSqlTypeMap.put(byte.class, Types.TINYINT); javaTypeToSqlTypeMap.put(Byte.class, Types.TINYINT); javaTypeToSqlTypeMap.put(short.class, Types.SMALLINT); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java index 18c6572883b..31f0fd1452a 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/JdbcTransactionObjectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -16,6 +16,7 @@ package org.springframework.jdbc.datasource; +import java.sql.SQLException; import java.sql.Savepoint; import org.apache.commons.logging.Log; @@ -41,9 +42,6 @@ import org.springframework.transaction.support.SmartTransactionObject; * will automatically delegate to this, as it autodetects transaction * objects that implement the SavepointManager interface. * - *

Note that savepoints are only supported for drivers which - * support JDBC 3.0 or higher. - * * @author Juergen Hoeller * @since 1.1 */ @@ -109,15 +107,9 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager, throw new NestedTransactionNotSupportedException( "Cannot create a nested transaction because savepoints are not supported by your JDBC driver"); } - } - catch (Throwable ex) { - throw new NestedTransactionNotSupportedException( - "Cannot create a nested transaction because your JDBC driver is not a JDBC 3.0 driver", ex); - } - try { return conHolder.createSavepoint(); } - catch (Throwable ex) { + catch (SQLException ex) { throw new CannotCreateTransactionException("Could not create JDBC savepoint", ex); } } 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 ea6be1ab010..30fc01625d5 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -26,9 +26,9 @@ import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.sql.Types; - import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -36,6 +36,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.jdbc.datasource.DataSourceUtils; +import org.springframework.util.ClassUtils; /** * Generic utility methods for working with JDBC. Mainly for internal use @@ -53,6 +54,10 @@ public abstract class JdbcUtils { public static final int TYPE_UNKNOWN = Integer.MIN_VALUE; + // Check for JDBC 4.1 getObject(int, Class) method - available on JDK 7 and higher + private static final boolean getObjectWithTypeAvailable = + ClassUtils.hasMethod(ResultSet.class, "getObject", int.class, Class.class); + private static final Log logger = LogFactory.getLog(JdbcUtils.class); @@ -134,74 +139,74 @@ public abstract class JdbcUtils { return getResultSetValue(rs, index); } - Object value = null; - boolean wasNullCheck = false; + Object value; // Explicitly extract typed value, as far as possible. if (String.class.equals(requiredType)) { - value = rs.getString(index); + return rs.getString(index); } else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) { value = rs.getBoolean(index); - wasNullCheck = true; } else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) { value = rs.getByte(index); - wasNullCheck = true; } else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) { value = rs.getShort(index); - wasNullCheck = true; } else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) { value = rs.getInt(index); - wasNullCheck = true; } else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) { value = rs.getLong(index); - wasNullCheck = true; } else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) { value = rs.getFloat(index); - wasNullCheck = true; } else if (double.class.equals(requiredType) || Double.class.equals(requiredType) || Number.class.equals(requiredType)) { value = rs.getDouble(index); - wasNullCheck = true; } - else if (byte[].class.equals(requiredType)) { - value = rs.getBytes(index); + else if (BigDecimal.class.equals(requiredType)) { + return rs.getBigDecimal(index); } else if (java.sql.Date.class.equals(requiredType)) { - value = rs.getDate(index); + return rs.getDate(index); } else if (java.sql.Time.class.equals(requiredType)) { - value = rs.getTime(index); + return rs.getTime(index); } else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) { - value = rs.getTimestamp(index); + return rs.getTimestamp(index); } - else if (BigDecimal.class.equals(requiredType)) { - value = rs.getBigDecimal(index); + else if (byte[].class.equals(requiredType)) { + return rs.getBytes(index); } else if (Blob.class.equals(requiredType)) { - value = rs.getBlob(index); + return rs.getBlob(index); } else if (Clob.class.equals(requiredType)) { - value = rs.getClob(index); + return rs.getClob(index); } else { // Some unknown type desired -> rely on getObject. - value = getResultSetValue(rs, index); + if (getObjectWithTypeAvailable) { + try { + return rs.getObject(index, requiredType); + } + catch (SQLFeatureNotSupportedException ex) { + logger.debug("JDBC driver does not support JDBC 4.1 'getObject(int, Class)' method", ex); + } + catch (AbstractMethodError err) { + logger.debug("JDBC driver does not implement JDBC 4.1 'getObject(int, Class)' method", err); + } + } + // Fall back to getObject without type specification... + return getResultSetValue(rs, index); } - // Perform was-null check if demanded (for results that the - // JDBC driver returns as primitives). - if (wasNullCheck && value != null && rs.wasNull()) { - value = null; - } - return value; + // Perform was-null check if necessary (for results that the JDBC driver returns as primitives). + return (rs.wasNull() ? null : value); } /** @@ -234,15 +239,12 @@ public abstract class JdbcUtils { else if (obj instanceof Clob) { obj = rs.getString(index); } - else if (className != null && - ("oracle.sql.TIMESTAMP".equals(className) || - "oracle.sql.TIMESTAMPTZ".equals(className))) { + else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) { obj = rs.getTimestamp(index); } else if (className != null && className.startsWith("oracle.sql.DATE")) { String metaDataClassName = rs.getMetaData().getColumnClassName(index); - if ("java.sql.Timestamp".equals(metaDataClassName) || - "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { + if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { obj = rs.getTimestamp(index); } else { @@ -371,9 +373,6 @@ public abstract class JdbcUtils { catch (SQLException ex) { logger.debug("JDBC driver 'supportsBatchUpdates' method threw exception", ex); } - catch (AbstractMethodError err) { - logger.debug("JDBC driver does not support JDBC 2.0 'supportsBatchUpdates' method", err); - } return false; } diff --git a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java index f9963c11233..a0f09abcec8 100644 --- a/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java +++ b/spring-jdbc/src/test/java/org/springframework/jdbc/core/JdbcTemplateQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -26,13 +26,13 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; import java.util.Map; - import javax.sql.DataSource; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.dao.IncorrectResultSizeDataAccessException; import static org.junit.Assert.*; @@ -177,7 +177,7 @@ public class JdbcTemplateQueryTests { public void testQueryForObjectWithBigInteger() throws Exception { String sql = "SELECT AGE FROM CUSTMR WHERE ID = 3"; given(this.resultSet.next()).willReturn(true, false); - given(this.resultSet.getObject(1)).willReturn("22"); + given(this.resultSet.getObject(1, BigInteger.class)).willReturn(new BigInteger("22")); assertEquals(new BigInteger("22"), this.template.queryForObject(sql, BigInteger.class)); verify(this.resultSet).close(); verify(this.statement).close();