说说 Spring DAO 的异常体系

1,876 阅读5分钟

Spring 提供了一套和实现技术无关的 、 面向 DAO 层语义级别的异常体系,内部通过转换器将不同持久化技术的异常转换成 Spring 的异常,实现统一管理。

1 异常体系

很多正统的 AP中,使用了过多的检查型异常,以致于在使用 API 时,代码中充斥了大量 try/catch 样板式的代码 。 大多数情况下,这些 catch 代码段除了记录日志外,并没有做多少其它有益的工作。

比如 JDK 中的 JDBC API,大家都说不好用,因为检查型异常泛滥,许多异常处理代码喧宾夺主地侵入到业务代码中,从而破坏了整体代码的整洁与优雅 。

Spring 在 org.springframework.dao 中提供了一套优雅的 DAO 异常体系, 这些异常都继承自 DataAccessExceptionDataAccessException 继承自NestedRuntimeException, NestedRuntimeException 异常以嵌套的方式封装了源异常 。 因此,虽然不同的持久化技术的特定异常被转换到 Spring 的 DAO 异常体系中,但我们可以通过 getCause() 方法获取原始异常信息 。

这套异常体系从 DAO 的抽象层次上定义了异常目录树,它使得开发者可以很容易地关注某个特定的语义异常。而 JDBC 的 SQLException 过于底层,而且与具体数据库强相关(比如 getErrorCode()),不仅不好编码,而且很难移植。

Spring 建立了异常分类目录,以适当的颗粒度划分了异常类型。这样做的好处是:

  • 开发者可以从底层繁琐复杂的技术细节中解脱出来。
  • 开发者可以选择自己感兴趣的异常进行处理 。

DataAccessException 下有这些异常子类:

异常子类 说明
CleanupFailureDataAccessException 执行 DAO 操作成功,但在释放数据资源时发生异常,如关闭 Connection 时发生异常。
ConcurrencyFailureException 并发地操作数据时发生异常,如无法获取乐观锁或悲观锁时、死锁引发的失败等场景。
DataAccessResourceFailureException 访问数据资源失败,如无法获取数据连接,无法获取 Hibernate 的会话等场景。
DataRetrievalFailureException 获取数据失败,如找不到对应主键的数据或使用了错误的列索引等场景。
DataSourceLookupFailureException 无法从 JNDI 中查找到数据源。
DataIntegrityViolationException 数据操作违反了数据一致性限制时抛出,如插入重复的主键或引用不存在的外键场景。
InvalidDataAccessApiUsageException 不正确地调用某一种持久化技术时抛出,如在 Spring JDBC 中查询对象在调用前没有事先进行编译操作,就会抛出该异常。这种异常主要是因为不正确地使用持久化技术而产生的。
InvalidDataAccessResourceUsageException 在访问数据源时使用了不正确的方法时抛出,如写错 SQL 语句。
PermissionDeniedDataAccessException 数据访问权限不足时抛出。如仅拥有只读权限却试图更改数据。
UncategorizedDataAccessException 其它未被分类的异常。

Spring 为了进一步细化错误问题域, 它对上述的这些一级异常类又进行了细分。

这套异常体系具有高度的可扩展性,当 Spring 需要对一个新的持久化技术提供支持时,只要为其定义一个对应的子异常即可,这种方式实现了设计模式中的“开闭原则” 。

开闭原则( OCP )是面向对象设计中 “ 可复用设计 ” 的基石,是面向对象设计中最重要的原则之一,其它很多的设计原则都是实现开闭原则的一种手段 。 对于扩展是开放的,对于修改是关闭的,这意味着模块的行为是可以扩展的 。 当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为 。

2 异常转换器

2.1 JDBC

一般情况下,JDBC API 在执行数据操作出现异常时,大都会抛出 SQLException ,SQLException 把异常的细节封装在异常属性中,所以如果希望了解异常的具体原因,我们必须对异常属性进行分析。

SQLException 拥有两个代表异常具体原因的属性:

属性 类型 说明
错误码 int 与具体数据库相关,调用 getErrorCode() 返回。
SQL 状态码 String 标准错误代码,由 5 个字符组成,调用 getSQLState() 返回。

Spring 会根据错误码和 SQL 状态码将 SQLExeption 转换为对应的 Spring DAO 异常 。 在 org.springframework.jdbc.support 包中定义了 SQLExceptionTranslator 接口,该接口的两个实现类 SQLErrorCodeSQLExceptionTranslator 和 `SQLStateSQLExceptionTranslator
分别负责处理 SQLException 中错误代码和 SQL 状态码的转换工作 。

2.2 其它 ORM 持久化技术

其它 ORM 持久化技术都拥有一个语义明确的异常体系,所以转换相对简单。

**注意:**Spring4 只支持 Hibernate3.6+。

Spring 在 org.springframe.orm 中为所支持的 ORM 技术定义了相应的子包。对应的异常转换器也定义在这些子包中:

ORM 持久化技术 异常转换器
HibernateX,X 可为 3、4 或 5 org.springframework.orm.hibernateX.SessionFactoryUtils
JPA org.springframework.orm.jpa.EntityManagerFactoryUtils
JDO org.springframework.orm.jdo.PersistenceManagerFactoryUtils

这些工具类除了具有异常转换的功能外,在进行事务管理时,还提供了从事务上下文环境中返回相同会话的功能 。

Spring 也支持 myBatis 持久化技术,因为 myBatis 抛出的异常与 JDBC 相同, 都是 SQLException 异常,所以采用了和 JDBC 相同的异常转换器 。