要了解Spring为何要提供统一的异常访问层次体系,得先从DAO模式提及.java
不论是一个逻辑简单的小软件系统,仍是一个关系复杂的大型软件系统,都极可能涉及到对数据的访问和存储,而这些对数据的访问和存储每每随着场景的不一样而各异。为了统一和简化相关的数据访问操做,J2EE核心模式提出了DAO(Data Access Object,数据访问对象)模式。使用DAO模式,能够彻底分离数据的访问和存储,很好的屏蔽了数据访问的差别性。不论数据存储在普通的文本文件或者csv文件,仍是关系数据库(RDBMS)或者LDAP(Lightweight Derectory Access Protocol 轻量级目录访问协议),使用DAO模式访问数据的客户端代码彻底能够忽视这种差别,用统一的接口访问相关的数据。mysql
看一具体的场景:
对于大部分软件系统来讲,访问用户信息是你们常常接触到的。以访问用户信息为例,使用DAO模式的话,须要先声明一个数据访问的接口,以下所示:spring
package com.google.spring.jdbc; public interface IUserDao { public User findUserByPK(Integer id); public void updateUser(User user); }
对于客户端代码,即一般的服务层代码来讲,只须要声明依赖的DAO接口便可,即便数据访问方式方式发生了改变,只须要改变相关的DAO实现方式,客户端代码不须要作任何的调整。 sql
package com.google.spring.jdbc; public class UserService { private IUserDao userDao; public IUserDao getUserDao() { return userDao; } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } public void disableUser(Integer userId) { User user = this.userDao.findUserByPK(userId); userDao.updateUser(user); } }
一般状况下,用户信息存储在关系数据库中,因此,相应的咱们会提供一个基于JDBC的DAO接口实现类: 数据库
package com.google.spring.jdbc; public class JDBCUserDao implements IUserDao { @Override public User findUserByPK(Integer id) { // TODO Auto-generated method stub return null; } @Override public void updateUser(User user) { // TODO Auto-generated method stub } }
可能随着系统需求的变动,顾客信息须要转移到LDAP服务,或者转而使用其它的LDAP服务,又或者别人须要使用咱们的Service,可是他们用的是另外的数据访问机制,这时就须要提供一个基于LDAP的数据访问对象,以下所示: 服务器
package com.google.spring.jdbc; public class LdapUserDao implements IUserDao { @Override public User findUserByPK(Integer id) { // TODO Auto-generated method stub return null; } @Override public void updateUser(User user) { // TODO Auto-generated method stub } }
即便具体的实现类发生了变化,客户端代码彻底能够忽视这种变化,惟一须要变化的是factory中几行代码的改变,或者是IOC容器中几行简单的替换而已,因此DAO模式能够很好的屏蔽不一样的数据访问的差别。并发
为了简化描述,上述省略了最基本的数据访问代码,当引入具体的数据访问代码的时候,问题就出现了。oracle
package com.google.spring.jdbc; import java.sql.Connection; import javax.sql.DataSource; public class JDBCUserDao implements IUserDao { private DataSource dataSource ; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public User findUserByPK(Integer id) { Connection conn = null; try { conn = getDataSource().getConnection(); //.... User user = new User(); //........ return user; } catch (Exception e) { //是抛出异常,仍是在当前位置处理。。。 } finally { releaseConnection(conn); } return null; } @Override public void updateUser(User user) { // TODO Auto-generated method stub } public void releaseConnection(Connection conn) { } }
使用JDBC进行数据库访问,当其间出现问题的时候,JDBC API会抛出SQLException来代表问题的发生。而SQLException属于checked exception,因此,咱们的DAO实现类要捕获这种异常并处理。
那如何处理DAO中捕获的SQLException呢,直接在DAO实现类处理掉?若是这样的话,客户端代码就没法得知在数据访问期间发生了什么变化?因此只好将SQLException抛给客户端,进而,DAO实现类的相应的签名 框架
public User findUserByPK(Integer id) throws SQLException
相应的,DAO接口中的相应的方法签名也须要修改: ide
public User findUserByPK(Integer id) throws SQLException;
可是,这样并无解决问题:
一、咱们的数据访问接口对客户端应该是通用的,无论数据访问的机制发生了如何的变化,客户端代码都不该该受到牵连。可是,由于如今用的JDBC访问数据库,须要抛出特定的SQLException,这与数据访问对象模式的初衷是背离的。
二、当引入另一种数据访问的模式的时候,好比,当加入LdapUserDao的时候,会抛出NamingException,若是要实现该接口,那么该方法签名又要发生改变,以下所示:
public User findUserByPK(Integer id) throws SQLException,NamingException;
这是很糟糕的解决方案,若是不一样的数据访问的对象的实现愈来愈多,以及考虑到数据访问对象中的其它的数据访问的方法,这种糟糕的问题还得继续下去吗?
也就是说,由于数据访问的机制有所不一样,咱们的数据访问接口的定义如今变成了空中楼阁,咱们没法最终肯定这个接口!好比,有的数据库提供商采用SQLException的ErrorCode做为具体的错误信息标准,有的数据库提供商则经过SQLException的SqlState来返回相信的错误信息。即便将SQLException封装后抛给客户端对象,当客户端要了解具体的错误信息的时候,依然要根据数据库提供商的不一样采起不一样的信息提取方式,这种客户端处理起来将是很是的糟糕,咱们应该向客户端对象屏蔽这种差别性。能够采用分类转译(Exception Translation)
a>首先,不该该将特定的数据访问异常的错误信息提取工做留给客户端对象,而是应该由DAO实现类,或者某个工具类进行统一的处理。假如咱们让具体的DAO实现类来作这个工做,那么,对于JdbcUserDao来讲,代码以下:
try { conn = getDataSource().getConnection(); //.... User user = new User(); Statement stmt = conn.createStatement(); stmt.execute(""); //........ return user; } catch (SQLException e) { //是抛出异常,仍是在当前位置处理。。。 if(isMysqlVendor()) { //按照mysql数据库的规则分析错误信息而后抛出 throw new RuntimeException(e); } if(isOracleVendor()) { //按照oracle数据库的规则分析错误信息并抛出 throw new RuntimeException(e); } throw new RuntimeException(e); }
b>信息提出出来了,但是,只经过RuntimeException一个异常类型,还不足以区分不一样的错误类型,咱们须要将数据访问期间发生的错误进行分类,而后为具体的错误分类分配一个对应的异常类型。好比,数据库链接不上、ldap服务器链接失败,他们被认为是资源获取失败;而主键冲突或者是其它的资源冲突,他们被认为是数据访问一致性冲突。针对这些状况,能够为RuntimeException为基准,为获取资源失败这种状况分配一个RuntimeException子类型,称其为ResourceFailerException,而数据一致性冲突对应另一个子类型DataIntegrityViolationException,其它的分类异常能够加以类推,因此咱们须要的只是一套unchecked exception类型的面向数据访问领域的异常层次类型。
不须要从新发明轮子
咱们知道unchecked exception类型的面向数据访问领域的异常层次体系存在的必要性,不需咱们设计,spring已经提供了异常访问体系。
spring框架中的异常层次体系所涉及的大部分的异常类型均定义在org.springframework.dao包中,处于这个异常体系中的异常类型均是以org.springframework.dao.DataAccessException为统领,而后根据职能划分为不一样的子类型,整体上看,整个异常体系以下所示:
CleanupFailureDataAccessException:当成功完成数据访问要对资源进行清理的时候,将抛出该异常,好比使用jdbc进行数据库进行访问的时候,查询或者更新完成以后须要关闭相应的数据库链接,若是在关闭的过程当中出现了SQLException,那么致使数据库链接没有被释放,致使资源清理失败。
DataAccessResourceFailureException:在没法访问相应的数据资源的状况下,将抛出DataAccessResourceFailureException。对应这种异常出现最多见的场景就是数据库服务器挂掉的状况,这时,链接数据库的应用程序经过捕获该异常须要了解到是数据库服务器出现了问题。对于JDBC来讲,服务器挂掉会抛出该类型的子类型,即org.springframework.dao.CannotGetJdbcConnectionException。
DataSourceLookupFailureException:当尝试对jndi服务或者是其它位置上的DataSource进行查找的时候,能够抛出DataSourceLookupFailureException。
ConcurrencyFailureException:并发访问失败的时候,能够抛出ConcurrencyFailureException 好比没法取得相应的数据库的锁,或者乐观锁更新冲突。根据不一样的并发数据访问失败的状况,ConcurrencyFailureException细分为所个子类:
OptimisticLockingFailureException对应数据更新的时候出现乐观锁冲突的状况。PessimisticLockingFailureException对应的是悲观锁冲突,PessimisticLockingFailureException还能够细分为CannotAcquireLockException和DeadlockLoserDataAccessException子类型。 InvalidDataAccessApiUsageException:该异常不是由于数据库资源出现了问题,而是咱们以错误的方式,使用了特定的数据访问API,好比使用Spring的JdbcTemplate的queryForObject()语义上只返回一个结果对象,因此咱们在查询多行的时候不能使用此方法。 InvalidDataAccessResourceUsageException:以错误的方式访问数据资源,会抛出该异常,好比要访问数据库资源,却传入错误的sql语句,分为不一样的子类,基于JDBC的访问会抛出BadSqlGrammarException 基于hibernate的会抛出HibernateQueryException异常。 DataRetrievalFailureException:在要获取预期的数据却失败的时候。 PermissionDeniedDataAccessException:要访问相关的数据资源却没相应的权限的时候。 DataIntegrityViolationException:数据一致性冲突异常,好比主键冲突。 UncategorizedDataAccessException:没法细分的其它的异常,能够子类化定义具体的异常。