这篇博客 来自spring揭秘一书的第十三章

为什么要有访问异常都有一个体系,这个我们得从DAO模式说起。

DAO模式

任何一个系统,不管是一个最简单的小系统,还是大规模的系统,都得跟数据打交道,说白了都得时常进行存取数据的操作。我们暂且不论数据本身,数据存储的方式就已经是各有不同了。

最简单的,把数据存储到关系型数据库中。这里面至少就有MySQL,Oracle等等

我还可以把数据存储到文本文件里。

还可以把数据存储到csv文件中(关于csv,大家百度之)

还有LDAP(Lightweight Directory Access Protocol)轻量目录访问协议





为了统一和简化系统访问这不同存储方式的数据,就提出了DAO模式。

换句话说,DAO层干的就是屏蔽因为不同的数据存储方式而带来存取差异。

我再说的简单点,在我们的系统里,一般会有一个总的DAO类,是一个接口。

然后就是有MySQLDaoImpl,OracleDaoImpl等等。






举个例子吧,我们看一个访问顾客的例子

public interface IUserDao{
    public User findUserByPK(Integer id);
    public void updateUser(User user);
}

在服务层的代码里,我们只有声明一个IUserDao型的实例变量,并让spring给我们注入即可

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);
    }
}

假定我们的数据存储在关系型数据库中

那么我就给IUserDao来一个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
     }
}

如果以后数据需要从关系型数据库中迁移到文本文件中(我知道这个假设很扯淡,就是为了说明如果后来数据的存储方式发生了改变),那么我们再实现一个TextUserDao即可

public class TextUserDao 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
     }
}

我们需要改动的地方,就是再spring的配置文件里,把UserService的配置变一下即可。



看到了吧,DAO的优势就在于可以屏蔽由不同的数据访问机制的差异而导致的存储差异。

Exception处理的问题

这样就OK了吗,似乎是OK了。

这么吧,我们把JDBCUserDao补充完整。

import java.sql.Connection;

import javax.sql.DataSource;

public class JDBCUserDao implements IUserDao{
    //省略datasource的getset
    private DataSource dataSource ;

    @Override
    public User findUserByPK(Integer id){
        Connection conn = null;
        try{
            conn = getDataSource().getConnection();
            //....
            User user = new User();
            //........
            return user;
        }
        catch (SQLException e){
            //是抛出异常,还是在当前位置处理。。。
        }
        finally{
            releaseConnection(conn);
        }
        return null;
    }

    //省略updateuser与releaseConnection
}

问题就在于try catch里,如果捕获到异常

是抛出呢,还是就直接处理呢?

如果是直接就处理了,那么service就收到一个null,它什么都不知道,那这怎么整?

那就只能抛给service层了。

不过,JDBCUserDao类的findUserByPK方法的签名就得改一改了。

public User findUserByPK(Integer id) throws SQLException

同样的IUserDao的签名也得改一次

public User findUserByPK(Integer id) throws SQLException;





这样一来就带来了两个问题

1 我们引入DAO的目的就是为了统一,不管使用什么样的数据存储机制,客户端(也就是service层)应该是不变的,现在因为使用关系型数据库,需要抛出特定的SQLException,那么你让service层怎么办?加上处理SQLException的逻辑?如果我不用关系型数据库了,我使用文本方式存储数据,那么就肯定不会抛出SQLException。你又让Service层怎么办?

2 如果我使用了文本方式来存储数据,findUserByPK方法需要抛出一个TextExcetpin(这个异常是我杜撰的),那怎么办?再改签名吗?

public User findUserByPK(Integer id) throws SQLException,TextExcetpin;

如果存储机制再改变,我们又继续添加抛出的异常类型,这个设计也太糟糕了吧。





就目前来看,我们的dao似乎建造了一个空中楼阁。

包装Exception

问题来了,不要怕,解决它就是了。

上面的问题的核心是,不同的dao的实现会抛出不同的异常,如果对每一种异常都处理一下,我们的dao层的设计也就没有意义了。

