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 62eb086861b..ba90f292972 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 @@ -28,8 +28,11 @@ import java.sql.Types; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,7 +63,10 @@ public abstract class StatementCreatorUtils { private static final Log logger = LogFactory.getLog(StatementCreatorUtils.class); - private static Map javaTypeToSqlTypeMap = new HashMap(32); + static final Set driversWithNoSupportForGetParameterType = + Collections.newSetFromMap(new ConcurrentHashMap(1)); + + private static final Map, Integer> javaTypeToSqlTypeMap = new HashMap, Integer>(32); static { /* JDBC 3.0 only - not compatible with e.g. MySQL at present @@ -94,7 +100,7 @@ public abstract class StatementCreatorUtils { * @param javaType the Java type to translate * @return the corresponding SQL type, or {@code null} if none found */ - public static int javaTypeToSqlParameterType(Class javaType) { + public static int javaTypeToSqlParameterType(Class javaType) { Integer sqlType = javaTypeToSqlTypeMap.get(javaType); if (sqlType != null) { return sqlType; @@ -219,19 +225,44 @@ public abstract class StatementCreatorUtils { private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException { if (sqlType == SqlTypeValue.TYPE_UNKNOWN) { boolean useSetObject = false; - sqlType = Types.NULL; - try { - sqlType = ps.getParameterMetaData().getParameterType(paramIndex); + Integer sqlTypeToUse = null; + DatabaseMetaData dbmd = null; + String jdbcDriverName = null; + boolean checkGetParameterType = true; + if (!driversWithNoSupportForGetParameterType.isEmpty()) { + try { + dbmd = ps.getConnection().getMetaData(); + jdbcDriverName = dbmd.getDriverName(); + checkGetParameterType = !driversWithNoSupportForGetParameterType.contains(jdbcDriverName); + } + catch (Throwable ex) { + logger.debug("Could not check connection metadata", ex); + } } - catch (Throwable ex) { - if (logger.isDebugEnabled()) { - logger.debug("JDBC 3.0 getParameterType call not supported - using fallback method instead: " + ex); + if (checkGetParameterType) { + try { + sqlTypeToUse = ps.getParameterMetaData().getParameterType(paramIndex); } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("JDBC 3.0 getParameterType call not supported - using fallback method instead: " + ex); + } + } + } + if (sqlTypeToUse == null) { // JDBC driver not compliant with JDBC 3.0 -> proceed with database-specific checks + sqlTypeToUse = Types.NULL; try { - DatabaseMetaData dbmd = ps.getConnection().getMetaData(); + if (dbmd == null) { + dbmd = ps.getConnection().getMetaData(); + } + if (jdbcDriverName == null) { + jdbcDriverName = dbmd.getDriverName(); + } + if (checkGetParameterType) { + driversWithNoSupportForGetParameterType.add(jdbcDriverName); + } String databaseProductName = dbmd.getDatabaseProductName(); - String jdbcDriverName = dbmd.getDriverName(); if (databaseProductName.startsWith("Informix") || jdbcDriverName.startsWith("Microsoft SQL Server")) { useSetObject = true; @@ -240,18 +271,18 @@ public abstract class StatementCreatorUtils { jdbcDriverName.startsWith("jConnect") || jdbcDriverName.startsWith("SQLServer")|| jdbcDriverName.startsWith("Apache Derby")) { - sqlType = Types.VARCHAR; + sqlTypeToUse = Types.VARCHAR; } } - catch (Throwable ex2) { - logger.debug("Could not check database or driver name", ex2); + catch (Throwable ex) { + logger.debug("Could not check connection metadata", ex); } } if (useSetObject) { ps.setObject(paramIndex, null); } else { - ps.setNull(paramIndex, sqlType); + ps.setNull(paramIndex, sqlTypeToUse); } } else if (typeName != null) { @@ -362,7 +393,7 @@ public abstract class StatementCreatorUtils { /** * Check whether the given value can be treated as a String value. */ - private static boolean isStringValue(Class inValueType) { + private static boolean isStringValue(Class inValueType) { // Consider any CharSequence (including StringBuffer and StringBuilder) as a String. return (CharSequence.class.isAssignableFrom(inValueType) || StringWriter.class.isAssignableFrom(inValueType)); @@ -372,7 +403,7 @@ public abstract class StatementCreatorUtils { * Check whether the given value is a {@code java.util.Date} * (but not one of the JDBC-specific subclasses). */ - private static boolean isDateValue(Class inValueType) { + private static boolean isDateValue(Class inValueType) { return (java.util.Date.class.isAssignableFrom(inValueType) && !(java.sql.Date.class.isAssignableFrom(inValueType) || java.sql.Time.class.isAssignableFrom(inValueType) || @@ -386,7 +417,7 @@ public abstract class StatementCreatorUtils { * @see DisposableSqlTypeValue#cleanup() * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup() */ - public static void cleanupParameters(Object[] paramValues) { + public static void cleanupParameters(Object... paramValues) { if (paramValues != null) { cleanupParameters(Arrays.asList(paramValues)); } @@ -399,7 +430,7 @@ public abstract class StatementCreatorUtils { * @see DisposableSqlTypeValue#cleanup() * @see org.springframework.jdbc.core.support.SqlLobValue#cleanup() */ - public static void cleanupParameters(Collection paramValues) { + public static void cleanupParameters(Collection paramValues) { if (paramValues != null) { for (Object inValue : paramValues) { if (inValue instanceof DisposableSqlTypeValue) { 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 694c3c65fad..88f1e51047a 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 @@ -18,6 +18,7 @@ package org.springframework.jdbc.core; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; @@ -26,6 +27,7 @@ import java.util.GregorianCalendar; import org.junit.Before; import org.junit.Test; +import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** @@ -41,46 +43,129 @@ public class StatementCreatorUtilsTests { preparedStatement = mock(PreparedStatement.class); } - @Test public void testSetParameterValueWithNullAndType() throws SQLException { + @Test + public void testSetParameterValueWithNullAndType() throws SQLException { StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.VARCHAR, null, null); verify(preparedStatement).setNull(1, Types.VARCHAR); } - @Test public void testSetParameterValueWithNullAndTypeName() throws SQLException { + @Test + public void testSetParameterValueWithNullAndTypeName() throws SQLException { StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.VARCHAR, "mytype", null); verify(preparedStatement).setNull(1, Types.VARCHAR, "mytype"); } - @Test public void testSetParameterValueWithNullAndUnknownType() throws SQLException { + @Test + public void testSetParameterValueWithNullAndUnknownType() throws SQLException { StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); verify(preparedStatement).setNull(1, Types.NULL); } @Test public void testSetParameterValueWithNullAndUnknownTypeOnInformix() throws SQLException { + StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); Connection con = mock(Connection.class); - DatabaseMetaData metaData = mock(DatabaseMetaData.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); given(preparedStatement.getConnection()).willReturn(con); - given(con.getMetaData()).willReturn(metaData); - given(metaData.getDatabaseProductName()).willReturn("Informix Dynamic Server"); - given(metaData.getDriverName()).willReturn("Informix Driver"); + given(con.getMetaData()).willReturn(dbmd); + given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server"); + given(dbmd.getDriverName()).willReturn("Informix Driver"); StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); - verify(metaData).getDatabaseProductName(); - verify(metaData).getDriverName(); + verify(dbmd).getDatabaseProductName(); + verify(dbmd).getDriverName(); verify(preparedStatement).setObject(1, null); + assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); } - @Test public void testSetParameterValueWithNullAndUnknownTypeOnDerbyEmbedded() throws SQLException { + @Test + public void testSetParameterValueWithNullAndUnknownTypeOnDerbyEmbedded() throws SQLException { + StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); Connection con = mock(Connection.class); - DatabaseMetaData metaData = mock(DatabaseMetaData.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); given(preparedStatement.getConnection()).willReturn(con); - given(con.getMetaData()).willReturn(metaData); - given(metaData.getDatabaseProductName()).willReturn("Apache Derby"); - given(metaData.getDriverName()).willReturn("Apache Derby Embedded Driver"); + given(con.getMetaData()).willReturn(dbmd); + given(dbmd.getDatabaseProductName()).willReturn("Apache Derby"); + given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver"); StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); - verify(metaData).getDatabaseProductName(); - verify(metaData).getDriverName(); + verify(dbmd).getDatabaseProductName(); + verify(dbmd).getDriverName(); verify(preparedStatement).setNull(1, Types.VARCHAR); + assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + } + + @Test + public void testSetParameterValueWithNullAndGetParameterTypeWorking() throws SQLException { + StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); + ParameterMetaData pmd = mock(ParameterMetaData.class); + given(preparedStatement.getParameterMetaData()).willReturn(pmd); + given(pmd.getParameterType(1)).willReturn(Types.SMALLINT); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); + verify(pmd).getParameterType(1); + verify(preparedStatement, never()).getConnection(); + verify(preparedStatement).setNull(1, Types.SMALLINT); + assertTrue(StatementCreatorUtils.driversWithNoSupportForGetParameterType.isEmpty()); + } + + @Test + public void testSetParameterValueWithNullAndGetParameterTypeWorkingButNotForOtherDriver() throws SQLException { + StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); + StatementCreatorUtils.driversWithNoSupportForGetParameterType.add("Oracle JDBC Driver"); + Connection con = mock(Connection.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); + ParameterMetaData pmd = mock(ParameterMetaData.class); + given(preparedStatement.getConnection()).willReturn(con); + given(con.getMetaData()).willReturn(dbmd); + given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver"); + given(preparedStatement.getParameterMetaData()).willReturn(pmd); + given(pmd.getParameterType(1)).willReturn(Types.SMALLINT); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); + verify(dbmd).getDriverName(); + verify(pmd).getParameterType(1); + verify(preparedStatement).setNull(1, Types.SMALLINT); + assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + } + + @Test + public void testSetParameterValueWithNullAndUnknownTypeAndGetParameterTypeNotWorking() throws SQLException { + StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); + Connection con = mock(Connection.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); + given(preparedStatement.getConnection()).willReturn(con); + given(con.getMetaData()).willReturn(dbmd); + given(dbmd.getDatabaseProductName()).willReturn("Apache Derby"); + given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver"); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); + verify(dbmd).getDatabaseProductName(); + verify(dbmd).getDriverName(); + verify(preparedStatement).setNull(1, Types.VARCHAR); + assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + + reset(preparedStatement, con, dbmd); + ParameterMetaData pmd = mock(ParameterMetaData.class); + given(preparedStatement.getConnection()).willReturn(con); + given(con.getMetaData()).willReturn(dbmd); + given(preparedStatement.getParameterMetaData()).willReturn(pmd); + given(pmd.getParameterType(1)).willThrow(new SQLException("unsupported")); + given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server"); + given(dbmd.getDriverName()).willReturn("Informix Driver"); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); + verify(pmd).getParameterType(1); + verify(dbmd).getDatabaseProductName(); + verify(dbmd).getDriverName(); + verify(preparedStatement).setObject(1, null); + assertEquals(2, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + + reset(preparedStatement, con, dbmd, pmd); + given(preparedStatement.getConnection()).willReturn(con); + given(con.getMetaData()).willReturn(dbmd); + given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server"); + given(dbmd.getDriverName()).willReturn("Informix Driver"); + StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); + verify(preparedStatement, never()).getParameterMetaData(); + verify(dbmd).getDatabaseProductName(); + verify(dbmd).getDriverName(); + verify(preparedStatement).setObject(1, null); + assertEquals(2, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); } @Test