`

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实现类要捕获这种异常并处理。 
那如何处理DAO中捕获的SQLException呢,直接在DAO实现类处理掉?如果这样的话,客户端代码就无法得知在数据访问期间发生了什么变化?所以只好将SQLException抛给客户端,进而,DAO实现类的相应的签名 
1 public User findUserByPK(Integer id) throws SQLException

 

相应的,DAO接口中的相应的方法签名也需要修改: 
1 public User findUserByPK(Integer id) throws SQLException;

 

但是,这样并没有解决问题: 
1、我们的数据访问接口对客户端应该是通用的,不管数据访问的机制发生了如何的变化,客户端代码都不应该受到牵连。但是,因为现在用的JDBC访问数据库,需要抛出特定的SQLException,这与数据访问对象模式的初衷是背离的。 
2、当引入另外一种数据访问的模式的时候,比如,当加入LdapUserDao的时候,会抛出NamingException,如果要实现该接口,那么该方法签名又要发生改变,如下所示: 
1 public User findUserByPK(Integer id) throws SQLException,NamingException;

 

这是很糟糕的解决方案,如果不同的数据访问的对象的实现越来越多,以及考虑到数据访问对象中的其它的数据访问的方法,这种糟糕的问题还得继续下去吗? 
也就是说,因为数据访问的机制有所不同,我们的数据访问接口的定义现在变成了空中楼阁,我们无法最终确定这个接口!比如,有的数据库提供商采用SQLException的ErrorCode作为具体的错误信息标准,有的数据库提供商则通过SQLException的SqlState来返回相信的错误信息。即使将SQLException封装后抛给客户端对象,当客户端要了解具体的错误信息的时候,依然要根据数据库提供商的不同采取不同的信息提取方式,这种客户端处理起来将是非常的糟糕,我们应该向客户端对象屏蔽这种差异性。可以采用分类转译(Exception Translation) 
a>首先,不应该将特定的数据访问异常的错误信息提取工作留给客户端对象,而是应该由DAO实现类,或者某个工具类进行统一的处理。假如我们让具体的DAO实现类来做这个工作,那么,对于JdbcUserDao来说,代码如下: 
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:无法细分的其它的异常,可以子类化定义具体的异常。

分享到:
评论

相关推荐

    Spring Dao层设计

    Spring 数据库访问的支持。包括统一异常体系和Spring的Template+CallBack的解读

    Spring同时集成JPA与Mybatis.docx

    同时Spring会将各个ORM框架的异常转译到Spring异常体系下。 12.统一的事务管理:Spring通过IoC和AOP技术,形成了事务管理抽象层,接管了各种ORM框架下的数据访问的事务管理。 随着版本的升级,Spring核心包中对ORM的...

    spring.net中文手册在线版

    15.2.统一的异常体系 15.3.为数据访问对象提供的统一抽象基类 第十六章. DbProvider 16.1.简介 16.1.1.IDbProvider和DbProviderFactory 16.1.2. XML配置 16.1.3.管理连接字符串 第十七章. 使用ADO.NET进行数据访问 ...

    Spring.3.x企业应用开发实战(完整版).part2

    8.2.1 Spring的DAO异常体系 8.2.2 JDBC的异常转换器 8.2.3 其他持久技术的异常转换器 8.3 统一数据访问模板 8.3.1 使用模板和回调机制 8.3.2 Spring为不同持久化技术所提供的模板类 8.4 数据源 8.4.1 配置一个数据源...

    第24次课-1 Spring与Hibernate的整合

    为了更好地与持久层框架整合,Spring还提供了统一的异常处理体系和事务管理方法。 24.1 概述 24.1.1 概述 如果Spring与Hibernate进行了整合,则Hibernate便处于被Spring管理的状态下,Hibernate所需的基础资源,都由...

    Spring3.x企业应用开发实战(完整版) part1

    8.2.1 Spring的DAO异常体系 8.2.2 JDBC的异常转换器 8.2.3 其他持久技术的异常转换器 8.3 统一数据访问模板 8.3.1 使用模板和回调机制 8.3.2 Spring为不同持久化技术所提供的模板类 8.4 数据源 8.4.1 配置一个数据源...

    计算机软件项目设计方案(2020).docx

    技术实现事务管理、服务日志、统一异常处理,在远程服务调用中使用RPC Context实现上下文管理,持久化框架采用Hibernate、Mybatis双框架兼容设计,使用数据访问代理服务,实现分库分表环境下的透明数据访问。...

    xmljava系统源码-lemon:Java体系学习教程

    xml java系统源码 JAVA技术学习教程样例 此教程主要用于Java各个知识点学习 有疑问和兴趣,请添加个人公众号 ...Springboot统一的异常处理 Study 1-1-3 AOP在springboot中应用 logback在springboot中应用 Study 1-1-

    从无到有搭建中小型互联网公司后台服务架构与运维架构

    2、 结合dubbo的restful框架,加上基于oauth2的token验证,并实现统一用户中心的设计 3、 重点讲解spring boot,然后结合之前的dubbo技术框架进行改造,实现springboot和dubbo的相融合 4、 作为一个技术架构肯定涉及...

    J2EE应用开发详解

    246 14.5.2 CORBA和RMI的互操作 247 14.6 小结 248 第15章 Spring框架 249 15.1 Spring 2.0的体系结构 249 15.2 Ioc容器 250 15.2.1 BeanFactory 250 15.2.2 ApplicationContext 252 15.2.3 Beans的生命周期过程 253...

    asp.net知识库

    .net 2.0 访问Oracle --与Sql Server的差异,注意事项,常见异常 Ado.net 与NHibernate的关系? 动态创建数据库 SQL Server数据库安全规划全攻略 .net通用数据库访问组件SQL Artisan应用简介1 在Framework1.0下...

    java开源包1

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包11

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包2

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包3

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包6

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包5

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包10

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包4

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

    java开源包8

    AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,...

Global site tag (gtag.js) - Google Analytics