Skip to content

Commit f6c7d99

Browse files
author
Thomas Risberg
committed
Improvements for registering custom SQL exception translators in app contexts.
Adding a static CustomSQLExceptionTranslatorRegistry and a CustomSQLExceptionTranslatorRegistrar that can be used to register custom SQLExceptionTranslator implementations for specific databases from any application context. This can be used in application contexts like this: <bean class="org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistrar"> <property name="sqlExceptionTranslators"> <map> <entry key="H2"> <bean class="com.yourcompany.data.CustomSqlExceptionTranslator"/> </entry> </map> </property> </bean> Issue: SPR-7675
1 parent e202d89 commit f6c7d99

File tree

6 files changed

+287
-4
lines changed

6 files changed

+287
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.support;
18+
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
26+
import org.springframework.beans.factory.InitializingBean;
27+
28+
/**
29+
* Registry for registering custom {@link org.springframework.jdbc.support.SQLExceptionTranslator}.
30+
*
31+
* @author Thomas Risberg
32+
* @since 3.1
33+
*/
34+
public class CustomSQLExceptionTranslatorRegistrar implements InitializingBean {
35+
36+
private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistrar.class);
37+
38+
/**
39+
* Map registry to hold custom translators specific databases.
40+
* Key is the database product name as defined in the
41+
* {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
42+
*/
43+
private final Map<String, SQLExceptionTranslator> sqlExceptionTranslators =
44+
new HashMap<String, SQLExceptionTranslator>();
45+
46+
/**
47+
* Setter for a Map of translators where the key must be the database name as defined in the
48+
* sql-error-codes.xml file. This method is used when this registry is used in an application context.
49+
* <p>Note that any existing translators will remain unless there is a match in the database name at which
50+
* point the new translator will replace the existing one.
51+
*
52+
* @param sqlExceptionTranslators
53+
*/
54+
public void setSqlExceptionTranslators(Map<String, SQLExceptionTranslator> sqlExceptionTranslators) {
55+
this.sqlExceptionTranslators.putAll(sqlExceptionTranslators);
56+
}
57+
58+
public void afterPropertiesSet() throws Exception {
59+
if (logger.isDebugEnabled()) {
60+
logger.debug("Registering custom SQL exception translators for database(s): " +
61+
sqlExceptionTranslators.keySet());
62+
}
63+
for (String dbName : sqlExceptionTranslators.keySet()) {
64+
CustomSQLExceptionTranslatorRegistry.getInstance()
65+
.registerSqlExceptionTranslator(dbName, sqlExceptionTranslators.get(dbName));
66+
}
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.support;
18+
19+
import java.util.Collections;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.WeakHashMap;
23+
import javax.sql.DataSource;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
28+
import org.springframework.beans.BeansException;
29+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
30+
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
31+
import org.springframework.core.io.ClassPathResource;
32+
import org.springframework.core.io.Resource;
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.PatternMatchUtils;
35+
36+
/**
37+
* Registry for custom {@link org.springframework.jdbc.support.SQLExceptionTranslator} instances associated with
38+
* specific databases allowing for overriding translation based on values contained in the configuration file
39+
* named "sql-error-codes.xml".
40+
*
41+
* @author Thomas Risberg
42+
* @since 3.1
43+
* @see SQLErrorCodesFactory
44+
*/
45+
public class CustomSQLExceptionTranslatorRegistry {
46+
47+
private static final Log logger = LogFactory.getLog(CustomSQLExceptionTranslatorRegistry.class);
48+
49+
/**
50+
* Map registry to hold custom translators specific databases.
51+
* Key is the database product name as defined in the
52+
* {@link org.springframework.jdbc.support.SQLErrorCodesFactory}.
53+
*/
54+
private final Map<String, SQLExceptionTranslator> sqlExceptionTranslatorRegistry =
55+
new HashMap<String, SQLExceptionTranslator>();
56+
57+
58+
/**
59+
* Keep track of a single instance so we can return it to classes that request it.
60+
*/
61+
private static final CustomSQLExceptionTranslatorRegistry instance = new CustomSQLExceptionTranslatorRegistry();
62+
63+
64+
/**
65+
* Return the singleton instance.
66+
*/
67+
public static CustomSQLExceptionTranslatorRegistry getInstance() {
68+
return instance;
69+
}
70+
71+
72+
/**
73+
* Create a new instance of the {@link org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistry} class.
74+
* <p>Not public to enforce Singleton design pattern.
75+
*/
76+
private CustomSQLExceptionTranslatorRegistry() {
77+
}
78+
79+
/**
80+
* Register a new custom translator for the specified database name.
81+
*
82+
* @param dbName the database name
83+
* @param sqlExceptionTranslator the custom translator
84+
*/
85+
public void registerSqlExceptionTranslator(String dbName, SQLExceptionTranslator sqlExceptionTranslator) {
86+
SQLExceptionTranslator replaced = sqlExceptionTranslatorRegistry.put(dbName, sqlExceptionTranslator);
87+
if (replaced != null) {
88+
logger.warn("Replacing custom translator '" + replaced +
89+
"' for database " + dbName +
90+
" with '" + sqlExceptionTranslator + "'");
91+
}
92+
else {
93+
logger.info("Adding custom translator '" + sqlExceptionTranslator.getClass().getSimpleName() +
94+
"' for database " + dbName);
95+
}
96+
}
97+
98+
public SQLExceptionTranslator findSqlExceptionTranslatorForDatabase(String dbName) {
99+
return sqlExceptionTranslatorRegistry.get(dbName);
100+
}
101+
102+
}

org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodes.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -104,6 +104,10 @@ public SQLExceptionTranslator getCustomSqlExceptionTranslator() {
104104
return customSqlExceptionTranslator;
105105
}
106106

107+
public void setCustomSqlExceptionTranslator(SQLExceptionTranslator customSqlExceptionTranslator) {
108+
this.customSqlExceptionTranslator = customSqlExceptionTranslator;
109+
}
110+
107111
public void setCustomSqlExceptionTranslatorClass(Class customSqlExceptionTranslatorClass) {
108112
if (customSqlExceptionTranslatorClass != null) {
109113
try {

org.springframework.jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodesFactory.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.jdbc.support;
1818

1919
import java.util.Collections;
20+
import java.util.HashMap;
2021
import java.util.Map;
2122
import java.util.WeakHashMap;
2223
import javax.sql.DataSource;
@@ -130,7 +131,7 @@ protected SQLErrorCodesFactory() {
130131
logger.warn("Error loading SQL error codes from config file", ex);
131132
errorCodes = Collections.emptyMap();
132133
}
133-
134+
134135
this.errorCodesMap = errorCodes;
135136
}
136137

@@ -159,7 +160,7 @@ protected Resource loadResource(String path) {
159160
*/
160161
public SQLErrorCodes getErrorCodes(String dbName) {
161162
Assert.notNull(dbName, "Database product name must not be null");
162-
163+
163164
SQLErrorCodes sec = this.errorCodesMap.get(dbName);
164165
if (sec == null) {
165166
for (SQLErrorCodes candidate : this.errorCodesMap.values()) {
@@ -170,6 +171,7 @@ public SQLErrorCodes getErrorCodes(String dbName) {
170171
}
171172
}
172173
if (sec != null) {
174+
checkSqlExceptionTranslatorRegistry(dbName, sec);
173175
if (logger.isDebugEnabled()) {
174176
logger.debug("SQL error codes for '" + dbName + "' found");
175177
}
@@ -246,4 +248,24 @@ public SQLErrorCodes registerDatabase(DataSource dataSource, String dbName) {
246248
}
247249
}
248250

251+
private void checkSqlExceptionTranslatorRegistry(String dbName, SQLErrorCodes dbCodes) {
252+
// Check the custom sql exception translator registry for any entries
253+
SQLExceptionTranslator customTranslator =
254+
CustomSQLExceptionTranslatorRegistry.getInstance().findSqlExceptionTranslatorForDatabase(dbName);
255+
if (customTranslator != null) {
256+
if (dbCodes.getCustomSqlExceptionTranslator() != null) {
257+
logger.warn("Overriding already defined custom translator '" +
258+
dbCodes.getCustomSqlExceptionTranslator().getClass().getSimpleName() +
259+
" with '" + customTranslator.getClass().getSimpleName() +
260+
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
261+
}
262+
else {
263+
logger.info("Using custom translator '" + customTranslator.getClass().getSimpleName() +
264+
"' found in the CustomSQLExceptionTranslatorRegistry for database " + dbName);
265+
}
266+
dbCodes.setCustomSqlExceptionTranslator(customTranslator);
267+
}
268+
269+
}
270+
249271
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.support;
18+
19+
import java.sql.SQLException;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
26+
import org.springframework.context.support.ClassPathXmlApplicationContext;
27+
import org.springframework.dao.DataAccessException;
28+
import org.springframework.dao.TransientDataAccessResourceException;
29+
import org.springframework.jdbc.BadSqlGrammarException;
30+
31+
import static org.junit.Assert.*;
32+
33+
/**
34+
* Tests for custom translator.
35+
*
36+
* @author Thomas Risberg
37+
*/
38+
public class CustomSQLExceptionTranslatorRegistrarTests {
39+
40+
@Before
41+
public void setUp() {
42+
new ClassPathXmlApplicationContext("test-custom-translators-context.xml",
43+
CustomSQLExceptionTranslatorRegistrarTests.class);
44+
}
45+
46+
@Test
47+
public void testCustomErrorCodeTranslation() {
48+
49+
SQLErrorCodes codes = SQLErrorCodesFactory.getInstance().getErrorCodes("H2");
50+
SQLErrorCodeSQLExceptionTranslator sext = new SQLErrorCodeSQLExceptionTranslator();
51+
sext.setSqlErrorCodes(codes);
52+
53+
DataAccessException exFor4200 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 42000));
54+
assertNotNull("Should have been translated", exFor4200);
55+
assertTrue("Should have been instance of BadSqlGrammarException",
56+
BadSqlGrammarException.class.isAssignableFrom(exFor4200.getClass()));
57+
58+
DataAccessException exFor2 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 2));
59+
assertNotNull("Should have been translated", exFor2);
60+
assertTrue("Should have been instance of TransientDataAccessResourceException",
61+
TransientDataAccessResourceException.class.isAssignableFrom(exFor2.getClass()));
62+
63+
DataAccessException exFor3 = sext.doTranslate("", "", new SQLException("Ouch", "42000", 3));
64+
assertNull("Should not have been translated", exFor3);
65+
}
66+
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<beans xmlns="http://www.springframework.org/schema/beans"
3+
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
6+
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">
7+
8+
<jdbc:embedded-database id="dataSource" type="H2"/>
9+
10+
<bean class="org.springframework.jdbc.support.CustomSQLExceptionTranslatorRegistrar">
11+
<property name="sqlExceptionTranslators">
12+
<map>
13+
<entry key="H2">
14+
<bean class="org.springframework.jdbc.support.CustomSqlExceptionTranslator"/>
15+
</entry>
16+
</map>
17+
</property>
18+
</bean>
19+
20+
</beans>

0 commit comments

Comments
 (0)