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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -104,6 +104,10 @@ public class SQLErrorCodes {
|
||||
return customSqlExceptionTranslator;
|
||||
}
|
||||
|
||||
public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) {
|
||||
this.customSqlExceptionTranslator = customSqlExceptionTranslator;
|
||||
}
|
||||
|
||||
public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) {
|
||||
if (customSqlExceptionTranslatorClass != null) {
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
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;
|
||||
@@ -130,7 +131,7 @@ public class SQLErrorCodesFactory {
|
||||
logger.warn("Error loading SQL error codes from config file", ex);
|
||||
errorCodes = Collections.emptyMap();
|
||||
}
|
||||
|
||||
|
||||
this.errorCodesMap = errorCodes;
|
||||
}
|
||||
|
||||
@@ -159,7 +160,7 @@ public class SQLErrorCodesFactory {
|
||||
*/
|
||||
public SQLErrorCodes getErrorCodes(String dbName) {
|
||||
Assert.notNull(dbName, "Database product name must not be null");
|
||||
|
||||
|
||||
SQLErrorCodes sec = this.errorCodesMap.get(dbName);
|
||||
if (sec == null) {
|
||||
for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
|
||||
@@ -170,6 +171,7 @@ public class SQLErrorCodesFactory {
|
||||
}
|
||||
}
|
||||
if (sec != null) {
|
||||
checkSqlExceptionTranslatorRegistry(dbName, sec);
|
||||
if (logger.isDebugEnabled()) {
|
||||
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