`

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数据访问对象框架入门

    Spring Data Access Object(DAO)框架是Spring生态体系中用于数据库交互的重要部分,它简化了数据访问层的实现,使得开发者可以更专注于业务逻辑,而不是底层数据库操作。本文将深入探讨Spring DAO框架的入门知识,...

    Spring Dao层设计

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

    spring 的Annotation方式

    - **`@Repository`**:主要用于数据访问层(DAO层),它不仅能将类标记为Spring Bean,还负责将数据访问异常转换为Spring的统一异常体系,增强了异常处理的一致性和可读性。 - **`@Service`**:通常用于业务逻辑层...

    spring学习的第三天

    - 能够将Hibernate抛出的异常转换为Spring统一的异常体系,便于上层统一处理。 - **使用方式**: - DAO中注入`HibernateTemplate`:通过在配置文件中配置`SessionFactory`并将其注入到`HibernateTemplate`中,...

    Spring Security 3多用户登录实现一

    Spring Security提供了统一的异常处理机制,如`AccessDeniedException`(访问被拒绝)和`AuthenticationException`(认证失败)。可以通过实现`AccessDeniedHandler`和`AuthenticationEntryPoint`接口来自定义异常...

    spring2.0技术手册.PDF

    此外,Spring还提供了对数据访问异常进行抽象的层次结构,并通过数据访问异常转换机制与通用异常层次结构相互转换。 八、资源群组信息 文档中提及的Java学习群信息,实际上是作为推广或附加资源存在的。它提供了一...

    Spring技术内幕:深入解析Spring架构与设计原理(第2版)

    数据访问对象(DAO)支持异常转换机制,将底层数据访问技术抛出的异常统一转换为Spring的DAO异常体系。 知识点六:Spring Boot 尽管不是《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》这本书的内容,...

    spring3.x的读书笔记-5

    总的来说,Spring 3.x通过AOP、DAO支持、统一异常体系、数据访问模板和数据源管理,为开发者提供了强大的工具和框架,简化了数据访问层的实现,提高了代码的可维护性和可复用性。这些知识点对于理解Spring的核心机制...

    spring-framework-master

    4. **MVC框架**:Spring MVC提供了灵活的模型绑定、数据验证、本地化和异常处理等功能,简化了Web应用的开发。 5. **声明式事务管理**:通过XML或注解方式声明事务边界,使得事务管理变得简单且不易出错。 6. **...

    尚硅谷SpringCloud视频 + 源码 百度网盘

    - **API网关**:使用Zuul作为API网关,对外提供统一的访问入口。 - **断路器**:使用Hystrix实现服务调用的断路器功能,防止因个别服务异常导致整个系统雪崩。 - **配置管理**:使用Spring Cloud Config进行外部配置...

    官方原版源码spring-framework-5.1.8.RELEASE.zip

    同时,Spring Data项目简化了数据访问层的开发,提供了一种统一的API。 6. **Web模块**:Spring MVC是Spring提供的Web应用框架,用于构建RESTful风格的Web服务。它包含模型-视图-控制器架构,提供了强大的视图解析...

    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 配置一个数据源...

    spring mvc 用户管理

    - 自定义异常处理:通过`@ExceptionHandler`注解编写全局异常处理器,统一处理系统抛出的异常。 9. **拦截器(Interceptor)** - 定义拦截器,实现HandlerInterceptor接口,可以对请求进行预处理和后处理,例如实现...

    77道Spring面试题以及参考答案(2024年最新版)

    - 异常转换,将 SQL 异常转化为 Spring 统一异常体系。 - **ORM 支持**: - 集成 Hibernate、MyBatis 等 ORM 框架,提供更高层次的抽象。 - 支持事务管理、缓存管理等功能。 ### 6. 面向切面编程 (AOP) - **基本...

    详解Spring_3.0基于Annotation的依赖注入实现

    除了将类标识为Bean外,`@Repository`还提供了一种机制来处理数据库访问异常,将其转换为Spring提供的统一异常体系,增强了代码的可读性和可维护性。 - **`@Component`**:这是一个泛用的注解,用于将任意类标记为...

    JSF+Spring+Hibernate整合图文教程详解

    Spring还提供了对Hibernate的异常转化支持,可以将Hibernate抛出的异常转换为Spring的一致性异常,从而简化异常处理的代码。 #### 五、总结 通过整合JSF、Spring和Hibernate,我们不仅能够构建出一个清晰、高效的...

    4.Spring-Tx銆丼pring5.pdf

    - **异常处理**:JdbcTemplate将数据库访问中可能出现的异常转换为Spring的DataAccessException异常体系,这样开发者只需要捕获DataAccessException就可以处理所有数据库相关的异常。 2. Spring框架中的数据访问...

    Spring/泛型Hibernate的实现

    3. **异常转换**:Spring可以将Hibernate抛出的异常转化为其自身的一致性异常体系,便于上层统一处理。 ##### 2.1 配置Spring管理Hibernate 在Spring的配置文件中,可以通过如下方式来配置Hibernate和Spring的集成...

    spring进阶.rar

    《Spring进阶》 ...以上内容只是Spring框架庞大体系的一部分,通过深入学习和实践"spring进阶.rar"中的资料,开发者可以更深入地掌握Spring的精髓,提升开发效率,构建出更加稳定、高效的企业级应用。

Global site tag (gtag.js) - Google Analytics