Merge pull request #33 from trisberg/SQLException-translation-enhancements
SQL exception translation enhancements
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2012 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator}.
|
||||||
|
*
|
||||||
|
* @author Thomas Risberg
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public class CustomSQLExceptionTranslatorRegistrar implements InitializingBean {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistrar.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map registry to hold custom translators specific databases.
|
||||||
|
* Key is the database product name as defined in the
|
||||||
|
* {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
|
||||||
|
*/
|
||||||
|
private final Map<String, SQLExceptionTranslator> sqlExceptionTranslators =
|
||||||
|
new HashMap<String, SQLExceptionTranslator>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for a Map of translators where the key must be the database name as defined in the
|
||||||
|
* sql-error-codes.xml file. This method is used when this registry is used in an application context.
|
||||||
|
* <p>Note that any existing translators will remain unless there is a match in the database name at which
|
||||||
|
* point the new translator will replace the existing one.
|
||||||
|
*
|
||||||
|
* @param sqlExceptionTranslators
|
||||||
|
*/
|
||||||
|
public void setSqlExceptionTranslators(Map<String, SQLExceptionTranslator> sqlExceptionTranslators) {
|
||||||
|
this.sqlExceptionTranslators.putAll(sqlExceptionTranslators);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Registering custom SQL exception translators for database(s): " +
|
||||||
|
sqlExceptionTranslators.keySet());
|
||||||
|
}
|
||||||
|
for (String dbName : sqlExceptionTranslators.keySet()) {
|
||||||
|
CustomSQLExceptionTranslatorRegistry.getInstance()
|
||||||
|
.registerSqlExceptionTranslator(dbName, sqlExceptionTranslators.get(dbName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2012 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.PatternMatchUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with
|
||||||
|
* specific databases allowing for overriding translation based on values contained in the configuration file
|
||||||
|
* named "sql-error-codes.xml".
|
||||||
|
*
|
||||||
|
* @author Thomas Risberg
|
||||||
|
* @since 3.1
|
||||||
|
* @see SQLErrorCodesFactory
|
||||||
|
*/
|
||||||
|
public class CustomSQLExceptionTranslatorRegistry {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map registry to hold custom translators specific databases.
|
||||||
|
* Key is the database product name as defined in the
|
||||||
|
* {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
|
||||||
|
*/
|
||||||
|
private final Map<String, SQLExceptionTranslator> sqlExceptionTranslatorRegistry =
|
||||||
|
new HashMap<String, SQLExceptionTranslator>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of a single instance so we can return it to classes that request it.
|
||||||
|
*/
|
||||||
|
private static final CustomSQLExceptionTranslatorRegistry instance = new CustomSQLExceptionTranslatorRegistry();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the singleton instance.
|
||||||
|
*/
|
||||||
|
public static CustomSQLExceptionTranslatorRegistry getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of the {@link org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistry} class.
|
||||||
|
* <p>Not public to enforce Singleton design pattern.
|
||||||
|
*/
|
||||||
|
private CustomSQLExceptionTranslatorRegistry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new custom translator for the specified database name.
|
||||||
|
*
|
||||||
|
* @param dbName the database name
|
||||||
|
* @param sqlExceptionTranslator the custom translator
|
||||||
|
*/
|
||||||
|
public void registerSqlExceptionTranslator(String dbName, SQLExceptionTranslator sqlExceptionTranslator) {
|
||||||
|
SQLExceptionTranslator replaced = sqlExceptionTranslatorRegistry.put(dbName, sqlExceptionTranslator);
|
||||||
|
if (replaced != null) {
|
||||||
|
logger.warn("Replacing custom translator '" + replaced +
|
||||||
|
"' for database " + dbName +
|
||||||
|
" with '" + sqlExceptionTranslator + "'");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info("Adding custom translator '" + sqlExceptionTranslator.getClass().getSimpleName() +
|
||||||
|
"' for database " + dbName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLExceptionTranslator findSqlExceptionTranslatorForDatabase(String dbName) {
|
||||||
|
return sqlExceptionTranslatorRegistry.get(dbName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2008 the original author or authors.
|
* Copyright 2002-2012 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.
|
||||||
@@ -104,6 +104,10 @@ public class SQLErrorCodes {
|
|||||||
return customSqlExceptionTranslator;
|
return customSqlExceptionTranslator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) {
|
||||||
|
this.customSqlExceptionTranslator = customSqlExceptionTranslator;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) {
|
public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) {
|
||||||
if (customSqlExceptionTranslatorClass != null) {
|
if (customSqlExceptionTranslatorClass != null) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2010 the original author or authors.
|
* Copyright 2002-2012 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.jdbc.support;
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
@@ -130,7 +131,7 @@ public class SQLErrorCodesFactory {
|
|||||||
logger.warn("Error loading SQL error codes from config file", ex);
|
logger.warn("Error loading SQL error codes from config file", ex);
|
||||||
errorCodes = Collections.emptyMap();
|
errorCodes = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.errorCodesMap = errorCodes;
|
this.errorCodesMap = errorCodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +160,7 @@ public class SQLErrorCodesFactory {
|
|||||||
*/
|
*/
|
||||||
public SQLErrorCodes getErrorCodes(String dbName) {
|
public SQLErrorCodes getErrorCodes(String dbName) {
|
||||||
Assert.notNull(dbName, "Database product name must not be null");
|
Assert.notNull(dbName, "Database product name must not be null");
|
||||||
|
|
||||||
SQLErrorCodes sec = this.errorCodesMap.get(dbName);
|
SQLErrorCodes sec = this.errorCodesMap.get(dbName);
|
||||||
if (sec == null) {
|
if (sec == null) {
|
||||||
for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
|
for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
|
||||||
@@ -170,6 +171,7 @@ public class SQLErrorCodesFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sec != null) {
|
if (sec != null) {
|
||||||
|
checkSqlExceptionTranslatorRegistry(dbName, sec);
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("SQL error codes for '" + dbName + "' found");
|
logger.debug("SQL error codes for '" + dbName + "' found");
|
||||||
}
|
}
|
||||||
@@ -246,4 +248,24 @@ public class SQLErrorCodesFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkSqlExceptionTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) {
|
||||||
|
// Check the custom sql exception translator registry for any entries
|
||||||
|
SQLExceptionTranslator customTranslator =
|
||||||
|
CustomSQLExceptionTranslatorRegistry.getInstance().findSqlExceptionTranslatorForDatabase(dbName);
|
||||||
|
if (customTranslator != null) {
|
||||||
|
if (dbCodes.getCustomSqlExceptionTranslator() != null) {
|
||||||
|
logger.warn("Overriding already defined custom translator '" +
|
||||||
|
dbCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() +
|
||||||
|
" with '" + customTranslator.getClass().getSimpleName() +
|
||||||
|
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() +
|
||||||
|
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
|
||||||
|
}
|
||||||
|
dbCodes.setCustomSqlExceptionTranslator(customTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2012 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.jdbc.support;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.dao.TransientDataAccessResourceException;
|
||||||
|
import org.springframework.jdbc.BadSqlGrammarException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for custom translator.
|
||||||
|
*
|
||||||
|
* @author Thomas Risberg
|
||||||
|
*/
|
||||||
|
public class CustomSQLExceptionTranslatorRegistrarTests {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
new ClassPathXmlApplicationContext("test-custom-translators-context.xml",
|
||||||
|
CustomSQLExceptionTranslatorRegistrarTests.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomErrorCodeTranslation() {
|
||||||
|
|
||||||
|
SQLErrorCodes codes = SQLErrorCodesFactory.getInstance().getErrorCodes("H2");
|
||||||
|
SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator();
|
||||||
|
sext.setSqlErrorCodes(codes);
|
||||||
|
|
||||||
|
DataAccessException exFor4200 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 42000));
|
||||||
|
assertNotNull("Should have been translated", exFor4200);
|
||||||
|
assertTrue("Should have been instance of BadSqlGrammarException",
|
||||||
|
BadSqlGrammarException.class.isAssignableFrom(exFor4200.getClass()));
|
||||||
|
|
||||||
|
DataAccessException exFor2 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 2));
|
||||||
|
assertNotNull("Should have been translated", exFor2);
|
||||||
|
assertTrue("Should have been instance of TransientDataAccessResourceException",
|
||||||
|
TransientDataAccessResourceException.class.isAssignableFrom(exFor2.getClass()));
|
||||||
|
|
||||||
|
DataAccessException exFor3 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 3));
|
||||||
|
assertNull("Should not have been translated", exFor3);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||||
|
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
|
||||||
|
|
||||||
|
<jdbc:embedded-database id="dataSource" type="H2"/>
|
||||||
|
|
||||||
|
<bean class="org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistrar">
|
||||||
|
<property name="sqlExceptionTranslators">
|
||||||
|
<map>
|
||||||
|
<entry key="H2">
|
||||||
|
<bean class="org.springframework.jdbc.support.CustomSqlExceptionTranslator"/>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2012 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.dao;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception to be thrown on a query timeout. This could have different causes depending on
|
||||||
|
* the database API in use but most likely thrown after the database interrupts or stops
|
||||||
|
* the processing of a query before it has completed.
|
||||||
|
*
|
||||||
|
* <p>This exception can be thrown by user code trapping the native database exception or
|
||||||
|
* by exception translation.
|
||||||
|
*
|
||||||
|
* @author Thomas Risberg
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public class QueryTimeoutException extends TransientDataAccessException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for QueryTimeoutException.
|
||||||
|
* @param msg the detail message
|
||||||
|
*/
|
||||||
|
public QueryTimeoutException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for QueryTimeoutException.
|
||||||
|
* @param msg the detail message
|
||||||
|
* @param cause the root cause from the data access API in use
|
||||||
|
*/
|
||||||
|
public QueryTimeoutException(String msg, Throwable cause) {
|
||||||
|
super(msg, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user