1
1
/*
2
- * Copyright 2002-2012 the original author or authors.
2
+ * Copyright 2002-2016 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
18
18
19
19
import java .util .Collections ;
20
20
import java .util .Map ;
21
- import java .util .WeakHashMap ;
22
21
import javax .sql .DataSource ;
23
22
24
23
import org .apache .commons .logging .Log ;
30
29
import org .springframework .core .io .ClassPathResource ;
31
30
import org .springframework .core .io .Resource ;
32
31
import org .springframework .util .Assert ;
32
+ import org .springframework .util .ConcurrentReferenceHashMap ;
33
33
import org .springframework .util .PatternMatchUtils ;
34
34
35
35
/**
@@ -85,7 +85,8 @@ public static SQLErrorCodesFactory getInstance() {
85
85
/**
86
86
* Map to cache the SQLErrorCodes instance per DataSource.
87
87
*/
88
- private final Map <DataSource , SQLErrorCodes > dataSourceCache = new WeakHashMap <DataSource , SQLErrorCodes >(16 );
88
+ private final Map <DataSource , SQLErrorCodes > dataSourceCache =
89
+ new ConcurrentReferenceHashMap <DataSource , SQLErrorCodes >(16 );
89
90
90
91
91
92
/**
@@ -153,33 +154,33 @@ protected Resource loadResource(String path) {
153
154
/**
154
155
* Return the {@link SQLErrorCodes} instance for the given database.
155
156
* <p>No need for a database metadata lookup.
156
- * @param dbName the database name (must not be {@code null})
157
+ * @param databaseName the database name (must not be {@code null})
157
158
* @return the {@code SQLErrorCodes} instance for the given database
158
159
* @throws IllegalArgumentException if the supplied database name is {@code null}
159
160
*/
160
- public SQLErrorCodes getErrorCodes (String dbName ) {
161
- Assert .notNull (dbName , "Database product name must not be null" );
161
+ public SQLErrorCodes getErrorCodes (String databaseName ) {
162
+ Assert .notNull (databaseName , "Database product name must not be null" );
162
163
163
- SQLErrorCodes sec = this .errorCodesMap .get (dbName );
164
+ SQLErrorCodes sec = this .errorCodesMap .get (databaseName );
164
165
if (sec == null ) {
165
166
for (SQLErrorCodes candidate : this .errorCodesMap .values ()) {
166
- if (PatternMatchUtils .simpleMatch (candidate .getDatabaseProductNames (), dbName )) {
167
+ if (PatternMatchUtils .simpleMatch (candidate .getDatabaseProductNames (), databaseName )) {
167
168
sec = candidate ;
168
169
break ;
169
170
}
170
171
}
171
172
}
172
173
if (sec != null ) {
173
- checkCustomTranslatorRegistry (dbName , sec );
174
+ checkCustomTranslatorRegistry (databaseName , sec );
174
175
if (logger .isDebugEnabled ()) {
175
- logger .debug ("SQL error codes for '" + dbName + "' found" );
176
+ logger .debug ("SQL error codes for '" + databaseName + "' found" );
176
177
}
177
178
return sec ;
178
179
}
179
180
180
181
// Could not find the database among the defined ones.
181
182
if (logger .isDebugEnabled ()) {
182
- logger .debug ("SQL error codes for '" + dbName + "' not found" );
183
+ logger .debug ("SQL error codes for '" + databaseName + "' not found" );
183
184
}
184
185
return new SQLErrorCodes ();
185
186
}
@@ -196,75 +197,97 @@ public SQLErrorCodes getErrorCodes(String dbName) {
196
197
public SQLErrorCodes getErrorCodes (DataSource dataSource ) {
197
198
Assert .notNull (dataSource , "DataSource must not be null" );
198
199
if (logger .isDebugEnabled ()) {
199
- logger .debug ("Looking up default SQLErrorCodes for DataSource [" + dataSource + "]" );
200
+ logger .debug ("Looking up default SQLErrorCodes for DataSource [" + identify ( dataSource ) + "]" );
200
201
}
201
202
202
- synchronized ( this . dataSourceCache ) {
203
- // Let's avoid looking up database product info if we can.
204
- SQLErrorCodes sec = this . dataSourceCache . get ( dataSource );
205
- if ( sec != null ) {
206
- if ( logger . isDebugEnabled ()) {
207
- logger . debug ( "SQLErrorCodes found in cache for DataSource [" +
208
- dataSource . getClass (). getName () + '@' + Integer . toHexString ( dataSource . hashCode ()) + "]" );
209
- }
210
- return sec ;
211
- }
212
- // We could not find it - got to look it up.
213
- try {
214
- String dbName = ( String ) JdbcUtils . extractDatabaseMetaData ( dataSource , "getDatabaseProductName" );
215
- if ( dbName != null ) {
216
- if ( logger . isDebugEnabled () ) {
217
- logger .debug ( "Database product name cached for DataSource [" +
218
- dataSource . getClass (). getName () + '@' + Integer . toHexString ( dataSource . hashCode ()) +
219
- "]: name is '" + dbName + "'" );
203
+ // Try efficient lock-free access for existing cache entry
204
+ SQLErrorCodes sec = this . dataSourceCache . get ( dataSource );
205
+ if ( sec == null ) {
206
+ synchronized ( this . dataSourceCache ) {
207
+ // Double-check within full dataSourceCache lock
208
+ sec = this . dataSourceCache . get ( dataSource );
209
+ if ( sec == null ) {
210
+ // We could not find it - got to look it up.
211
+ try {
212
+ String name = ( String ) JdbcUtils . extractDatabaseMetaData ( dataSource , "getDatabaseProductName" );
213
+ if ( name != null ) {
214
+ return registerDatabase ( dataSource , name );
215
+ }
216
+ }
217
+ catch ( MetaDataAccessException ex ) {
218
+ logger .warn ( "Error while extracting database name - falling back to empty error codes" , ex );
219
+ // Fallback is to return an empty SQLErrorCodes instance.
220
+ return new SQLErrorCodes ( );
220
221
}
221
- sec = getErrorCodes (dbName );
222
- this .dataSourceCache .put (dataSource , sec );
223
- return sec ;
224
222
}
225
223
}
226
- catch (MetaDataAccessException ex ) {
227
- logger .warn ("Error while extracting database product name - falling back to empty error codes" , ex );
228
- }
229
224
}
230
225
231
- // Fallback is to return an empty SQLErrorCodes instance.
232
- return new SQLErrorCodes ();
226
+ if (logger .isDebugEnabled ()) {
227
+ logger .debug ("SQLErrorCodes found in cache for DataSource [" + identify (dataSource ) + "]" );
228
+ }
229
+
230
+ return sec ;
233
231
}
234
232
235
233
/**
236
234
* Associate the specified database name with the given {@link DataSource}.
237
235
* @param dataSource the {@code DataSource} identifying the database
238
- * @param dbName the corresponding database name as stated in the error codes
236
+ * @param databaseName the corresponding database name as stated in the error codes
239
237
* definition file (must not be {@code null})
240
- * @return the corresponding {@code SQLErrorCodes} object
238
+ * @return the corresponding {@code SQLErrorCodes} object (never {@code null})
239
+ * @see #unregisterDatabase(DataSource)
241
240
*/
242
- public SQLErrorCodes registerDatabase (DataSource dataSource , String dbName ) {
243
- synchronized ( this . dataSourceCache ) {
244
- SQLErrorCodes sec = getErrorCodes ( dbName );
245
- this . dataSourceCache . put ( dataSource , sec );
246
- return sec ;
241
+ public SQLErrorCodes registerDatabase (DataSource dataSource , String databaseName ) {
242
+ SQLErrorCodes sec = getErrorCodes ( databaseName );
243
+ if ( logger . isDebugEnabled ()) {
244
+ logger . debug ( "Caching SQL error codes for DataSource [" + identify ( dataSource ) +
245
+ "]: database product name is '" + databaseName + "'" ) ;
247
246
}
247
+ this .dataSourceCache .put (dataSource , sec );
248
+ return sec ;
249
+ }
250
+
251
+ /**
252
+ * Clear the cache for the specified {@link DataSource}, if registered.
253
+ * @param dataSource the {@code DataSource} identifying the database
254
+ * @return the corresponding {@code SQLErrorCodes} object,
255
+ * or {@code null} if not registered
256
+ * @since 4.3.5
257
+ * @see #registerDatabase(DataSource, String)
258
+ */
259
+ public SQLErrorCodes unregisterDatabase (DataSource dataSource ) {
260
+ return this .dataSourceCache .remove (dataSource );
261
+ }
262
+
263
+ /**
264
+ * Build an identification String for the given {@link DataSource},
265
+ * primarily for logging purposes.
266
+ * @param dataSource the {@code DataSource} to introspect
267
+ * @return the identification String
268
+ */
269
+ private String identify (DataSource dataSource ) {
270
+ return dataSource .getClass ().getName () + '@' + Integer .toHexString (dataSource .hashCode ());
248
271
}
249
272
250
273
/**
251
274
* Check the {@link CustomSQLExceptionTranslatorRegistry} for any entries.
252
275
*/
253
- private void checkCustomTranslatorRegistry (String dbName , SQLErrorCodes dbCodes ) {
276
+ private void checkCustomTranslatorRegistry (String databaseName , SQLErrorCodes errorCodes ) {
254
277
SQLExceptionTranslator customTranslator =
255
- CustomSQLExceptionTranslatorRegistry .getInstance ().findTranslatorForDatabase (dbName );
278
+ CustomSQLExceptionTranslatorRegistry .getInstance ().findTranslatorForDatabase (databaseName );
256
279
if (customTranslator != null ) {
257
- if (dbCodes .getCustomSqlExceptionTranslator () != null ) {
280
+ if (errorCodes .getCustomSqlExceptionTranslator () != null && logger . isWarnEnabled () ) {
258
281
logger .warn ("Overriding already defined custom translator '" +
259
- dbCodes .getCustomSqlExceptionTranslator ().getClass ().getSimpleName () +
282
+ errorCodes .getCustomSqlExceptionTranslator ().getClass ().getSimpleName () +
260
283
" with '" + customTranslator .getClass ().getSimpleName () +
261
- "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName );
284
+ "' found in the CustomSQLExceptionTranslatorRegistry for database ' " + databaseName + "'" );
262
285
}
263
- else {
286
+ else if ( logger . isInfoEnabled ()) {
264
287
logger .info ("Using custom translator '" + customTranslator .getClass ().getSimpleName () +
265
- "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName );
288
+ "' found in the CustomSQLExceptionTranslatorRegistry for database ' " + databaseName + "'" );
266
289
}
267
- dbCodes .setCustomSqlExceptionTranslator (customTranslator );
290
+ errorCodes .setCustomSqlExceptionTranslator (customTranslator );
268
291
}
269
292
}
270
293
0 commit comments