Spring 提供了一套和实现技术无关的 、 面向 DAO 层语义级别的异常体系,内部通过转换器将不同持久化技术的异常转换成 Spring 的异常,实现统一管理。
1 异常体系
很多正统的 AP中,使用了过多的检查型异常,以致于在使用 API 时,代码中充斥了大量 try/catch 样板式的代码 。 大多数情况下,这些 catch 代码段除了记录日志外,并没有做多少其它有益的工作。
比如 JDK 中的 JDBC API,大家都说不好用,因为检查型异常泛滥,许多异常处理代码喧宾夺主地侵入到业务代码中,从而破坏了整体代码的整洁与优雅 。
Spring 在 org.springframework.dao
中提供了一套优雅的 DAO 异常体系, 这些异常都继承自 DataAccessException
, DataAccessException
继承自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 相同的异常转换器 。
网友评论