Retry SQLErrorCodesFactory retrieval if DatabaseMetaData access failed
Includes deprecation of JdbcUtils.extractDatabaseMetaData(DataSource, String) in favor of the now generified version of extractDatabaseMetaData(DataSource, DatabaseMetaDataCallback). Closes gh-25681 Closes gh-25686
This commit is contained in:
parent
c2363a6ef9
commit
670b9fd60b
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.scheduling.quartz;
|
package org.springframework.scheduling.quartz;
|
||||||
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
@ -147,7 +148,8 @@ public class LocalDataSourceJobStore extends JobStoreCMT {
|
||||||
|
|
||||||
// No, if HSQL is the platform, we really don't want to use locks...
|
// No, if HSQL is the platform, we really don't want to use locks...
|
||||||
try {
|
try {
|
||||||
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource, "getDatabaseProductName");
|
String productName = JdbcUtils.extractDatabaseMetaData(this.dataSource,
|
||||||
|
DatabaseMetaData::getDatabaseProductName);
|
||||||
productName = JdbcUtils.commonDatabaseName(productName);
|
productName = JdbcUtils.commonDatabaseName(productName);
|
||||||
if (productName != null && productName.toLowerCase().contains("hsql")) {
|
if (productName != null && productName.toLowerCase().contains("hsql")) {
|
||||||
setUseDBLocks(false);
|
setUseDBLocks(false);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ public final class CallMetaDataProviderFactory {
|
||||||
*/
|
*/
|
||||||
public static CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
|
public static CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
|
||||||
try {
|
try {
|
||||||
return (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
||||||
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
||||||
boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();
|
boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();
|
||||||
if (context.isFunction()) {
|
if (context.isFunction()) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -49,9 +49,8 @@ public final class TableMetaDataProviderFactory {
|
||||||
*/
|
*/
|
||||||
public static TableMetaDataProvider createMetaDataProvider(DataSource dataSource, TableMetaDataContext context) {
|
public static TableMetaDataProvider createMetaDataProvider(DataSource dataSource, TableMetaDataContext context) {
|
||||||
try {
|
try {
|
||||||
return (TableMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
return JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
|
||||||
String databaseProductName =
|
String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
||||||
JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
|
|
||||||
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
|
boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
|
||||||
TableMetaDataProvider provider;
|
TableMetaDataProvider provider;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -26,10 +26,12 @@ import java.sql.SQLException;
|
||||||
* and handled correctly by the JdbcUtils class.
|
* and handled correctly by the JdbcUtils class.
|
||||||
*
|
*
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @see JdbcUtils#extractDatabaseMetaData
|
* @author Juergen Hoeller
|
||||||
|
* @param <T> the result type
|
||||||
|
* @see JdbcUtils#extractDatabaseMetaData(javax.sql.DataSource, DatabaseMetaDataCallback)
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface DatabaseMetaDataCallback {
|
public interface DatabaseMetaDataCallback<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementations must implement this method to process the meta-data
|
* Implementations must implement this method to process the meta-data
|
||||||
|
|
@ -42,6 +44,6 @@ public interface DatabaseMetaDataCallback {
|
||||||
* @throws MetaDataAccessException in case of other failures while
|
* @throws MetaDataAccessException in case of other failures while
|
||||||
* extracting meta-data (for example, reflection failure)
|
* extracting meta-data (for example, reflection failure)
|
||||||
*/
|
*/
|
||||||
Object processMetaData(DatabaseMetaData dbmd) throws SQLException, MetaDataAccessException;
|
T processMetaData(DatabaseMetaData dbmd) throws SQLException, MetaDataAccessException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -315,26 +315,44 @@ public abstract class JdbcUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract database meta-data via the given DatabaseMetaDataCallback.
|
* Extract database meta-data via the given DatabaseMetaDataCallback.
|
||||||
* <p>This method will open a connection to the database and retrieve the database meta-data.
|
* <p>This method will open a connection to the database and retrieve its meta-data.
|
||||||
* Since this method is called before the exception translation feature is configured for
|
* Since this method is called before the exception translation feature is configured
|
||||||
* a datasource, this method can not rely on the SQLException translation functionality.
|
* for a DataSource, this method can not rely on SQLException translation itself.
|
||||||
* <p>Any exceptions will be wrapped in a MetaDataAccessException. This is a checked exception
|
* <p>Any exceptions will be wrapped in a MetaDataAccessException. This is a checked
|
||||||
* and any calling code should catch and handle this exception. You can just log the
|
* exception and any calling code should catch and handle this exception. You can just
|
||||||
* error and hope for the best, but there is probably a more serious error that will
|
* log the error and hope for the best, but there is probably a more serious error that
|
||||||
* reappear when you try to access the database again.
|
* will reappear when you try to access the database again.
|
||||||
* @param dataSource the DataSource to extract meta-data for
|
* @param dataSource the DataSource to extract meta-data for
|
||||||
* @param action callback that will do the actual work
|
* @param action callback that will do the actual work
|
||||||
* @return object containing the extracted information, as returned by
|
* @return object containing the extracted information, as returned by
|
||||||
* the DatabaseMetaDataCallback's {@code processMetaData} method
|
* the DatabaseMetaDataCallback's {@code processMetaData} method
|
||||||
* @throws MetaDataAccessException if meta-data access failed
|
* @throws MetaDataAccessException if meta-data access failed
|
||||||
|
* @see java.sql.DatabaseMetaData
|
||||||
*/
|
*/
|
||||||
public static Object extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback action)
|
public static <T> T extractDatabaseMetaData(DataSource dataSource, DatabaseMetaDataCallback<T> action)
|
||||||
throws MetaDataAccessException {
|
throws MetaDataAccessException {
|
||||||
|
|
||||||
Connection con = null;
|
Connection con = null;
|
||||||
try {
|
try {
|
||||||
con = DataSourceUtils.getConnection(dataSource);
|
con = DataSourceUtils.getConnection(dataSource);
|
||||||
DatabaseMetaData metaData = con.getMetaData();
|
DatabaseMetaData metaData;
|
||||||
|
try {
|
||||||
|
metaData = con.getMetaData();
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
if (DataSourceUtils.isConnectionTransactional(con, dataSource)) {
|
||||||
|
// Probably a closed thread-bound Connection - retry against fresh Connection
|
||||||
|
DataSourceUtils.releaseConnection(con, dataSource);
|
||||||
|
con = null;
|
||||||
|
logger.debug("Failed to obtain DatabaseMetaData from transactional Connection - " +
|
||||||
|
"retrying against fresh Connection", ex);
|
||||||
|
con = dataSource.getConnection();
|
||||||
|
metaData = con.getMetaData();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (metaData == null) {
|
if (metaData == null) {
|
||||||
// should only happen in test environments
|
// should only happen in test environments
|
||||||
throw new MetaDataAccessException("DatabaseMetaData returned by Connection [" + con + "] was null");
|
throw new MetaDataAccessException("DatabaseMetaData returned by Connection [" + con + "] was null");
|
||||||
|
|
@ -365,7 +383,11 @@ public abstract class JdbcUtils {
|
||||||
* @throws MetaDataAccessException if we couldn't access the DatabaseMetaData
|
* @throws MetaDataAccessException if we couldn't access the DatabaseMetaData
|
||||||
* or failed to invoke the specified method
|
* or failed to invoke the specified method
|
||||||
* @see java.sql.DatabaseMetaData
|
* @see java.sql.DatabaseMetaData
|
||||||
|
* @deprecated as of 5.2.9, in favor of
|
||||||
|
* {@link #extractDatabaseMetaData(DataSource, DatabaseMetaDataCallback)}
|
||||||
|
* with a lambda expression or method reference and a generically typed result
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T extractDatabaseMetaData(DataSource dataSource, final String metaDataMethodName)
|
public static <T> T extractDatabaseMetaData(DataSource dataSource, final String metaDataMethodName)
|
||||||
throws MetaDataAccessException {
|
throws MetaDataAccessException {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -35,6 +35,8 @@ import org.springframework.dao.TransientDataAccessResourceException;
|
||||||
import org.springframework.jdbc.BadSqlGrammarException;
|
import org.springframework.jdbc.BadSqlGrammarException;
|
||||||
import org.springframework.jdbc.InvalidResultSetAccessException;
|
import org.springframework.jdbc.InvalidResultSetAccessException;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
import org.springframework.util.function.SupplierUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link SQLExceptionTranslator} that analyzes vendor-specific error codes.
|
* Implementation of {@link SQLExceptionTranslator} that analyzes vendor-specific error codes.
|
||||||
|
|
@ -76,7 +78,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
|
|
||||||
/** Error codes used by this translator. */
|
/** Error codes used by this translator. */
|
||||||
@Nullable
|
@Nullable
|
||||||
private SQLErrorCodes sqlErrorCodes;
|
private SingletonSupplier<SQLErrorCodes> sqlErrorCodes;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -120,7 +122,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
*/
|
*/
|
||||||
public SQLErrorCodeSQLExceptionTranslator(SQLErrorCodes sec) {
|
public SQLErrorCodeSQLExceptionTranslator(SQLErrorCodes sec) {
|
||||||
this();
|
this();
|
||||||
this.sqlErrorCodes = sec;
|
this.sqlErrorCodes = SingletonSupplier.of(sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -134,7 +136,9 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
||||||
*/
|
*/
|
||||||
public void setDataSource(DataSource dataSource) {
|
public void setDataSource(DataSource dataSource) {
|
||||||
this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
|
this.sqlErrorCodes =
|
||||||
|
SingletonSupplier.of(() -> SQLErrorCodesFactory.getInstance().resolveErrorCodes(dataSource));
|
||||||
|
this.sqlErrorCodes.get(); // try early initialization - otherwise the supplier will retry later
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -146,7 +150,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
||||||
*/
|
*/
|
||||||
public void setDatabaseProductName(String dbName) {
|
public void setDatabaseProductName(String dbName) {
|
||||||
this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dbName);
|
this.sqlErrorCodes = SingletonSupplier.of(SQLErrorCodesFactory.getInstance().getErrorCodes(dbName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -154,7 +158,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
* @param sec custom error codes to use
|
* @param sec custom error codes to use
|
||||||
*/
|
*/
|
||||||
public void setSqlErrorCodes(@Nullable SQLErrorCodes sec) {
|
public void setSqlErrorCodes(@Nullable SQLErrorCodes sec) {
|
||||||
this.sqlErrorCodes = sec;
|
this.sqlErrorCodes = SingletonSupplier.ofNullable(sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -164,7 +168,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public SQLErrorCodes getSqlErrorCodes() {
|
public SQLErrorCodes getSqlErrorCodes() {
|
||||||
return this.sqlErrorCodes;
|
return SupplierUtils.resolve(this.sqlErrorCodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -175,7 +179,6 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {
|
if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {
|
||||||
SQLException nestedSqlEx = sqlEx.getNextException();
|
SQLException nestedSqlEx = sqlEx.getNextException();
|
||||||
if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {
|
if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {
|
||||||
logger.debug("Using nested SQLException from the BatchUpdateException");
|
|
||||||
sqlEx = nestedSqlEx;
|
sqlEx = nestedSqlEx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,8 +190,9 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, try the custom SQLException translator, if available.
|
// Next, try the custom SQLException translator, if available.
|
||||||
if (this.sqlErrorCodes != null) {
|
SQLErrorCodes sqlErrorCodes = getSqlErrorCodes();
|
||||||
SQLExceptionTranslator customTranslator = this.sqlErrorCodes.getCustomSqlExceptionTranslator();
|
if (sqlErrorCodes != null) {
|
||||||
|
SQLExceptionTranslator customTranslator = sqlErrorCodes.getCustomSqlExceptionTranslator();
|
||||||
if (customTranslator != null) {
|
if (customTranslator != null) {
|
||||||
DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
|
DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
|
||||||
if (customDex != null) {
|
if (customDex != null) {
|
||||||
|
|
@ -198,9 +202,9 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check SQLErrorCodes with corresponding error code, if available.
|
// Check SQLErrorCodes with corresponding error code, if available.
|
||||||
if (this.sqlErrorCodes != null) {
|
if (sqlErrorCodes != null) {
|
||||||
String errorCode;
|
String errorCode;
|
||||||
if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {
|
if (sqlErrorCodes.isUseSqlStateForTranslation()) {
|
||||||
errorCode = sqlEx.getSQLState();
|
errorCode = sqlEx.getSQLState();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -215,7 +219,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
|
|
||||||
if (errorCode != null) {
|
if (errorCode != null) {
|
||||||
// Look for defined custom translations first.
|
// Look for defined custom translations first.
|
||||||
CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations();
|
CustomSQLErrorCodesTranslation[] customTranslations = sqlErrorCodes.getCustomTranslations();
|
||||||
if (customTranslations != null) {
|
if (customTranslations != null) {
|
||||||
for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
|
for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
|
||||||
if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&
|
if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0 &&
|
||||||
|
|
@ -230,43 +234,43 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Next, look for grouped error codes.
|
// Next, look for grouped error codes.
|
||||||
if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
|
if (Arrays.binarySearch(sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
|
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new InvalidResultSetAccessException(task, (sql != null ? sql : ""), sqlEx);
|
return new InvalidResultSetAccessException(task, (sql != null ? sql : ""), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
|
else if (Arrays.binarySearch(sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
|
||||||
logTranslation(task, sql, sqlEx, false);
|
logTranslation(task, sql, sqlEx, false);
|
||||||
return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
|
return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +280,7 @@ public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExcep
|
||||||
// We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.
|
// We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
String codes;
|
String codes;
|
||||||
if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) {
|
if (sqlErrorCodes != null && sqlErrorCodes.isUseSqlStateForTranslation()) {
|
||||||
codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();
|
codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.jdbc.support;
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
@ -159,6 +160,7 @@ public class SQLErrorCodesFactory {
|
||||||
* <p>No need for a database meta-data lookup.
|
* <p>No need for a database meta-data lookup.
|
||||||
* @param databaseName 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
|
||||||
|
* (never {@code null}; potentially empty)
|
||||||
* @throws IllegalArgumentException if the supplied database name is {@code null}
|
* @throws IllegalArgumentException if the supplied database name is {@code null}
|
||||||
*/
|
*/
|
||||||
public SQLErrorCodes getErrorCodes(String databaseName) {
|
public SQLErrorCodes getErrorCodes(String databaseName) {
|
||||||
|
|
@ -195,9 +197,27 @@ public class SQLErrorCodesFactory {
|
||||||
* instance if no {@code SQLErrorCodes} were found.
|
* instance if no {@code SQLErrorCodes} were found.
|
||||||
* @param dataSource the {@code DataSource} identifying the database
|
* @param dataSource the {@code DataSource} identifying the database
|
||||||
* @return the corresponding {@code SQLErrorCodes} object
|
* @return the corresponding {@code SQLErrorCodes} object
|
||||||
|
* (never {@code null}; potentially empty)
|
||||||
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
||||||
*/
|
*/
|
||||||
public SQLErrorCodes getErrorCodes(DataSource dataSource) {
|
public SQLErrorCodes getErrorCodes(DataSource dataSource) {
|
||||||
|
SQLErrorCodes sec = resolveErrorCodes(dataSource);
|
||||||
|
return (sec != null ? sec : new SQLErrorCodes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return {@link SQLErrorCodes} for the given {@link DataSource},
|
||||||
|
* evaluating "databaseProductName" from the
|
||||||
|
* {@link java.sql.DatabaseMetaData}, or {@code null} if case
|
||||||
|
* of a JDBC meta-data access problem.
|
||||||
|
* @param dataSource the {@code DataSource} identifying the database
|
||||||
|
* @return the corresponding {@code SQLErrorCodes} object,
|
||||||
|
* or {@code null} in case of a JDBC meta-data access problem
|
||||||
|
* @since 5.2.9
|
||||||
|
* @see java.sql.DatabaseMetaData#getDatabaseProductName()
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public SQLErrorCodes resolveErrorCodes(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 [" + identify(dataSource) + "]");
|
logger.debug("Looking up default SQLErrorCodes for DataSource [" + identify(dataSource) + "]");
|
||||||
|
|
@ -212,16 +232,16 @@ public class SQLErrorCodesFactory {
|
||||||
if (sec == null) {
|
if (sec == null) {
|
||||||
// We could not find it - got to look it up.
|
// We could not find it - got to look it up.
|
||||||
try {
|
try {
|
||||||
String name = JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName");
|
String name = JdbcUtils.extractDatabaseMetaData(dataSource,
|
||||||
|
DatabaseMetaData::getDatabaseProductName);
|
||||||
if (StringUtils.hasLength(name)) {
|
if (StringUtils.hasLength(name)) {
|
||||||
return registerDatabase(dataSource, name);
|
return registerDatabase(dataSource, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (MetaDataAccessException ex) {
|
catch (MetaDataAccessException ex) {
|
||||||
logger.warn("Error while extracting database name - falling back to empty error codes", ex);
|
logger.warn("Error while extracting database name", ex);
|
||||||
}
|
}
|
||||||
// Fallback is to return an empty SQLErrorCodes instance.
|
return null;
|
||||||
return new SQLErrorCodes();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,15 @@
|
||||||
package org.springframework.jdbc.support;
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
import java.sql.BatchUpdateException;
|
import java.sql.BatchUpdateException;
|
||||||
|
import java.sql.Connection;
|
||||||
import java.sql.DataTruncation;
|
import java.sql.DataTruncation;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import org.springframework.dao.CannotAcquireLockException;
|
import org.springframework.dao.CannotAcquireLockException;
|
||||||
import org.springframework.dao.CannotSerializeTransactionException;
|
import org.springframework.dao.CannotSerializeTransactionException;
|
||||||
|
|
@ -35,6 +40,9 @@ import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rod Johnson
|
* @author Rod Johnson
|
||||||
|
|
@ -79,7 +87,7 @@ public class SQLErrorCodeSQLExceptionTranslatorTests {
|
||||||
|
|
||||||
SQLException dupKeyEx = new SQLException("", "", 10);
|
SQLException dupKeyEx = new SQLException("", "", 10);
|
||||||
DataAccessException dksex = sext.translate("task", "SQL", dupKeyEx);
|
DataAccessException dksex = sext.translate("task", "SQL", dupKeyEx);
|
||||||
assertThat(DataIntegrityViolationException.class.isAssignableFrom(dksex.getClass())).as("Not instance of DataIntegrityViolationException").isTrue();
|
assertThat(DataIntegrityViolationException.class.isInstance(dksex)).as("Not instance of DataIntegrityViolationException").isTrue();
|
||||||
|
|
||||||
// Test fallback. We assume that no database will ever return this error code,
|
// Test fallback. We assume that no database will ever return this error code,
|
||||||
// but 07xxx will be bad grammar picked up by the fallback SQLState translator
|
// but 07xxx will be bad grammar picked up by the fallback SQLState translator
|
||||||
|
|
@ -152,14 +160,13 @@ public class SQLErrorCodeSQLExceptionTranslatorTests {
|
||||||
final SQLErrorCodes customErrorCodes = new SQLErrorCodes();
|
final SQLErrorCodes customErrorCodes = new SQLErrorCodes();
|
||||||
final CustomSQLErrorCodesTranslation customTranslation = new CustomSQLErrorCodesTranslation();
|
final CustomSQLErrorCodesTranslation customTranslation = new CustomSQLErrorCodesTranslation();
|
||||||
|
|
||||||
customErrorCodes.setBadSqlGrammarCodes(new String[] {"1", "2"});
|
customErrorCodes.setBadSqlGrammarCodes("1", "2");
|
||||||
customErrorCodes.setDataIntegrityViolationCodes(new String[] {"3", "4"});
|
customErrorCodes.setDataIntegrityViolationCodes("3", "4");
|
||||||
customTranslation.setErrorCodes(new String[] {"1"});
|
customTranslation.setErrorCodes("1");
|
||||||
customTranslation.setExceptionClass(CustomErrorCodeException.class);
|
customTranslation.setExceptionClass(CustomErrorCodeException.class);
|
||||||
customErrorCodes.setCustomTranslations(new CustomSQLErrorCodesTranslation[] {customTranslation});
|
customErrorCodes.setCustomTranslations(customTranslation);
|
||||||
|
|
||||||
SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator();
|
SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(customErrorCodes);
|
||||||
sext.setSqlErrorCodes(customErrorCodes);
|
|
||||||
|
|
||||||
// Should custom translate this
|
// Should custom translate this
|
||||||
SQLException badSqlEx = new SQLException("", "", 1);
|
SQLException badSqlEx = new SQLException("", "", 1);
|
||||||
|
|
@ -176,4 +183,28 @@ public class SQLErrorCodeSQLExceptionTranslatorTests {
|
||||||
customTranslation.setExceptionClass(String.class));
|
customTranslation.setExceptionClass(String.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dataSourceInitialization() throws Exception {
|
||||||
|
SQLException connectionException = new SQLException();
|
||||||
|
SQLException duplicateKeyException = new SQLException("test", "", 1);
|
||||||
|
|
||||||
|
DataSource dataSource = mock(DataSource.class);
|
||||||
|
given(dataSource.getConnection()).willThrow(connectionException);
|
||||||
|
|
||||||
|
SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator(dataSource);
|
||||||
|
assertThat(sext.translate("test", null, duplicateKeyException)).isNotInstanceOf(DuplicateKeyException.class);
|
||||||
|
|
||||||
|
DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
|
||||||
|
given(databaseMetaData.getDatabaseProductName()).willReturn("Oracle");
|
||||||
|
|
||||||
|
Connection connection = mock(Connection.class);
|
||||||
|
given(connection.getMetaData()).willReturn(databaseMetaData);
|
||||||
|
|
||||||
|
Mockito.reset(dataSource);
|
||||||
|
given(dataSource.getConnection()).willReturn(connection);
|
||||||
|
assertThat(sext.translate("test", null, duplicateKeyException)).isInstanceOf(DuplicateKeyException.class);
|
||||||
|
|
||||||
|
verify(connection).close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2019 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -31,6 +31,7 @@ import org.springframework.core.io.Resource;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -39,6 +40,7 @@ import static org.mockito.Mockito.verify;
|
||||||
* @author Rod Johnson
|
* @author Rod Johnson
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
|
* @author Juergen Hoeller
|
||||||
*/
|
*/
|
||||||
public class SQLErrorCodesFactoryTests {
|
public class SQLErrorCodesFactoryTests {
|
||||||
|
|
||||||
|
|
@ -239,7 +241,11 @@ public class SQLErrorCodesFactoryTests {
|
||||||
|
|
||||||
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
|
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
|
||||||
assertIsEmpty(sec);
|
assertIsEmpty(sec);
|
||||||
|
verify(connection).close();
|
||||||
|
|
||||||
|
reset(connection);
|
||||||
|
sec = SQLErrorCodesFactory.getInstance().resolveErrorCodes(dataSource);
|
||||||
|
assertThat(sec).isNull();
|
||||||
verify(connection).close();
|
verify(connection).close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,12 +258,9 @@ public class SQLErrorCodesFactoryTests {
|
||||||
|
|
||||||
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
|
SQLErrorCodes sec = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
|
||||||
assertIsEmpty(sec);
|
assertIsEmpty(sec);
|
||||||
}
|
|
||||||
|
|
||||||
private void assertIsEmpty(SQLErrorCodes sec) {
|
sec = SQLErrorCodesFactory.getInstance().resolveErrorCodes(dataSource);
|
||||||
// Codes should be empty
|
assertThat(sec).isNull();
|
||||||
assertThat(sec.getBadSqlGrammarCodes().length).isEqualTo(0);
|
|
||||||
assertThat(sec.getDataIntegrityViolationCodes().length).isEqualTo(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SQLErrorCodes getErrorCodesFromDataSource(String productName, SQLErrorCodesFactory factory) throws Exception {
|
private SQLErrorCodes getErrorCodesFromDataSource(String productName, SQLErrorCodesFactory factory) throws Exception {
|
||||||
|
|
@ -270,17 +273,9 @@ public class SQLErrorCodesFactoryTests {
|
||||||
DataSource dataSource = mock(DataSource.class);
|
DataSource dataSource = mock(DataSource.class);
|
||||||
given(dataSource.getConnection()).willReturn(connection);
|
given(dataSource.getConnection()).willReturn(connection);
|
||||||
|
|
||||||
SQLErrorCodesFactory secf = null;
|
SQLErrorCodesFactory secf = (factory != null ? factory : SQLErrorCodesFactory.getInstance());
|
||||||
if (factory != null) {
|
|
||||||
secf = factory;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
secf = SQLErrorCodesFactory.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLErrorCodes sec = secf.getErrorCodes(dataSource);
|
SQLErrorCodes sec = secf.getErrorCodes(dataSource);
|
||||||
|
|
||||||
|
|
||||||
SQLErrorCodes sec2 = secf.getErrorCodes(dataSource);
|
SQLErrorCodes sec2 = secf.getErrorCodes(dataSource);
|
||||||
assertThat(sec).as("Cached per DataSource").isSameAs(sec2);
|
assertThat(sec).as("Cached per DataSource").isSameAs(sec2);
|
||||||
|
|
||||||
|
|
@ -375,4 +370,9 @@ public class SQLErrorCodesFactoryTests {
|
||||||
assertIsEmpty(sec);
|
assertIsEmpty(sec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertIsEmpty(SQLErrorCodes sec) {
|
||||||
|
assertThat(sec.getBadSqlGrammarCodes().length).isEqualTo(0);
|
||||||
|
assertThat(sec.getDataIntegrityViolationCodes().length).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue