Skip to content

Commit 2e15f94

Browse files
committed
Optimized use of JDBC 3.0 ParameterMetaData.getParameterType, caching information about drivers which do not support that feature
Issue: SPR-11100 (cherry picked from commit 4c8a789)
1 parent d0fc38e commit 2e15f94

File tree

2 files changed

+150
-34
lines changed

2 files changed

+150
-34
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@
2828
import java.util.Arrays;
2929
import java.util.Calendar;
3030
import java.util.Collection;
31+
import java.util.Collections;
3132
import java.util.HashMap;
3233
import java.util.Map;
34+
import java.util.Set;
35+
import java.util.concurrent.ConcurrentHashMap;
3336

3437
import org.apache.commons.logging.Log;
3538
import org.apache.commons.logging.LogFactory;
@@ -60,7 +63,10 @@ public abstract class StatementCreatorUtils {
6063

6164
private static final Log logger = LogFactory.getLog(StatementCreatorUtils.class);
6265

63-
private static Map<Class, Integer> javaTypeToSqlTypeMap = new HashMap<Class, Integer>(32);
66+
static final Set<String> driversWithNoSupportForGetParameterType =
67+
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(1));
68+
69+
private static final Map<Class<?>, Integer> javaTypeToSqlTypeMap = new HashMap<Class<?>, Integer>(32);
6470

