Browse Source

SQLErrorCodesFactory provides unregisterDatabase method

This commit also migrates from a WeakHashMap to a ConcurrentReferenceHashMap, allowing for concurrent access to existing cache entries.

Issue: SPR-15006
pull/1243/merge
Juergen Hoeller 9 years ago
parent
commit
b825528d01
  1. 126
      spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java

126
spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java

@ -18,7 +18,6 @@ package org.springframework.jdbc.support;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -30,6 +29,7 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.PatternMatchUtils; import org.springframework.util.PatternMatchUtils;
/** /**
@ -85,7 +85,7 @@ public class SQLErrorCodesFactory {
/** /**
* Map to cache the SQLErrorCodes instance per DataSource. * Map to cache the SQLErrorCodes instance per DataSource.
*/ */
private final Map<DataSource, SQLErrorCodes> dataSourceCache = new WeakHashMap<>(16); private final Map<DataSource, SQLErrorCodes> dataSourceCache = new ConcurrentReferenceHashMap<>(16);
/** /**
@ -153,33 +153,33 @@ public class SQLErrorCodesFactory {
/** /**
* Return the {@link SQLErrorCodes} instance for the given database. * Return the {@link SQLErrorCodes} instance for the given database.
* <p>No need for a database metadata lookup. * <p>No need for a database metadata lookup.
* @param dbName the database name (must not be {@code null}) * @param databaseName the database name (must not be {@code null})
* @return the {@code SQLErrorCodes} instance for the given database * @return the {@code SQLErrorCodes} instance for the given database
* @throws IllegalArgumentException if the supplied database name is {@code null} * @throws IllegalArgumentException if the supplied database name is {@code null}
*/ */
public SQLErrorCodes getErrorCodes(String dbName) { public SQLErrorCodes getErrorCodes(String databaseName) {
Assert.notNull(dbName, "Database product name must not be null"); Assert.notNull(databaseName, "Database product name must not be null");
SQLErrorCodes sec = this.errorCodesMap.get(dbName); SQLErrorCodes sec = this.errorCodesMap.get(databaseName);
if (sec == null) { if (sec == null) {
for (SQLErrorCodes candidate : this.errorCodesMap.values()) { for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
if (PatternMatchUtils.simpleMatch(candidate.getDatabaseProductNames(), dbName)) { if (PatternMatchUtils.simpleMatch(candidate.getDatabaseProductNames(), databaseName)) {
sec = candidate; sec = candidate;
break; break;
} }
} }
} }
if (sec != null) { if (sec != null) {
checkCustomTranslatorRegistry(dbName, sec); checkCustomTranslatorRegistry(databaseName, sec);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("SQL error codes for '" + dbName + "' found"); logger.debug("SQL error codes for '" + databaseName + "' found");
} }
return sec; return sec;
} }
// Could not find the database among the defined ones. // Could not find the database among the defined ones.
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("SQL error codes for '" + dbName + "' not found"); logger.debug("SQL error codes for '" + databaseName + "' not found");
} }
return new SQLErrorCodes(); return new SQLErrorCodes();
} }
@ -196,75 +196,97 @@ public class SQLErrorCodesFactory {
public SQLErrorCodes getErrorCodes(DataSource dataSource) { public SQLErrorCodes getErrorCodes(DataSource dataSource) {
Assert.notNull(dataSource, "DataSource must not be null"); Assert.notNull(dataSource, "DataSource must not be null");
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Looking up default SQLErrorCodes for DataSource [" + dataSource + "]"); logger.debug("Looking up default SQLErrorCodes for DataSource [" + identify(dataSource) + "]");
} }
synchronized (this.dataSourceCache) { // Try efficient lock-free access for existing cache entry
// Let's avoid looking up database product info if we can. SQLErrorCodes sec = this.dataSourceCache.get(dataSource);
SQLErrorCodes sec = this.dataSourceCache.get(dataSource); if (sec == null) {
if (sec != null) { synchronized (this.dataSourceCache) {
if (logger.isDebugEnabled()) { // Double-check within full dataSourceCache lock
logger.debug("SQLErrorCodes found in cache for DataSource [" + sec = this.dataSourceCache.get(dataSource);
dataSource.getClass().getName() + '@' + Integer.toHexString(dataSource.hashCode()) + "]"); if (sec == null) {
} // We could not find it - got to look it up.
return sec; try {
} String name = (String) JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName");
// We could not find it - got to look it up. if (name != null) {
try { return registerDatabase(dataSource, name);
String dbName = (String) JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName"); }
if (dbName != null) { }
if (logger.isDebugEnabled()) { catch (MetaDataAccessException ex) {
logger.debug("Database product name cached for DataSource [" + logger.warn("Error while extracting database name - falling back to empty error codes", ex);
dataSource.getClass().getName() + '@' + Integer.toHexString(dataSource.hashCode()) + // Fallback is to return an empty SQLErrorCodes instance.
"]: name is '" + dbName + "'"); return new SQLErrorCodes();
} }
sec = getErrorCodes(dbName);
this.dataSourceCache.put(dataSource, sec);
return sec;
} }
} }
catch (MetaDataAccessException ex) {
logger.warn("Error while extracting database product name - falling back to empty error codes", ex);
}
} }
// Fallback is to return an empty SQLErrorCodes instance. if (logger.isDebugEnabled()) {
return new SQLErrorCodes(); logger.debug("SQLErrorCodes found in cache for DataSource [" + identify(dataSource) + "]");
}
return sec;
} }
/** /**
* Associate the specified database name with the given {@link DataSource}. * Associate the specified database name with the given {@link DataSource}.
* @param dataSource the {@code DataSource} identifying the database * @param dataSource the {@code DataSource} identifying the database
* @param dbName the corresponding database name as stated in the error codes * @param databaseName the corresponding database name as stated in the error codes
* definition file (must not be {@code null}) * definition file (must not be {@code null})
* @return the corresponding {@code SQLErrorCodes} object * @return the corresponding {@code SQLErrorCodes} object (never {@code null})
* @see #unregisterDatabase(DataSource)
*/ */
public SQLErrorCodes registerDatabase(DataSource dataSource, String dbName) { public SQLErrorCodes registerDatabase(DataSource dataSource, String databaseName) {
synchronized (this.dataSourceCache) { SQLErrorCodes sec = getErrorCodes(databaseName);
SQLErrorCodes sec = getErrorCodes(dbName); if (logger.isDebugEnabled()) {
this.dataSourceCache.put(dataSource, sec); logger.debug("Caching SQL error codes for DataSource [" + identify(dataSource) +
return sec; "]: database product name is '" + databaseName + "'");
} }
this.dataSourceCache.put(dataSource, sec);
return sec;
}
/**
* Clear the cache for the specified {@link DataSource}, if registered.
* @param dataSource the {@code DataSource} identifying the database
* @return the corresponding {@code SQLErrorCodes} object,
* or {@code null} if not registered
* @since 4.3.5
* @see #registerDatabase(DataSource, String)
*/
public SQLErrorCodes unregisterDatabase(DataSource dataSource) {
return this.dataSourceCache.remove(dataSource);
}
/**
* Build an identification String for the given {@link DataSource},
* primarily for logging purposes.
* @param dataSource the {@code DataSource} to introspect
* @return the identification String
*/
private String identify(DataSource dataSource) {
return dataSource.getClass().getName() + '@' + Integer.toHexString(dataSource.hashCode());
} }
/** /**
* Check the {@link CustomSQLExceptionTranslatorRegistry} for any entries. * Check the {@link CustomSQLExceptionTranslatorRegistry} for any entries.
*/ */
private void checkCustomTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) { private void checkCustomTranslatorRegistry(String databaseName, SQLErrorCodes errorCodes) {
SQLExceptionTranslator customTranslator = SQLExceptionTranslator customTranslator =
CustomSQLExceptionTranslatorRegistry.getInstance().findTranslatorForDatabase(dbName); CustomSQLExceptionTranslatorRegistry.getInstance().findTranslatorForDatabase(databaseName);
if (customTranslator != null) { if (customTranslator != null) {
if (dbCodes.getCustomSqlExceptionTranslator() != null) { if (errorCodes.getCustomSqlExceptionTranslator() != null && logger.isWarnEnabled()) {
logger.warn("Overriding already defined custom translator '" + logger.warn("Overriding already defined custom translator '" +
dbCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() + errorCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() +
" with '" + customTranslator.getClass().getSimpleName() + " with '" + customTranslator.getClass().getSimpleName() +
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName); "' found in the CustomSQLExceptionTranslatorRegistry for database '" + databaseName + "'");
} }
else { else if (logger.isInfoEnabled()) {
logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() + logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() +
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName); "' found in the CustomSQLExceptionTranslatorRegistry for database '" + databaseName + "'");
} }
dbCodes.setCustomSqlExceptionTranslator(customTranslator); errorCodes.setCustomSqlExceptionTranslator(customTranslator);
} }
} }

Loading…
Cancel
Save