11/*
2- * Copyright 2002-2012 the original author or authors.
2+ * Copyright 2002-2016 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1818
1919import java .util .Collections ;
2020import java .util .Map ;
21- import java .util .WeakHashMap ;
2221import javax .sql .DataSource ;
2322
2423import org .apache .commons .logging .Log ;
3029import org .springframework .core .io .ClassPathResource ;
3130import org .springframework .core .io .Resource ;
3231import org .springframework .util .Assert ;
32+ import org .springframework .util .ConcurrentReferenceHashMap ;
3333import org .springframework .util .PatternMatchUtils ;
3434
3535/**
@@ -85,7 +85,8 @@ public static SQLErrorCodesFactory getInstance() {
8585 /**
8686 * Map to cache the SQLErrorCodes instance per DataSource.
8787 */
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 );
8990
9091
9192 /**
@@ -153,33 +154,33 @@ protected Resource loadResource(String path) {
153154 /**
154155 * Return the {@link SQLErrorCodes} instance for the given database.
155156 * <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})
157158 * @return the {@code SQLErrorCodes} instance for the given database
158159 * @throws IllegalArgumentException if the supplied database name is {@code null}
159160 */
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" );
162163
163- SQLErrorCodes sec = this .errorCodesMap .get (dbName );
164+ SQLErrorCodes sec = this .errorCodesMap .get (databaseName );
164165 if (sec == null ) {
165166 for (SQLErrorCodes candidate : this .errorCodesMap .values ()) {
166- if (PatternMatchUtils .simpleMatch (candidate .getDatabaseProductNames (), dbName )) {
167+ if (PatternMatchUtils .simpleMatch (candidate .getDatabaseProductNames (), databaseName )) {
167168 sec = candidate ;
168169 break ;
169170 }
170171 }
171172 }
172173 if (sec != null ) {
173- checkCustomTranslatorRegistry (dbName , sec );
174+ checkCustomTranslatorRegistry (databaseName , sec );
174175 if (logger .isDebugEnabled ()) {
175- logger .debug ("SQL error codes for '" + dbName + "' found" );
176+ logger .debug ("SQL error codes for '" + databaseName + "' found" );
176177 }
177178 return sec ;
178179 }
179180
180181 // Could not find the database among the defined ones.
181182 if (logger .isDebugEnabled ()) {
182- logger .debug ("SQL error codes for '" + dbName + "' not found" );
183+ logger .debug ("SQL error codes for '" + databaseName + "' not found" );
183184 }
184185 return new SQLErrorCodes ();
185186 }
@@ -196,75 +197,97 @@ public SQLErrorCodes getErrorCodes(String dbName) {
196197 public SQLErrorCodes getErrorCodes (DataSource dataSource ) {
197198 Assert .notNull (dataSource , "DataSource must not be null" );
198199 if (logger .isDebugEnabled ()) {
199- logger .debug ("Looking up default SQLErrorCodes for DataSource [" + dataSource + "]" );
200+ logger .debug ("Looking up default SQLErrorCodes for DataSource [" + identify ( dataSource ) + "]" );
200201 }
201202
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 ( );
220221 }
221- sec = getErrorCodes (dbName );
222- this .dataSourceCache .put (dataSource , sec );
223- return sec ;
224222 }
225223 }
226- catch (MetaDataAccessException ex ) {
227- logger .warn ("Error while extracting database product name - falling back to empty error codes" , ex );
228- }
229224 }
230225
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 ;
233231 }
234232
235233 /**
236234 * Associate the specified database name with the given {@link DataSource}.
237235 * @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
239237 * 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)
241240 */
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 + "'" ) ;
247246 }
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 ());
248271 }
249272
250273 /**
251274 * Check the {@link CustomSQLExceptionTranslatorRegistry} for any entries.
252275 */
253- private void checkCustomTranslatorRegistry (String dbName , SQLErrorCodes dbCodes ) {
276+ private void checkCustomTranslatorRegistry (String databaseName , SQLErrorCodes errorCodes ) {
254277 SQLExceptionTranslator customTranslator =
255- CustomSQLExceptionTranslatorRegistry .getInstance ().findTranslatorForDatabase (dbName );
278+ CustomSQLExceptionTranslatorRegistry .getInstance ().findTranslatorForDatabase (databaseName );
256279 if (customTranslator != null ) {
257- if (dbCodes .getCustomSqlExceptionTranslator () != null ) {
280+ if (errorCodes .getCustomSqlExceptionTranslator () != null && logger . isWarnEnabled () ) {
258281 logger .warn ("Overriding already defined custom translator '" +
259- dbCodes .getCustomSqlExceptionTranslator ().getClass ().getSimpleName () +
282+ errorCodes .getCustomSqlExceptionTranslator ().getClass ().getSimpleName () +
260283 " with '" + customTranslator .getClass ().getSimpleName () +
261- "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName );
284+ "' found in the CustomSQLExceptionTranslatorRegistry for database ' " + databaseName + "'" );
262285 }
263- else {
286+ else if ( logger . isInfoEnabled ()) {
264287 logger .info ("Using custom translator '" + customTranslator .getClass ().getSimpleName () +
265- "' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName );
288+ "' found in the CustomSQLExceptionTranslatorRegistry for database ' " + databaseName + "'" );
266289 }
267- dbCodes .setCustomSqlExceptionTranslator (customTranslator );
290+ errorCodes .setCustomSqlExceptionTranslator (customTranslator );
268291 }
269292 }
270293
0 commit comments