那么我们把不同的异常,包装成一个统一的异常不就OK了?

包装成什么异常呢?checked exception还是unchecked exception呢?





这里我简要介绍一下这两种异常。

Exception的子类有两类

一类是 类似NullPointerException这种,继承自RuntimeException(RuntimeException又继承自Exception),在出现RuntimeException的地方,我们不需要try catch,方法的签名处也不需要声明throws,这类exception我们称之为unchecked exception

另一类是 类似ClassNotFound,SQLExceptin这种,直接继承自Exception。这种异常必须得try catch,如果不及时捕获的话,在方法的签名处就得声明 throws。这类exception我们称之为checked exception。





我们现在的情况是,在dao里面的exception需要抛出,但是抛出后,我们需要service层怎么处理?最好的办法就是不做处理,仅仅让他知道就ok。

那么我们选择unchecked exception

这么一来JDBCUserDao的核心代码就是下面的样子了:

public User findUserByPK(Integer id){
	try{
	 conn = getDataSource().getConnection();
	 //....
	  User user = new User();
	   Statement stmt = conn.createStatement();
	   stmt.execute("");
	   //........
	  return user;
	}
	catch (SQLException e){
	   throw new RuntimeException(e);
	}
}

关键问题是,因为RuntimeException不需要在方法的签名处声明throws

我们的方法签名又回归大一统了。

public User findUserByPK(Integer id)

另外还有一个小问题,就拿关系型数据库来说吧,mysql与oracle的错误包装方式不一样,有的数据库提供商采用SQLException的ErrorCode作为具体的错误信息标准,有的数据库提供商则通过SQLException的SqlState来返回相信的错误信息。如果直接像上面的那样子直接抛出一个RuntimeException,那么客户端要想知道错误信息还不知道去errorcode看还是去sqlstate看呢。

怎么办?我们使用分类转译的方式,改成下面的样子

catch (SQLException e){
    //是抛出异常,还是在当前位置处理。。。
    if(isMysqlVendor()){
        //按照mysql数据库的规则分析错误信息然后抛出
        throw new RuntimeException(e);
    }
    if(isOracleVendor()){
        //按照oracle数据库的规则分析错误信息并抛出
        throw new RuntimeException(e);
    }
    throw new RuntimeException(e);
}

或者我们可以吧上面的两个if判断装到一个工具类里面去。





还有一个问题,上面的处理方式,所有的异常其实就一个RuntimeException。还不够细致。

比如,数据库连接不上、ldap服务器连接失败,他们被认为是资源获取失败;而主键冲突或者是其它的资源冲突,他们被认为是数据访问一致性冲突。针对这些情况,可以为RuntimeException为基准,为获取资源失败这种情况分配一个RuntimeException子类型,称其为ResourceFailerException,而数据一致性冲突对应另外一个子类型DataIntegrityViolationException,其它的分类异常可以加以类推,所以我们需要的只是一套unchecked exception类型的面向数据访问领域的异常层次类型。

不需要重新发明轮子

spring已经为我们做好了下面的异常体系:

DataAccessException位于org.springframework.dao中

上面各种exception的具体职责,我就不细说了,大家百度之。

参照资料

http://my.oschina.net/u/218421/blog/38478

