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 1644ce1596a..981d38952ab 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, Integer> javaTypeToSqlTypeMap = new HashMap, Integer>(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 @@ -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) { 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