JDBC 抽象和数据存储异常层次
数据访问是Spring 的另一个闪光点。
JDBC 提供了还算不错的数据库抽象,但是需要用痛苦的API。这些问题包括:
需要冗长的错误处理代码来确保ResultSets,Statements以及(最重要的)Connections在使用后关闭。这意味着对JDBC的正确使用可以快速地导致大量的代码量。它还是一个常见的错误来源。Connection leak可以在有负载的情况下快速宕掉应用程序。
SQLException相对来说不能说明任何问题。JDBC不提供异常的层次,而是用抛出SQLException来响应所有的错误。找出到底哪里出错了——例如,问题是死锁还是无效的SQL?——要去检查SQLState或错误代码。这意味着这些值在数据库之间是变化的。
Spring用两种方法解决这些问题:
提供API,把冗长乏味和容易出错的异常处理从程序代码移到框架之中。框架处理所有的异常处理;程序代码能够集中精力于编写恰当的SQL和提取结果上。
为你本要处理SQLException程序代码提供有意义的异常层次。当Spring第一次从数据源取得一个连接时,它检查元数据以确定数据库。它使用这些信息把SQLException映射为自己从org.springframework.dao.DataAccessException派生下来的类层次中正确的异常。因而你的代码可以与有意义的异常打交道,并且不需要为私有的SQLState或者错误码担心。Spring的数据访问异常不是JDBC特有的,因而你的DAO并不一定会因为它们可能抛出的异常而绑死在JDBC上。
Spring提供两层JDBC API。第一个时,在org.springframework.jdbc.core包中,使用回调机制移动控制权——并且因而把错误处理和连接获取和释放——从程序的代码移到了框架之中。这是一种不同的Inversion of Control,但是和用于配置管理的几乎有同等重要的意义。
Spring使用类似的回调机制关注其他包含特殊获取和清理资源步骤的API,例如JDO(获取和释放是由PersistenceManager完成的),事务管理(使用JTA)和JNDI。Spring中完成这些回调的类被称作template。
例如,Spring的JdbcTemplate对象能够用于执行SQL查询并且在如下的列表中保存结果:
代码: |
JdbcTemplate template = new JdbcTemplate(dataSource); final List names = new LinkedList(); template.query("SELECT USER.NAME FROM USER", new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { names.add(rs.getString(1)); } }); |
注意回调中的程序代码是能够自由抛出SQLException的:Spring将会捕捉到这些异常并且用自己的类层次重新抛出。程序的开发者可以选择哪个异常,如果有的话,被捕捉然后处理。
JdbcTemplate提供许多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起标准JDBC来说性能损失非常小,甚至在当应用中需要的结果集数量很大的时候。
在org.springframework.jdbc.object包中是对JDBC的更高层次的抽象。这是建立在核心的JDBC回调功能基础纸上的,但是提供了一个能够对RDBMS操作——无论是查询,更新或者是存储过程——使用Java对象来建模的API。这个API部分是受到JDO查询API的影响,我发现它直观而且非常有用。
一个用于返回User对象的查询对象可能是这样的:
代码: |
class UserQuery extends MappingSqlQuery {
public UserQuery(DataSource datasource) { super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?"); declareParameter(new SqlParameter(Types.NUMERIC)); compile(); }
// Map a result set row to a Java object protected Object mapRow(ResultSet rs, int rownum) throws SQLException { User user = new User(); user.setId(rs.getLong("USER_ID")); user.setForename(rs.getString("FORENAME")); return user; }
public User findUser(long id) { // Use superclass convenience method to provide strong typing return (User) findObject(id); } }
|
这个类可以在下面用上:
代码: |
User user = userQuery.findUser(25);
|
这样的对象经常可以用作DAO的inner class。它们是线程安全的,除非子类作了一些超出常规的事情。
在org.springframework.jdbc.object包中另一个重要的类是StoredProcedure类。Spring让存储过程通过带有一个业务方法的Java类进行代理。如果你喜欢的话,你可以定义一个存储过程实现的接口,意味着你能够把你的程序代码从对存储过程的依赖中完全解脱出来。
Spring数据访问异常层次是基于unchecked(运行时)exception的。在几个工程中使用了Spring之后,我越来越确信这个决定是正确的。
数据访问异常一般是不可恢复的。例如,如果我们不能链接到数据库,某个业务对象很有可能就不能完成要解决的问题了。一个可能的异常是optimistic locking violation,但是不是所有的程序使用optimistic locking。强制编写捕捉其无法有效处理的致命的异常通常是不好的。让它们传播到上层的handler,比如servlet或者EJB 容器通常更加合适。所有的Spring对象访问异常都是DataAccessException的子类,因而如果我们确实选择了捕捉所有的Spring数据访问异常,我们可以很容易做到这点。
注意如果我们确实需要从unchecked数据访问异常中恢复,我们仍然可以这么做。我们可以编写代码仅仅处理可恢复的情况。例如,如果我们认为只有optimistic locking violation是可恢复的,我们可以在Spring的DAO中如下这么写:
代码: |
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this }
|
如果Spring的数据访问异常是checked的,我们需要编写如下的代码。注意我们还是可以选择这么写:
代码: |
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this } catch (DataAccessException ex) { // Fatal; just rethrow it }
|
第一个例子的潜在缺陷是——编译器不能强制处理可能的可恢复的异常——这对于第二个也是如此。因为我们被强制捕捉base exception(DataAccessException),编译器不会强制对子类(OptimisticLockingFailureException)的检查。因而编译器可能强制我们编写处理不可恢复问题的代码,但是对于强制我们处理可恢复的问题并未有任何帮助。
Spring对于数据访问异常的unchecked使用和许多——可能是大多数——成功的持久化框架是一致的。(确实,它部分是受到JDO的影响。)JDBC是少数几个使用checked exception的数据访问API之一。例如TopLink和JDO大量使用unchecked exception。Gavin King现在相信Hibernate也应该选择使用unchecked exception。
Spring的JDBC能够用以下办法帮助你:
你决不需要在使用JDBC时再编写finally block。
总的来说你需要编写的代码更少了
你再也不需要挖掘你的RDBMS的文档以找出它为错误的列名称返回的某个罕见的错误代码。你的程序不再依赖于RDBMS特有的错误处理代码。
无论使用的是什么持久化技术,你都会发现容易实现DAO模式,让业务代码无需依赖于任何特定的数据访问API。
在实践中,我们发现所有这些都确实有助于生产力的提高和更少的bug。我过去常常厌恶编写JDBC代码;现在我发现我能够集中精力于我要执行的SQL,而不是烦杂的JDBC资源管理。
如果需要的话Spring的JDBC抽象可以独立使用——不强迫你把它们用作Spring的一部分。
posted on 2005-10-26 15:53
Sung 阅读(322)
评论(0) 编辑 收藏 所属分类:
Java