Exception Handling
异常处理
Exception handling is such an important issue that we should develop a consistent strategy for it before we begin to implement our framework proper.
异常处理是如此的重要,在我们开始实现我们自己的框架特性时,我们必须为它开发一个持久的策略。
The JDBC API is an object lesson in how not to use exceptions. As well as ensuring that we release resources even if we encounter errors, our abstraction layer should provide a better exception handling model for application code than JDBC does.
jdbc api是一个对象的教训在错误的用异常处理。确保我们释放资源即使我们遇到了错误。对比jdbc代码而言,应用代码的抽象层必须提供一个更好的异常处理模型。
JDBC uses a single exception class—java.lang.SQLException—for all problems except data truncation. Catching a SQLException in itself provides no more information than "something went wrong". As we've seen, it's only possible to distinguish between problems by examining potentially vendor-specific codes included in SQL exceptions.
jdbc使用了单个的异常类 --- java.lang.SQXException -- 对于所有的问题,除了数据截断。自我捕获一个sqlException提供了某些发生了错误就再也没有更多的信息了。正如我们所看见的,它仅仅可能区别问题靠检查潜在的厂商-详细的代码包含在sql Exception中。
JDBC could in fact have offered more sophisticated error handling while retaining portability between databases. The following are just a few of the errors that are meaningful in any RDBMS:
jdbc 能事实上提供更精密的错误处理当保留数据库之间的可移植性。下面的一些错误是有意义的在任何rdbms中:
-
Grammatical error in SQL statement issued using JDBC
-
语法错误在sql语句发布使用时
-
Data integrity constraint violation
-
违反数据完整性约束
-
Attempt to set the value of a SQL bind variable to an incorrect type
-
尝试设置sql值的绑定变量到一个不正确的类型
Such standard problems could—and should—have been modeled as individual subclasses of Java.lang.SQLException. In our abstraction framework, we will provide a richer exception hierarchy that does use individual exception classes.
这样标准的问题可能-应该-已经被建模做为java.lang.sqlException的一个子类。在我们抽象框架中,我们将提供一个丰富的异常层次,相比用这个单个的异常子类。
We'll also address two other issues with JDBC exception handling.
我们也将用jdbc异常处理强调2个其他的问题。
-
We don't want to tie code using our abstraction layer to JDBC. Our abstraction layer is primarily intended for use to implement the Data-Access Object pattern. If code using DAOs ever needs to catch resource-specific exceptions, such as SQLExceptions, the decoupling of business logic and data access implementation that the DAO pattern exists to provide is lost. (Exceptions thrown are an integral part of method signatures.) So while the JDBC API, reasonably enough, uses JDBC-specific exceptions, we won't tie our exceptions to JDBC.
-
我们不想用我们的抽象层去关联jdbc代码。我们的抽象层主要目的为用做去实现数据访问对象模式。如果代码用dao需要捕获特定资源的异常,例如sqlexception,业务逻辑和数据访问实现的解耦用dao模式完全不行。(异常抛出是方法声明的一部分)。所以当jdbc api,足够的合理,用jdbc指定的异常,我们将依赖到jdbc的异常。
-
Following our discussion of checked and runtime exceptions in Chapter 4, we will make our API much easier to use by making all exceptions runtime, rather than checked exceptions. JDO takes this approach with good results. Using runtime exceptions, callers need catch only those exceptions (if any) that they may be able to recover from. This models JDBC (and other data-access) usage well: JDBC exceptions are often unrecoverable. For example, if a SQL query contained an invalid column name, there's no point catching it and retrying. We want the fatal error logged, but need to correct the offending application code.
-
下面我们讨论的检查和运行是异常在第4章,我们将是我们的api更容易去用,靠使所有的异常编程运行时的,相比检查异常。jdo采用这种方法具有靓号的结果。用运行时异常,调用者需要捕获仅仅这些异常(如果有的话),他们可能从中恢复的。jdbc模型(和其他的数据访问)使用惯例:jdbc异常经常不可恢复的。例如,如果一个sql产线包含了一个不可用的列名,这是毫无意义的捕获他并且重试。我们想要致命的错误日志,但是不需要改成这个提供的应用代码。
-
If we're running the query from an EJB, we can simply let the EJB container catch the runtime exception and roll back the current transaction. If we caught a checked exception in the EJB implementation we'd need to roll back the transaction ourselves, or throw a runtime exception that would cause the container to do so. In this example, using a checked exception would allow more scope for application code to cause incorrect behavior. Likewise, failure to connect to a database is probably fatal. In contrast, a recoverable error that we might want to catch would be an optimistic locking violation.
-
如果我们从ejb中运行查询,我们能简化让ejb容器捕获运行时异常并且回滚当前事物。如果我们在rjb实现中捕获了一个检查异常,我们需要自己回滚事物,或者抛出一个运行时异常将引起容器去做些什么。在这个例子中,用一个检查异常将允许更多的应用范围的代码导致不正确的行为。同样的,失败连接一个数据库可能是致命的。相反的,一个可恢复的错误,我们想去捕获的将是一个违反了乐观锁。
A Generic Data-Access Exception Hierarchy
一个通用数据访问层次
We can meet all our exception handling goals by creating a generic data-access exception hierarchy. This won't be limited to use with JDBC, although some JDBC-specific exceptions will be derived from generic exceptions within it. This exception hierarchy will enable code using DAOs to handle exceptions in a database-agnostic way.
我们能满足我们所有的异常处理目标,创建一个通用的数据异常层次。这不会被限制使用jbdc,即使一些特定的jdbc异常来源与通用的异常。这个异常层次将可能是代码用dao模式去处理异常在一个不依靠数据库的方式中。
The root of all data-access exceptions is the abstract DataAccessException class, which extends the NestedRuntimeException class discussed in Chapter 4. Using the NestedRuntimeException superclass enables us to preserve the stack trace of any exceptions (such as SQLExceptions) we may need to wrap.
所有数据访问异常的根是抽象DataAccessException类,继承了在第4章讨论的NestedRuntimeException 类。用NestedRuntimeException 父类可以使我们去保留任何我们需要去包装的异常(例如sqlexception)的堆栈跟踪。
Unlike SQLException, DataAccessException has several subclasses that indicate specific data-access problems. The direct subclasses are:
不像sqlexception,DataAccessException有几个子类暗示了特定数据访问问题。直接子类是:
-
DataAccessResourceFailureException
Complete failure to access the resource in question. In JDBC implementation, this would result from failure to get a connection from a datasource. However, such errors are meaningful for any persistence strategy. -
完全的失败去访问资源是在讨论中的。在jdbc实现中,这将导致连接失败。然而,这些错误是有意义的对任何持久策略。
-
CleanupFailureDataAccessException
Failure to clean up (for example, by closing a JDBC Connection) after successfully completing an operation. In some cases we might want to treat this as a recoverable error, preventing the current transaction from being rolled back (for example, we know that any update succeeded). -
失败去清理(例如,关闭jdbc连接)在成功的完成了一个操作后。在某些情形下我们可能想去对待这个做为一个不可恢复的错误,防止当前事物被回滚(例如,我们知道任何成功的更新)。
-
DataIntegrityViolationException
Thrown when an attempted update is rejected by the database because it would have violated data integrity. -
抛出一个当试图更新被数据库拒绝的异常,因为它将违反数据库的完整性。
-
InvalidDataAccessApiUsageException
This exception indicates not a problem from the underlying resource (such as a SQLException), but incorrect usage of the data-access API. The JDBC abstraction layer discussed below throws this exception when it is used incorrectly. -
异常从底层的资源表明不是一个问题(例如sqlexception),但是不正确的数据访问api的用法。jdbc抽象层在下面的讨论了当它被不正确的用抛出了异常。
-
InvalidDataAccessResourceUsageException
This exception indicates that the data-access resource (such as an RDBMS) was used incorrectly. For example, our JDBC abstraction layer will throw a subclass of this exception on an attempt to execute invalid SQL. -
数据访问资源被不正确的用。
-
OptimisticLockingViolationException
This indicates an optimistic locking violation and is thrown when a competing update is detected. This exception will normally be thrown by a Data-Access Object, rather than an underlying API like JDBC. -
违反了乐观锁。
-
DeadlockLoserDataAccessException
Indicates that the current operation was a deadlock loser, causing it to be rejected by the database. -
当前操作是一个死锁,引起了它被数据库拒绝。
-
UncategorizedDataAccessException
An exception that doesn't fit within the recognized categories listed above. This exception is abstract, so resource-specific exceptions will extend it carrying additional information. Our JDBC abstraction layer will throw this exception when it catches a SQLException it cannot classify. -
一个异常不适合在上面列出的类别中出现。
All these concepts are meaningful for any persistence strategy, not just JDBC. Generic exceptions will include nested root causes, such as SQLExceptions, ensuring that no information is lost and ensuring that all available information can be logged.
所有的这些概念是有意义的为持久策略,并不是只为jdbc。通用异常将包含内在跟异常,例如sqlexception,确保没有更多的信息丢失,确保所有的变量信息能被记录。
The following UML class diagram illustrates our exception hierarchy, contained in the com.interface21.dao package. Note how some JDBC-specific exceptions in the com.interface21.jdbc.core package (shown below the horizontal line) extend generic exceptions, enabling the maximum information to be included about a problem without making calling code dependent on JDBC. Calling code will normally catch generic exceptions, although JDBC-specific DAO implementations can catch JDBC-specific subclasses they understand, while letting fatal errors propagate to business objects that use them.
下面的uml类图展现了我们的异常层次,包含在com.interface21.dao 包中。注意一些jdbc特定的异常在com.interface21.jdbc.core包中(显示在下面的水平线上).
Although this exception hierarchy may appear complex, it can help to deliver a great simplification in application code. All these exception classes will be thrown by our framework; application code need only catch those it considers recoverable. Most application code won't catch any of these exceptions.
即使异常层次可能显示的复杂,他能帮助提供一个极大的简化应用代码。所有的这些异常类将被抛出被我们的框架;应用代码需要仅仅捕获这些它认为可恢复的。大部分的应用代码不能捕获这些异常。
This hierarchy automatically gives us much more useful information than a SQLException and makes application code that needs to work with exceptions much more readable. While we can choose to catch the base DataAccessException if we want to know if anything went wrong (in the same way as we catch a SQLException), it will be more useful in most cases to catch only a particular subclass.
这些异常自动的给我们更多的有用的信息比起sqlexception,是的应用代码需要与异常更可读的工作。当我们选择图捕获基于DataAccessException ,如果我们想知道是否出现差错(用同样的方式我们捕获一个sqlexception),它将更有用在大部分的场合去捕获仅仅一个特定的子类。
Consider a plausible requirement to perform an update operation and apply a recovery strategy if the operation resulted in a data integrity violation. In an RDBMS context, the operation might have attempted to set a non-nullable column's value to null. Using the exception hierarchy we've just examined, all we need to do is catch com.interface21.dao.DataIntegrityViolationException. All other exceptions can be ignored, as they're unchecked. The following code clearly conveys the recovery requirement just described. As a bonus, it isn't dependent on JDBC:
考虑一个合理的要求去执行一个更新操作,应用一个恢复策略如果操作结果导致一个数据约束错误。在一个rdbms上下文中,操作可能尝试去设置一个非空列的值为空。用我们检查的异常层次,所有我们需要去做的是捕获com.interface21.dao.DataIntegrityViolationException。所有其他的异常能被忽略,因为他们是未检查的。下面的代码清楚的传达了刚刚描述的需求。做为奖励,他不依赖与jdbc:
try {
//do data operation using JDBC abstraction layer
}
catch (DataIntegrityViolationException ex) {
// Apply recovery strategy
}
Now consider how we would need to do this using JDBC, without an abstraction layer. As SQLExceptions are checked, we can't let exceptions we're not interested in through. The only way to establish whether we are interested in the exception is to examine its SQLState string or vendor code. In the following example we check the SQLState string. As this may be null, we need to check it's non-null before examining it:
现在考虑我们如何用jdbc,没有一个抽象层。sqlexception是检查的,我们不能让我们不感兴趣的异常通过。仅仅的发那个是去简历是否我们感兴趣的异常是去检查它的sql状态字符串或者厂商代码。在下面的例子中我们能检查sqlstate字符串。这可能是null,我们需要检查非空性:
try {
//do data operation using JDBC
}
catch (SQLException ex) (
boolean recovered = false;
String sqlstate = sqlex.getSQLStat();
if (sqlstate != null) {
String classCode = sqlstate.substring(0, 2);
if ("23".equals(classCode) ||
"27".equals(classCode) ||
"44".equals(classCode)) {
// Apply recovery strategy
recovered = true;
}
}
if (!recovered)
throw new ApplicationSpecificException("Other SQL exception", ex);
}
// Finally block omitted
There's no doubt which is the simpler, more readable, and more maintainable approach. Not only is the second approach far more complex, it makes the calling code dependent on intimate details of the JDBC API.
毫无疑问这是简单的,更可读的,可维护的途径。不仅仅是第2个途径更复杂,它使得调用代码依赖与jdbcapi。