Merge pull request #33 from trisberg/SQLException-translation-enhancements

SQL exception translation enhancements
This commit is contained in:
jhoeller
2012-02-12 07:19:21 -08:00
7 changed files with 336 additions and 4 deletions

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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);
}
}