View Javadoc
1   /*
2    * Copyright 2002-2014 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.HashSet;
21  import java.util.Set;
22  
23  import org.springframework.dao.ConcurrencyFailureException;
24  import org.springframework.dao.DataAccessException;
25  import org.springframework.dao.DataAccessResourceFailureException;
26  import org.springframework.dao.DataIntegrityViolationException;
27  import org.springframework.dao.QueryTimeoutException;
28  import org.springframework.dao.TransientDataAccessResourceException;
29  import org.springframework.jdbc.BadSqlGrammarException;
30  
31  /**
32   * {@link SQLExceptionTranslator} implementation that analyzes the SQL state in
33   * the {@link SQLException} based on the first two digits (the SQL state "class").
34   * Detects standard SQL state values and well-known vendor-specific SQL states.
35   *
36   * <p>Not able to diagnose all problems, but is portable between databases and
37   * does not require special initialization (no database vendor detection, etc.).
38   * For more precise translation, consider {@link SQLErrorCodeSQLExceptionTranslator}.
39   *
40   * @author Rod Johnson
41   * @author Juergen Hoeller
42   * @author Thomas Risberg
43   * @see java.sql.SQLException#getSQLState()
44   * @see SQLErrorCodeSQLExceptionTranslator
45   */
46  public class SQLStateSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator {
47  
48  	private static final Set<String> BAD_SQL_GRAMMAR_CODES = new HashSet<String>(8);
49  
50  	private static final Set<String> DATA_INTEGRITY_VIOLATION_CODES = new HashSet<String>(8);
51  
52  	private static final Set<String> DATA_ACCESS_RESOURCE_FAILURE_CODES = new HashSet<String>(8);
53  
54  	private static final Set<String> TRANSIENT_DATA_ACCESS_RESOURCE_CODES = new HashSet<String>(8);
55  
56  	private static final Set<String> CONCURRENCY_FAILURE_CODES = new HashSet<String>(4);
57  
58  
59  	static {
60  		BAD_SQL_GRAMMAR_CODES.add("07");	// Dynamic SQL error
61  		BAD_SQL_GRAMMAR_CODES.add("21");	// Cardinality violation
62  		BAD_SQL_GRAMMAR_CODES.add("2A");	// Syntax error direct SQL
63  		BAD_SQL_GRAMMAR_CODES.add("37");	// Syntax error dynamic SQL
64  		BAD_SQL_GRAMMAR_CODES.add("42");	// General SQL syntax error
65  		BAD_SQL_GRAMMAR_CODES.add("65");	// Oracle: unknown identifier
66  
67  		DATA_INTEGRITY_VIOLATION_CODES.add("01");	// Data truncation
68  		DATA_INTEGRITY_VIOLATION_CODES.add("02");	// No data found
69  		DATA_INTEGRITY_VIOLATION_CODES.add("22");	// Value out of range
70  		DATA_INTEGRITY_VIOLATION_CODES.add("23");	// Integrity constraint violation
71  		DATA_INTEGRITY_VIOLATION_CODES.add("27");	// Triggered data change violation
72  		DATA_INTEGRITY_VIOLATION_CODES.add("44");	// With check violation
73  
74  		DATA_ACCESS_RESOURCE_FAILURE_CODES.add("08");	 // Connection exception
75  		DATA_ACCESS_RESOURCE_FAILURE_CODES.add("53");	 // PostgreSQL: insufficient resources (e.g. disk full)
76  		DATA_ACCESS_RESOURCE_FAILURE_CODES.add("54");	 // PostgreSQL: program limit exceeded (e.g. statement too complex)
77  		DATA_ACCESS_RESOURCE_FAILURE_CODES.add("57");	 // DB2: out-of-memory exception / database not started
78  		DATA_ACCESS_RESOURCE_FAILURE_CODES.add("58");	 // DB2: unexpected system error
79  
80  		TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JW");	 // Sybase: internal I/O error
81  		TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("JZ");	 // Sybase: unexpected I/O error
82  		TRANSIENT_DATA_ACCESS_RESOURCE_CODES.add("S1");	 // DB2: communication failure
83  
84  		CONCURRENCY_FAILURE_CODES.add("40");	// Transaction rollback
85  		CONCURRENCY_FAILURE_CODES.add("61");	// Oracle: deadlock
86  	}
87  
88  
89  	@Override
90  	protected DataAccessException doTranslate(String task, String sql, SQLException ex) {
91  		// First, the getSQLState check...
92  		String sqlState = getSqlState(ex);
93  		if (sqlState != null && sqlState.length() >= 2) {
94  			String classCode = sqlState.substring(0, 2);
95  			if (logger.isDebugEnabled()) {
96  				logger.debug("Extracted SQL state class '" + classCode + "' from value '" + sqlState + "'");
97  			}
98  			if (BAD_SQL_GRAMMAR_CODES.contains(classCode)) {
99  				return new BadSqlGrammarException(task, sql, ex);
100 			}
101 			else if (DATA_INTEGRITY_VIOLATION_CODES.contains(classCode)) {
102 				return new DataIntegrityViolationException(buildMessage(task, sql, ex), ex);
103 			}
104 			else if (DATA_ACCESS_RESOURCE_FAILURE_CODES.contains(classCode)) {
105 				return new DataAccessResourceFailureException(buildMessage(task, sql, ex), ex);
106 			}
107 			else if (TRANSIENT_DATA_ACCESS_RESOURCE_CODES.contains(classCode)) {
108 				return new TransientDataAccessResourceException(buildMessage(task, sql, ex), ex);
109 			}
110 			else if (CONCURRENCY_FAILURE_CODES.contains(classCode)) {
111 				return new ConcurrencyFailureException(buildMessage(task, sql, ex), ex);
112 			}
113 		}
114 
115 		// For MySQL: exception class name indicating a timeout?
116 		// (since MySQL doesn't throw the JDBC 4 SQLTimeoutException)
117 		if (ex.getClass().getName().contains("Timeout")) {
118 			return new QueryTimeoutException(buildMessage(task, sql, ex), ex);
119 		}
120 
121 		// Couldn't resolve anything proper - resort to UncategorizedSQLException.
122 		return null;
123 	}
124 
125 	/**
126 	 * Gets the SQL state code from the supplied {@link SQLException exception}.
127 	 * <p>Some JDBC drivers nest the actual exception from a batched update, so we
128 	 * might need to dig down into the nested exception.
129 	 * @param ex the exception from which the {@link SQLException#getSQLState() SQL state}
130 	 * is to be extracted
131 	 * @return the SQL state code
132 	 */
133 	private String getSqlState(SQLException ex) {
134 		String sqlState = ex.getSQLState();
135 		if (sqlState == null) {
136 			SQLException nestedEx = ex.getNextException();
137 			if (nestedEx != null) {
138 				sqlState = nestedEx.getSQLState();
139 			}
140 		}
141 		return sqlState;
142 	}
143 
144 }