Spring 统一的异常访问体系
要了解Spring为什么要提供统一的异常访问层次体系,得先从DAO模式说起.
不管是一个逻辑简单的小软件系统,还是一个关系复杂的大型软件系统,都很可能涉及到对数据的访问和存储,而这些对数据的访问和存储往往随着场景的不同而各异。为了统一和简化相关的数据访问操作,J2EE核心模式提出了DAO(Data Access Object,数据访问对象)模式。使用DAO模式,可以完全分离数据的访问和存储,很好的屏蔽了数据访问的差异性。不论数据存储在普通的文本文件或者csv文件,还是关系数据库(RDBMS)或者LDAP(Lightweight Derectory Access Protocol 轻量级目录访问协议),使用DAO模式访问数据的客户端代码完全可以忽视这种差异,用统一的接口访问相关的数据。
看一具体的场景:
对于大部分软件系统来说,访问用户信息是大家经常接触到的。以访问用户信息为例,使用DAO模式的话,需要先声明一个数据访问的接口,如下所示:
1
package
?com.google.spring.jdbc;
2
?3
public
?interface
?IUserDao
4
{
5
????
public
?User findUserByPK(Integer id);
6
????
?7
????
public
?void
?updateUser(User user);
8
}
?
对于客户端代码,即通常的服务层代码来说,只需要声明依赖的DAO接口即可,即使数据访问方式方式发生了改变,只需要改变相关的DAO实现方式,客户端代码不需要做任何的调整。?01
package
?com.google.spring.jdbc;
02
?03
public
?class
?UserService
04
{
05
????
private
?IUserDao userDao;
06
?07
????
public
?IUserDao getUserDao()
08
????
{
09
????????
return
?userDao;
10
????
}
11
?12
????
public
?void
?setUserDao(IUserDao userDao)
13
????
{
14
????????
this
.userDao = userDao;
15
????
}
16
????
?17
????
public
?void
?disableUser(Integer userId)
18
????
{
19
????????
User user =?
this
.userDao.findUserByPK(userId);
20
????????
userDao.updateUser(user);
21
????
}
22
}
通常情况下,用户信息存储在关系数据库中,所以,相应的我们会提供一个基于JDBC的DAO接口实现类:?01
package
?com.google.spring.jdbc;
02
?03
public
?class
?JDBCUserDao?
implements
?IUserDao
04
{
05
?06
????
@Override
07
????
public
?User findUserByPK(Integer id)
08
????
{
09
????????
// TODO Auto-generated method stub
10
????????
return
?null
;
11
????
}
12
?13
????
@Override
14
????
public
?void
?updateUser(User user)
15
????
{
16
????????
// TODO Auto-generated method stub
17
?18
????
}
19
?20
}
?
可能随着系统需求的变更,顾客信息需要转移到LDAP服务,或者转而使用其它的LDAP服务,又或者别人需要使用我们的Service,但是他们用的是另外的数据访问机制,这时就需要提供一个基于LDAP的数据访问对象,如下所示:?01
package
?com.google.spring.jdbc;
02
?03
public
?class
?LdapUserDao?
implements
?IUserDao
04
{
05
?06
????
@Override
07
????
public
?User findUserByPK(Integer id)
08
????
{
09
????????
// TODO Auto-generated method stub
10
????????
return
?null
;
11
????
}
12
?13
????
@Override
14
????
public
?void
?updateUser(User user)
15
????
{
16
????????
// TODO Auto-generated method stub
17
?18
????
}
19
?20
}
?
即使具体的实现类发生了变化,客户端代码完全可以忽视这种变化,唯一需要变化的是factory中几行代码的改变,或者是IOC容器中几行简单的替换而已,所以DAO模式可以很好的屏蔽不同的数据访问的差异。
为了简化描述,上述省略了最基本的数据访问代码,当引入具体的数据访问代码的时候,问题就出现了。
01
package
?com.google.spring.jdbc;
02
?03
import
?java.sql.Connection;
04
?05
import
?javax.sql.DataSource;
06
?07
public
?class
?JDBCUserDao?
implements
?IUserDao
08
{
09
????
private
?DataSource dataSource ;
10
????
?11
?12
????
public
?DataSource getDataSource()
13
????
{
14
????????
return
?dataSource;
15
????
}
16
?17
????
public
?void
?setDataSource(DataSource dataSource)
18
????
{
19
????????
this
.dataSource = dataSource;
20
????
}
21
?22
????
@Override
23
????
public
?User findUserByPK(Integer id)
24
????
{
25
????????
Connection conn =?
null
;
26
????????
try
27
????????
{
28
????????????
conn = getDataSource().getConnection();
29
????????????
//....
30
????????????
User user =?
new
?User();
31
????????????
//........
32
????????????
return
?user;
33
????????
}
34
????????
catch
?(Exception e)
35
????????
{
36
????????????
//是抛出异常,还是在当前位置处理。。。
37
????????
}
38
????????
finally
39
????????
{
40
????????????
releaseConnection(conn);
41
????????
}
42
????????
return
?null
;
43
????
}
44
?45
????
@Override
46
????
public
?void
?updateUser(User user)
47
????
{
48
????????
// TODO Auto-generated method stub
49
?50
????
}
51
????
?52
????
public
?void
?releaseConnection(Connection conn)
53
????
{
54
????????
?55
????
}
56
}
?
使用JDBC进行数据库访问,当其间出现问题的时候,JDBC API会抛出SQLException来表明问题的发生。而SQLException属于checked??? ?exception,所以,我们的DAO实现类要捕获这种异常并处理。?1
public
?User findUserByPK(Integer id)?
throws
?SQLException
?
相应的,DAO接口中的相应的方法签名也需要修改:?1
public
?User findUserByPK(Integer id)?
throws
?SQLException;
?
但是,这样并没有解决问题:?1
public
?User findUserByPK(Integer id)?
throws
?SQLException,NamingException;
?
这是很糟糕的解决方案,如果不同的数据访问的对象的实现越来越多,以及考虑到数据访问对象中的其它的数据访问的方法,这种糟糕的问题还得继续下去吗??01
try
02
{
03
????
conn = getDataSource().getConnection();
04
????
//....
05
????
User user =?
new
?User();
06
????
Statement stmt = conn.createStatement();
07
????
stmt.execute(
""
);
08
????
//........
09
????
return
?user;
10
}
11
catch
?(SQLException e)
12
{
13
????
//是抛出异常,还是在当前位置处理。。。
14
????
if
(isMysqlVendor())
15
????
{
16
????????
//按照mysql数据库的规则分析错误信息然后抛出
17
????????
throw
?new
?RuntimeException(e);
18
????
}
19
????
if
(isOracleVendor())
20
????
{
21
????????
//按照oracle数据库的规则分析错误信息并抛出
22
????????
throw
?new
?RuntimeException(e);
23
????
}
24
????
throw
?new
?RuntimeException(e);
25
}
?
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:无法细分的其它的异常,可以子类化定义具体的异常。