6571
static {
6672
/* JDBC 3.0 only - not compatible with e.g. MySQL at present
@@ -94,7 +100,7 @@ public abstract class StatementCreatorUtils {
94100
* @param javaType the Java type to translate
95101
* @return the corresponding SQL type, or {@code null} if none found
96102
*/
97-
public static int javaTypeToSqlParameterType(Class javaType) {
103+
public static int javaTypeToSqlParameterType(Class<?> javaType) {
98104
Integer sqlType = javaTypeToSqlTypeMap.get(javaType);
99105
if (sqlType != null) {
100106
return sqlType;
@@ -219,19 +225,44 @@ private static void setParameterValueInternal(PreparedStatement ps, int paramInd
219225
private static void setNull(PreparedStatement ps, int paramIndex, int sqlType, String typeName) throws SQLException {
220226
if (sqlType == SqlTypeValue.TYPE_UNKNOWN) {
221227
boolean useSetObject = false;
222-
sqlType = Types.NULL;
223-
try {
224-
sqlType = ps.getParameterMetaData().getParameterType(paramIndex);
228+
Integer sqlTypeToUse = null;
229+
DatabaseMetaData dbmd = null;
230+
String jdbcDriverName = null;
231+
boolean checkGetParameterType = true;
232+
if (!driversWithNoSupportForGetParameterType.isEmpty()) {
233+
try {
234+
dbmd = ps.getConnection().getMetaData();
235+
jdbcDriverName = dbmd.getDriverName();
236+
checkGetParameterType = !driversWithNoSupportForGetParameterType.contains(jdbcDriverName);
237+
}
238+
catch (Throwable ex) {
239+
logger.debug("Could not check connection metadata", ex);
240+
}
225241
}
226-
catch (Throwable ex) {
227-
if (logger.isDebugEnabled()) {
228-
logger.debug("JDBC 3.0 getParameterType call not supported - using fallback method instead: " + ex);
242+
if (checkGetParameterType) {
243+
try {
244+
sqlTypeToUse = ps.getParameterMetaData().getParameterType(paramIndex);
229245
}
246+
catch (Throwable ex) {
247+
if (logger.isDebugEnabled()) {
248+
logger.debug("JDBC 3.0 getParameterType call not supported - using fallback method instead: " + ex);
249+
}
250+
}
251+
}
252+
if (sqlTypeToUse == null) {
230253
// JDBC driver not compliant with JDBC 3.0 -> proceed with database-specific checks
254+
sqlTypeToUse = Types.NULL;
231255
try {
232-
DatabaseMetaData dbmd = ps.getConnection().getMetaData();
256+
if (dbmd == null) {
257+
dbmd = ps.getConnection().getMetaData();
258+
}
259+
if (jdbcDriverName == null) {
260+
jdbcDriverName = dbmd.getDriverName();
261+
}
262+
if (checkGetParameterType) {
263+
driversWithNoSupportForGetParameterType.add(jdbcDriverName);
264+
}
233265
String databaseProductName = dbmd.getDatabaseProductName();
234-
String jdbcDriverName = dbmd.getDriverName();
235266
if (databaseProductName.startsWith("Informix") ||
236267
jdbcDriverName.startsWith("Microsoft SQL Server")) {
237268
useSetObject = true;
@@ -240,18 +271,18 @@ else if (databaseProductName.startsWith("DB2") ||
240271
jdbcDriverName.startsWith("jConnect") ||
241272
jdbcDriverName.startsWith("SQLServer")||
242273
jdbcDriverName.startsWith("Apache Derby")) {
243-
sqlType = Types.VARCHAR;
274+
sqlTypeToUse = Types.VARCHAR;
244275
}
245276
}
246-
catch (Throwable ex2) {
247-
logger.debug("Could not check database or driver name", ex2);
277+
catch (Throwable ex) {
278+
logger.debug("Could not check connection metadata", ex);
248279
}
249280
}
250281
if (useSetObject) {
251282
ps.setObject(paramIndex, null);
252283
}
253284
else {
254-
ps.setNull(paramIndex, sqlType);
285+
ps.setNull(paramIndex, sqlTypeToUse);
255286
}
256287
}
257288
else if (typeName != null) {
@@ -362,7 +393,7 @@ else if (inValue instanceof Calendar) {
362393
/**
363394
* Check whether the given value can be treated as a String value.
364395
*/
365-
private static boolean isStringValue(Class inValueType) {
396+
private static boolean isStringValue(Class<?> inValueType) {
366397
// Consider any CharSequence (including StringBuffer and StringBuilder) as a String.
367398
return (CharSequence.class.isAssignableFrom(inValueType) ||
368399
StringWriter.class.isAssignableFrom(inValueType));
@@ -372,7 +403,7 @@ private static boolean isStringValue(Class inValueType) {
372403
* Check whether the given value is a {@code java.util.Date}
373404
* (but not one of the JDBC-specific subclasses).
374405
*/
375-
private static boolean isDateValue(Class inValueType) {
406+
private static boolean isDateValue(Class<?> inValueType) {
376407
return (java.util.Date.class.isAssignableFrom(inValueType) &&
377408
!(java.sql.Date.class.isAssignableFrom(inValueType) ||
378409
java.sql.Time.class.isAssignableFrom(inValueType) ||
@@ -386,7 +417,7 @@ private static boolean isDateValue(Class inValueType) {
386417
* @see DisposableSqlTypeValue#cleanup()
387418
* @see org.springframework.jdbc.core.support.SqlLobValue#cleanup()
388419
*/
389-
public static void cleanupParameters(Object[] paramValues) {
420+
public static void cleanupParameters(Object... paramValues) {
390421
if (paramValues != null) {
391422
cleanupParameters(Arrays.asList(paramValues));
392423
}
@@ -399,7 +430,7 @@ public static void cleanupParameters(Object[] paramValues) {
399430
* @see DisposableSqlTypeValue#cleanup()
400431
* @see org.springframework.jdbc.core.support.SqlLobValue#cleanup()
401432
*/
402-
public static void cleanupParameters(Collection paramValues) {
433+
public static void cleanupParameters(Collection<?> paramValues) {
403434
if (paramValues != null) {
404435
for (Object inValue : paramValues) {
405436
if (inValue instanceof DisposableSqlTypeValue) {

spring-jdbc/src/test/java/org/springframework/jdbc/core/StatementCreatorUtilsTests.java

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.sql.Connection;
2020
import java.sql.DatabaseMetaData;
21+
import java.sql.ParameterMetaData;
2122
import java.sql.PreparedStatement;
2223
import java.sql.SQLException;
2324
import java.sql.Types;
@@ -26,6 +27,7 @@
2627
import org.junit.Before;
2728
import org.junit.Test;
2829

30+
import static org.junit.Assert.*;
2931
import static org.mockito.BDDMockito.*;
3032

3133
/**
@@ -41,46 +43,129 @@ public void setUp() {
4143
preparedStatement = mock(PreparedStatement.class);
4244
}
4345

44-
@Test public void testSetParameterValueWithNullAndType() throws SQLException {
46+
@Test
47+
public void testSetParameterValueWithNullAndType() throws SQLException {
4548
StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.VARCHAR, null, null);
4649
verify(preparedStatement).setNull(1, Types.VARCHAR);
4750
}
4851

49-
@Test public void testSetParameterValueWithNullAndTypeName() throws SQLException {
52+
@Test
53+
public void testSetParameterValueWithNullAndTypeName() throws SQLException {
5054
StatementCreatorUtils.setParameterValue(preparedStatement, 1, Types.VARCHAR, "mytype", null);
5155
verify(preparedStatement).setNull(1, Types.VARCHAR, "mytype");
5256
}
5357

54-
@Test public void testSetParameterValueWithNullAndUnknownType() throws SQLException {
58+
@Test
59+
public void testSetParameterValueWithNullAndUnknownType() throws SQLException {
5560
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
5661
verify(preparedStatement).setNull(1, Types.NULL);
5762
}
5863

5964
@Test
6065
public void testSetParameterValueWithNullAndUnknownTypeOnInformix() throws SQLException {
66+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear();
6167
Connection con = mock(Connection.class);
62-
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
68+
DatabaseMetaData dbmd = mock(DatabaseMetaData.class);
6369
given(preparedStatement.getConnection()).willReturn(con);
64-
given(con.getMetaData()).willReturn(metaData);
65-
given(metaData.getDatabaseProductName()).willReturn("Informix Dynamic Server");
66-
given(metaData.getDriverName()).willReturn("Informix Driver");
70+
given(con.getMetaData()).willReturn(dbmd);
71+
given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server");
72+
given(dbmd.getDriverName()).willReturn("Informix Driver");
6773
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
68-
verify(metaData).getDatabaseProductName();
69-
verify(metaData).getDriverName();
74+
verify(dbmd).getDatabaseProductName();
75+
verify(dbmd).getDriverName();
7076
verify(preparedStatement).setObject(1, null);
77+
assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
7178
}
7279

73-
@Test public void testSetParameterValueWithNullAndUnknownTypeOnDerbyEmbedded() throws SQLException {
80+
@Test
81+
public void testSetParameterValueWithNullAndUnknownTypeOnDerbyEmbedded() throws SQLException {
82+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear();
7483
Connection con = mock(Connection.class);
75-
DatabaseMetaData metaData = mock(DatabaseMetaData.class);
84+
DatabaseMetaData dbmd = mock(DatabaseMetaData.class);
7685
given(preparedStatement.getConnection()).willReturn(con);
77-
given(con.getMetaData()).willReturn(metaData);
78-
given(metaData.getDatabaseProductName()).willReturn("Apache Derby");
79-
given(metaData.getDriverName()).willReturn("Apache Derby Embedded Driver");
86+
given(con.getMetaData()).willReturn(dbmd);
87+
given(dbmd.getDatabaseProductName()).willReturn("Apache Derby");
88+
given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver");
8089
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
81-
verify(metaData).getDatabaseProductName();
82-
verify(metaData).getDriverName();
90+
verify(dbmd).getDatabaseProductName();
91+
verify(dbmd).getDriverName();
8392
verify(preparedStatement).setNull(1, Types.VARCHAR);
93+
assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
94+
}
95+
96+
@Test
97+
public void testSetParameterValueWithNullAndGetParameterTypeWorking() throws SQLException {
98+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear();
99+
ParameterMetaData pmd = mock(ParameterMetaData.class);
100+
given(preparedStatement.getParameterMetaData()).willReturn(pmd);
101+
given(pmd.getParameterType(1)).willReturn(Types.SMALLINT);
102+
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
103+
verify(pmd).getParameterType(1);
104+
verify(preparedStatement, never()).getConnection();
105+
verify(preparedStatement).setNull(1, Types.SMALLINT);
106+
assertTrue(StatementCreatorUtils.driversWithNoSupportForGetParameterType.isEmpty());
107+
}
108+
109+
@Test
110+
public void testSetParameterValueWithNullAndGetParameterTypeWorkingButNotForOtherDriver() throws SQLException {
111+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear();
112+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.add("Oracle JDBC Driver");
113+
Connection con = mock(Connection.class);
114+
DatabaseMetaData dbmd = mock(DatabaseMetaData.class);
115+
ParameterMetaData pmd = mock(ParameterMetaData.class);
116+
given(preparedStatement.getConnection()).willReturn(con);
117+
given(con.getMetaData()).willReturn(dbmd);
118+
given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver");
119+
given(preparedStatement.getParameterMetaData()).willReturn(pmd);
120+
given(pmd.getParameterType(1)).willReturn(Types.SMALLINT);
121+
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
122+
verify(dbmd).getDriverName();
123+
verify(pmd).getParameterType(1);
124+
verify(preparedStatement).setNull(1, Types.SMALLINT);
125+
assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
126+
}
127+
128+
@Test
129+
public void testSetParameterValueWithNullAndUnknownTypeAndGetParameterTypeNotWorking() throws SQLException {
130+
StatementCreatorUtils.driversWithNoSupportForGetParameterType.clear();
131+
Connection con = mock(Connection.class);
132+
DatabaseMetaData dbmd = mock(DatabaseMetaData.class);
133+
given(preparedStatement.getConnection()).willReturn(con);
134+
given(con.getMetaData()).willReturn(dbmd);
135+
given(dbmd.getDatabaseProductName()).willReturn("Apache Derby");
136+
given(dbmd.getDriverName()).willReturn("Apache Derby Embedded Driver");
137+
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
138+
verify(dbmd).getDatabaseProductName();
139+
verify(dbmd).getDriverName();
140+
verify(preparedStatement).setNull(1, Types.VARCHAR);
141+
assertEquals(1, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
142+
143+
reset(preparedStatement, con, dbmd);
144+
ParameterMetaData pmd = mock(ParameterMetaData.class);
145+
given(preparedStatement.getConnection()).willReturn(con);
146+
given(con.getMetaData()).willReturn(dbmd);
147+
given(preparedStatement.getParameterMetaData()).willReturn(pmd);
148+
given(pmd.getParameterType(1)).willThrow(new SQLException("unsupported"));
149+
given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server");
150+
given(dbmd.getDriverName()).willReturn("Informix Driver");
151+
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
152+
verify(pmd).getParameterType(1);
153+
verify(dbmd).getDatabaseProductName();
154+
verify(dbmd).getDriverName();
155+
verify(preparedStatement).setObject(1, null);
156+
assertEquals(2, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
157+
158+
reset(preparedStatement, con, dbmd, pmd);
159+
given(preparedStatement.getConnection()).willReturn(con);
160+
given(con.getMetaData()).willReturn(dbmd);
161+
given(dbmd.getDatabaseProductName()).willReturn("Informix Dynamic Server");
162+
given(dbmd.getDriverName()).willReturn("Informix Driver");
163+
StatementCreatorUtils.setParameterValue(preparedStatement, 1, SqlTypeValue.TYPE_UNKNOWN, null, null);
164+
verify(preparedStatement, never()).getParameterMetaData();
165+
verify(dbmd).getDatabaseProductName();
166+
verify(dbmd).getDriverName();
167+
verify(preparedStatement).setObject(1, null);
168+
assertEquals(2, StatementCreatorUtils.driversWithNoSupportForGetParameterType.size());
84169
}
85170

86171
@Test

0 commit comments

Comments
 (0)