From 5ac7a32c8aa50626a13abee37f8f2f2b087f91cf Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 28 Jul 2016 10:51:29 +0200 Subject: [PATCH] Raise JDBC driver feature baseline to JDBC 4.0+ Issue: SPR-13826 --- .../jdbc/core/StatementCreatorUtils.java | 93 +++++-------------- .../jdbc/core/StatementCreatorUtilsTests.java | 91 +++++------------- 2 files changed, 42 insertions(+), 142 deletions(-) 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 1877301eb27..91f593bc70a 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,16 +25,12 @@ 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.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; @@ -79,10 +75,7 @@ public abstract class StatementCreatorUtils { public static final String IGNORE_GETPARAMETERTYPE_PROPERTY_NAME = "spring.jdbc.getParameterType.ignore"; - static final boolean shouldIgnoreGetParameterType = SpringProperties.getFlag(IGNORE_GETPARAMETERTYPE_PROPERTY_NAME); - - static final Set driversWithNoSupportForGetParameterType = - Collections.newSetFromMap(new ConcurrentHashMap<>(1)); + static boolean shouldIgnoreGetParameterType = SpringProperties.getFlag(IGNORE_GETPARAMETERTYPE_PROPERTY_NAME); private static final Log logger = LogFactory.getLog(StatementCreatorUtils.class); @@ -244,61 +237,25 @@ public abstract class StatementCreatorUtils { if (sqlType == SqlTypeValue.TYPE_UNKNOWN || sqlType == Types.OTHER) { boolean useSetObject = false; Integer sqlTypeToUse = null; - DatabaseMetaData dbmd = null; - String jdbcDriverName = null; - boolean checkGetParameterType = !shouldIgnoreGetParameterType; - if (checkGetParameterType && !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); - } - } - 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 (!shouldIgnoreGetParameterType) { + sqlTypeToUse = ps.getParameterMetaData().getParameterType(paramIndex); } if (sqlTypeToUse == null) { - // JDBC driver not compliant with JDBC 3.0 -> proceed with database-specific checks + // Proceed with database-specific checks sqlTypeToUse = Types.NULL; - try { - if (dbmd == null) { - dbmd = ps.getConnection().getMetaData(); - } - if (jdbcDriverName == null) { - jdbcDriverName = dbmd.getDriverName(); - } - if (checkGetParameterType && - !(jdbcDriverName.startsWith("Oracle") && dbmd.getDriverMajorVersion() >= 12)) { - // Register JDBC driver with no support for getParameterType, except for the - // Oracle 12c driver where getParameterType fails for specific statements only - // (so an exception thrown above does not indicate general lack of support). - driversWithNoSupportForGetParameterType.add(jdbcDriverName); - } - String databaseProductName = dbmd.getDatabaseProductName(); - if (databaseProductName.startsWith("Informix") || - (jdbcDriverName.startsWith("Microsoft") && jdbcDriverName.contains("SQL Server"))) { - // "Microsoft SQL Server JDBC Driver 3.0" versus "Microsoft JDBC Driver 4.0 for SQL Server" - useSetObject = true; - } - else if (databaseProductName.startsWith("DB2") || - jdbcDriverName.startsWith("jConnect") || - jdbcDriverName.startsWith("SQLServer")|| - jdbcDriverName.startsWith("Apache Derby")) { - sqlTypeToUse = Types.VARCHAR; - } + DatabaseMetaData dbmd = ps.getConnection().getMetaData(); + String jdbcDriverName = dbmd.getDriverName(); + String databaseProductName = dbmd.getDatabaseProductName(); + if (databaseProductName.startsWith("Informix") || + (jdbcDriverName.startsWith("Microsoft") && jdbcDriverName.contains("SQL Server"))) { + // "Microsoft SQL Server JDBC Driver 3.0" versus "Microsoft JDBC Driver 4.0 for SQL Server" + useSetObject = true; } - catch (Throwable ex) { - logger.debug("Could not check connection metadata", ex); + else if (databaseProductName.startsWith("DB2") || + jdbcDriverName.startsWith("jConnect") || + jdbcDriverName.startsWith("SQLServer")|| + jdbcDriverName.startsWith("Apache Derby")) { + sqlTypeToUse = Types.VARCHAR; } } if (useSetObject) { @@ -334,21 +291,13 @@ public abstract class StatementCreatorUtils { if (strVal.length() > 4000) { // Necessary for older Oracle drivers, in particular when running against an Oracle 10 database. // Should also work fine against other drivers/databases since it uses standard JDBC 4.0 API. - try { - if (sqlType == Types.NCLOB) { - ps.setNClob(paramIndex, new StringReader(strVal), strVal.length()); - } - else { - ps.setClob(paramIndex, new StringReader(strVal), strVal.length()); - } - return; + if (sqlType == Types.NCLOB) { + ps.setNClob(paramIndex, new StringReader(strVal), strVal.length()); } - catch (AbstractMethodError err) { - logger.debug("JDBC driver does not implement JDBC 4.0 'setClob(int, Reader, long)' method", err); - } - catch (SQLFeatureNotSupportedException ex) { - logger.debug("JDBC driver does not support JDBC 4.0 'setClob(int, Reader, long)' method", ex); + else { + ps.setClob(paramIndex, new StringReader(strVal), strVal.length()); } + return; } // Fallback: regular setString binding ps.setString(paramIndex, strVal); 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 1c7cb2e39e8..d8f5389e914 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -27,7 +27,6 @@ import java.util.GregorianCalendar; import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** @@ -59,13 +58,21 @@ public class StatementCreatorUtilsTests { @Test public void testSetParameterValueWithNullAndUnknownType() throws SQLException { + StatementCreatorUtils.shouldIgnoreGetParameterType = true; + Connection con = mock(Connection.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); + given(preparedStatement.getConnection()).willReturn(con); + given(dbmd.getDatabaseProductName()).willReturn("Oracle"); + given(dbmd.getDriverName()).willReturn("Oracle Driver"); + given(con.getMetaData()).willReturn(dbmd); StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null); verify(preparedStatement).setNull(1, Types.NULL); + StatementCreatorUtils.shouldIgnoreGetParameterType = false; } @Test public void testSetParameterValueWithNullAndUnknownTypeOnInformix() throws SQLException { - StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); + StatementCreatorUtils.shouldIgnoreGetParameterType = true; Connection con = mock(Connection.class); DatabaseMetaData dbmd = mock(DatabaseMetaData.class); given(preparedStatement.getConnection()).willReturn(con); @@ -76,12 +83,12 @@ public class StatementCreatorUtilsTests { verify(dbmd).getDatabaseProductName(); verify(dbmd).getDriverName(); verify(preparedStatement).setObject(1, null); - assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + StatementCreatorUtils.shouldIgnoreGetParameterType = false; } @Test public void testSetParameterValueWithNullAndUnknownTypeOnDerbyEmbedded() throws SQLException { - StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear(); + StatementCreatorUtils.shouldIgnoreGetParameterType = true; Connection con = mock(Connection.class); DatabaseMetaData dbmd = mock(DatabaseMetaData.class); given(preparedStatement.getConnection()).willReturn(con); @@ -92,12 +99,11 @@ public class StatementCreatorUtilsTests { verify(dbmd).getDatabaseProductName(); verify(dbmd).getDriverName(); verify(preparedStatement).setNull(1, Types.VARCHAR); - assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size()); + StatementCreatorUtils.shouldIgnoreGetParameterType = false; } @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); @@ -105,69 +111,6 @@ public class StatementCreatorUtilsTests { 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 @@ -277,8 +220,16 @@ public class StatementCreatorUtilsTests { @Test // SPR-8571 public void testSetParameterValueWithNullAndVendorSpecificType() throws SQLException { + StatementCreatorUtils.shouldIgnoreGetParameterType = true; + Connection con = mock(Connection.class); + DatabaseMetaData dbmd = mock(DatabaseMetaData.class); + given(preparedStatement.getConnection()).willReturn(con); + given(dbmd.getDatabaseProductName()).willReturn("Oracle"); + given(dbmd.getDriverName()).willReturn("Oracle Driver"); + given(con.getMetaData()).willReturn(dbmd); StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.OTHER, null, null); verify(preparedStatement).setNull(1, Types.NULL); + StatementCreatorUtils.shouldIgnoreGetParameterType = false; } }