Spring揭秘读书笔记 八 数据访问异常体系的更多相关文章

  1. Spring揭秘 读书笔记 三 bean的scope与FactoryBean

    本书可作为王富强所著<<Spring揭秘>>一书的读书笔记  第四章 BeanFactory的xml之旅 bean的scope scope有时被翻译为"作用域&quo ...

  2. spring揭秘 读书笔记 二 BeanFactory的对象注册与依赖绑定

    本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,而且IoC Service Pr ...

  3. spring揭秘 读书笔记 一 IoC初探

    本文是王福强所著<<spring揭秘>>一书的读书笔记 ioc的基本概念 一个例子 我们看下面这个类,getAndPersistNews方法干了四件事 1 通过newsList ...

  4. spring揭秘 读书笔记 二 BeanFactory的对象注冊与依赖绑定

    本文是王福强所著<<spring揭秘>>一书的读书笔记 我们前面就说过,Spring的IoC容器时一个IoC Service Provider,并且IoC Service Pr ...

  5. Spring.Net学习笔记(二)-数据访问器

    Spring对ADO.NET也提供了支持,依赖与程序集Spring.Data.dll IDbProvider IDbProvider定义了数据访问提供器的基础,配置如下 <?xml versio ...

  6. Spring揭秘 读书笔记 七 BeanFactory的启动分析

    首先,先看我自己画的BeanFactory启动时的时序图. 第一次接触时序图,可能有些地方画的不是很符合时序图的规则,大家只关注调用顺序即可. public static void main(Stri ...

  7. spring揭秘 读书笔记 六 bean的一生

    我们知道,Spring容器具有对象的BeanDefinition来保存该对象实例化时需要的数据. 对象通过container.getBean()方法是才会初始化该对象. BeanFactory 我们知 ...

  8. spring揭秘读书笔记----spring的ioc容器之BeanFactory

    spring的ioc容器是一种特殊的Ioc Service Provider(ioc服务提供者),如果把普通的ioc容器认为是工厂模式(其实很相似),那spring的ioc容器只是让这个工厂的功能更强 ...

  9. Spring揭秘 读书笔记 五 容器的启动

    Spring的IoC容器所起的作用,就是生产bean,并维持bean间的依赖关系.它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定 ...

随机推荐

  1. Docker配置文件

    Docker 的 Registry 利用配置文件提供了一些仓库的模板(flavor),用户可以直接使用它们来进行开发或生产部署. 模板 在 config_sample.yml 文件中,可以看到一些现成 ...

  2. android Git命令家底儿及Git数据通信原理详解

    声明:本文为CSDN原创投稿文章,未经许可,禁止任何形式的转载. 现在大部分使用的都是SVN,也有一部分迁移了Git,虽然挺好的,不过还有其它很多版本控制的工具,并没有谁最好用,最重要的是适合自己的公 ...

  3. arm-none-eabi-g++ -Xlinker -T "../LF3Kmonitor.ld" -Xlinker -Map="Bogota_ICT_V.map"-ram-hosted.ld -mc

    1.arm-none-eabi-g++:是编译ARM裸板用的编译器,不依赖于操作系统. 2.-Xlinker -T "../LF3Kmonitor.ld" -Xlinker -Ma ...

  4. 24 AIDL案例

    服务端 MainActivity.java package com.qf.day24_aidl_wordserver; import android.app.Activity; import andr ...

  5. 【伯乐在线】100个高质量Java开发者博客

    本文由 ImportNew - 夏千林 翻译自 programcreek.欢迎加入翻译小组.转载请见文末要求. ImportNew注:原文中还没有100个.作者希望大家一起来推荐高质量的Java开发博 ...

  6. maven配置详解

    什么是pom?     pom作为项目对象模型.通过xml表示maven项目,使用pom.xml来实现.主要描述了项目:包括配置文件:开发者需要遵循的规则,缺陷管理系统,组织和licenses,项目的 ...

  7. 4.2、Android Studio压缩你的代码和资源

    为了让你的APK文件尽可能的小,你需要在构建的时候开启压缩来移除无用的代码和资源. 代码压缩可在ProGuard中使用,可以检测和清除无用的类,变量,方法和属性,甚至包括你引用的库.ProGuard同 ...

  8. TCP的发送系列 — 发送缓存的管理(二)

    主要内容:从TCP层面判断发送缓存的申请是否合法,进程因缺少发送缓存而进行睡眠等待. 因为有发送缓存可写事件而被唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zh ...

  9. Xcode中不用Storyboard,用纯xib创建TabBar模式视图

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 如果要开发Tab类型视图的App,在Xcode中可以使用对应的 ...

  10. Android文本框-android学习之旅(十七 )

    文本框简介 文本框属于基本的andoid控件,TextView继承了View是最基本的文本框,它的子类包括EditView和Button等,TextView的大部分方法,它的子类也可以使用. Text ...