1、Chapter 1. 使用 Spring 进行数据访问(Data Access With Spring) 2008-01-16 09:34 6151 人阅读 评论(3) 收藏 举报 Table of Contents1.1. 统一的数据访问异常层次体系 (Consistent Exception Hierarchy In Spring) 1.1.1. DAO 模式的背景(Background of the DAO Pattern) 1.1.2. 梦想照进现实(The reality of implementing the DAO pattern) 1.1.3. 发现问题,解决问题(How to
2、 get through?) 1.1.4. 不重新发明轮子(Dont Reinvent The Wheels.)1.2. JDBC API 的最佳实践(JDBC made easy with spring) 1.2.1. 基于 Template 的 Jdbc 使用方式 1.2.1.1. JDBC 的尴尬 1.2.1.2. JdbcTemplate 的诞生 1.2.1.2.1. 模板方法模式简介(Template Method Pattern Introduction) 1.2.1.2.2. JdbcTemplate 的演化 1.2.1.2.3. 使用 DataSourceUtils 进行 Co
3、nnection 的管理 1.2.1.2.4. 使用 NativeJdbcExtractor 来获得“真相” 1.2.1.2.5. 控制 JdbcTemplate 的行为 1.2.1.2.6. SQLException 到 DataAccessException 体系的转译 1.2.1.2.6.1. 扩展 SQLErrorCodeSQLExceptionTranslator 1.2.1.2.6.2. 提供 sql-error-codes.xml 自定义配置1.2.1.3. JdbcTemplate 和他的兄弟们 1.2.1.3.1. 使用 JdbcTemplate 进行数据访问 1.2.1.3
4、.1.1. 初始化 JdbcTemplate 1.2.1.3.1.2. 基于 JdbcTemplate 的数据访问 1.2.1.3.1.3. 递增主键生成策略的抽象 1.2.1.3.1.4. Spring 中的 Lob 类型处理1.2.1.3.2. NamedParameterJdbcTemplate 1.2.1.3.2.1. NamedParameterJdbcTemplate 的功能 1.2.1.3.2.2. NamedParameterJdbcTemplate 的实现原理1.2.1.3.3. SimpleJdbcTemplate1.2.1.4. Spring 中的 DataSource
5、1.2.1.4.1. DataSource 的种类 1.2.1.4.2. DataSource 的访问方式 1.2.1.4.2.1. 本地 DataSource 访问 1.2.1.4.2.2. 远程 DataSource 访问1.2.1.4.3. 自定义 DataSource 实现 1.2.1.4.3.1. 新建 DataSource 实现 1.2.1.4.3.2. 为现有 DataSource 添加新行为1.2.1.5. JdbcDaoSupport1.2.2. 基于操作对象(Operation Object)的 Jdbc 使用方式 1.2.2.1. 基于操作对象的查询 1.2.2.1.1.
6、 MappingSqlQueryWithParameters 1.2.2.1.2. MappingSqlQuery 1.2.2.1.3. SqlFunction 1.2.2.1.4. UpdatableSqlQuery 1.2.2.1.5. 基于操作对象的 LOB 查询1.2.2.2. 基于操作对象的更新 1.2.2.2.1. SqlUpdate 1.2.2.2.2. BatchSqlUpdate 1.2.2.2.3. 基于操作对象的 LOB 更新1.2.2.3. 基于操作对象的存储过程调用 1.2.2.3.1. StoredProcedure1.3. Spring 对各种 ORM 的集成(O
7、RM Integration In Spring) 1.3.1. Spring 对 Hibernate 的集成 1.3.1.1. 旧日“ 冬眠 ”时光 1.3.1.2. “春天”里的 “冬眠”(Hibernate with Spring Framework) 1.3.1.2.1. HibernateTemplate 的登场 1.3.1.2.1.1. 基于 HibernateTemplate 的 Session 资源管理 1.3.1.2.1.2. 特定的 Hibernate 数据访问异常转译 1.3.1.2.1.3. 基本数据访问操作1.3.1.2.2. Spring 中的 SessionFac
8、tory 的配置及获取 1.3.1.2.2.1. LocalSessionFactoryBean 1.3.1.2.2.2. AnnotationSessionFactoryBean 1.3.1.2.2.3. 通过 JNDI 获取 SessionFactory1.3.1.2.3. HibernateDaoSupport1.3.2. Spring 对 iBatis 的集成 1.3.2.1. iBatis 实践之前生篇 1.3.2.2. iBatis 实践之今世篇 1.3.2.2.1. SqlMapClientTemplate 的实现 1.3.2.2.2. SqlMapClientTemplate
9、的使用 1.3.2.2.2.1. SqlMapClientTemplate 的构建 1.3.2.2.2.2. 使用 SqlMapClientCallback 进行数据访问 1.3.2.2.2.3. 基于 SqlMapClientTemplate 基本数据访问操作1.3.2.2.3. SqlMapClientDaoSupport1.3.3. Spring 中对其他 ORM 方案的集成概略 1.3.3.1. spring 对 JDO 的集成 1.3.3.1.1. Spring 中的 JDO 资源管理 1.3.3.1.2. Spring 的 JDO 异常转译 1.3.3.1.3. JdoDaoSup
10、port1.3.3.2. spring 对 TopLink 的集成 1.3.3.2.1. Spring 中的 toplink 资源管理 1.3.3.2.2. toplink 数据访问异常到 spring 异常体系的转译 1.3.3.2.3. TopLinkDaoSupport1.3.3.3. spring 对 JPA 的集成 1.3.3.3.1. Spring 中 JPA 的资源管理 1.3.3.3.2. Spring 中 JPA 的异常转译 1.3.3.3.3. JpaDaoSupport1.4. Spring 数据访问扩展篇 1.4.1. 活用模板方法模式及 Callback 1.4.1.
11、1. FTPClientTemplate 1.4.1.2. HttpClientTemplate1.4.2. 数据访问方式的选择漫谈 1.4.2.1. JDBC 选择漫谈 1.4.2.2. iBatis 选择漫谈 1.4.2.3. Hibernate 选择漫谈spring 框架为了简化 Java 应用程序开发,提供了一个数据访问层,该数据访问层主要可以划分为三个部分: 统一的数据访问异常层次体系(Exception Hierarchy). spring 框架将特定的数据访问技术相关的 Exception 进行转译,然后封装为一套标准的异常层次体系。 通过这套标准异常层次体系,不管使用的数据访问
12、技术如何变化,客户端对象只需要捕获并处理这套标准的 Exception 就可以, 再也不需要因为所使用的数据访问技术变更或者迁移等问题而做任何改动。 JDBC1 API 的最佳实践. JDBC 作为一套数据访问标准来说,是很成功的,他规范了各个数据库厂商之间的数据访问接口,极大的促进了 RDBMS2在 Java 平台上的迅速普及。但是,任何事物都有瑕疵,虽然 JDBC 作为一套标准来说很成功,但在 JDBC API 的设计和使用上则不尽然: o SQLException 设计本身没有将自身作为标准的职责进行到底,各种异常信息全部放给了各个 RDBMS 厂商,从而导致应用程序需要根据数据库提供商
13、的不同,来判定异常中所提供的信息具体是什么意思;o JDBC API 较为贴近底层,使用上比较繁琐,如果不做合适的封装,在该 API的使用上很容易造成问题,比如数据库连接没有释放就是最容易看到的情况; spring 框架针对 JDBC API 使用上的问题,提出了一套最佳实践。 以统一的方式对各种 ORM 方案的集成. 除了使用标准的 JDBC 进行数据库的访问,现在使用比较多的就是 ORM,它的全称为“Object Relational Mapping”,或者称之为“对象-关系映射” , 主要用来屏蔽对象与关系数据库之间结构的非一致性。大部分的 ORM API 在使用上都贴近于 JDBC A
14、PI 的使用风格, 所以,Spring 也以“JDBC API 的最佳实践”同样的方式对现有的各种 ORM 方案进行了集成,同时,将这些 ORM 特定的Exception 纳入了它那套统一的异常层次体系; 概况来说,Spring 的数据访问层实际上就是“一个中心,两个基本点”,即以“统一的数据访问异常层次体系”为核心,以“JDBC API 的最佳实践”和“以统一的方式对各种 ORM 方案的集成”为两翼, 为Java 平台的数据访问铺就了一条平坦的大道。 现在,让我们先从“统一的数据访问异常层次体系”开始,探索一下 Spring 的数据访问层到底蕴含什么奥妙吧! 1.1. 统一的数据访问异常层次
15、体系(Consistent Exception Hierarchy In Spring)1.1.1. DAO 模式的背景(Background of the DAO Pattern) 1.1.2. 梦想照进现实(The reality of implementing the DAO pattern) 1.1.3. 发现问题,解决问题(How to get through?) 1.1.4. 不重新发明轮子(Dont Reinvent The Wheels.)要了解 Spring 为什么要提供这么一套“统一的数据访问异常层次体系”,我们得先从 DAO 模式说起. 1.1.1. DAO 模式的背景(
16、Background of the DAO Pattern)不管是一个逻辑简单的小软件系统还是一个关系复杂的大软件系统,都需要对系统的相关数据进行访问和存储,而这些数据的存储机制和访问方式往往随场景不同而各异。 为了统一和简化相关的数据访问操作,J2EE 核心模式提出了 DAO 模式,即“Data Access Object”,中文通常称为“数据访问对象”。 通过 DAO 模式,我们可以完全将数据的访问方式和数据的存储机制相分离,很好的屏蔽掉了各种数据访问方式的差异性。 不论你数据是存储在普通的文本文件或者是 csv 文件,甚至关系数据库(RDBMS)或者 LDAP(Lightweight D
17、irectory Access Protocol)系统中, 使用 DAO 模式,数据访问的客户端代码可以完全忽视这种差异,而以统一的接口来访问相应数据。 空话多说无益,我们还是来看一个具体的应用 DAO 模式的场景吧! 我想,对于大部分软件系统来说,顾客信息的数据访问是大家经常接触的吧?我们就以这对顾客信息的数据访问为例,看一下使用 DAO 模式如何运作的。 使用 DAO 模式对顾客信息进行数据访问,我们首先需要声明一个数据访问对象接口定义: public interface ICustomerDao Customer findCustomerByPK(String customerId);v
18、oid updateCustomerStatus(Customer customer);/.对于客户端代码,比如通常的 Service 层代码,只需要声明依赖该数据访问接口即可,所有的数据访问全部通过该接口进行, 即使以后因为数据存储机制发生变化而导致 DAO 接口实现类发生变化,客户端代码也不需要做任何的调整。(当然,如果设计不良,依然会存在需要调整客户端代码的情况) public class CustomerService private ICustomerDao customerDao;public void disableCustomerCampain(String customerI
19、d)Customer customer = getCustomerDao().findCustomerByPK(customerId);customer.setCampainStatus(CampainStatus.DISABLE);getCustomerDao().updateCustomerStatus(customer);public ICustomerDao getCustomerDao() return customerDao;public void setCustomerDao(ICustomerDao customerDao) this.customerDao = custome
20、rDao;通常情况下,顾客信息大都存储于关系数据库当中,所以,相应的,我们会提供一个基于 JDBC 的DAO 接口实现类: public class JdbcCustomerDao implements ICustomerDao public Customer findCustomerByPK(String customerId) / TODO Auto-generated method stubreturn null;public void updateCustomerStatus(Customer customer) / TODO Auto-generated method stub可能随
21、着系统需求的变更,我们的顾客信息需要转移到 LDAP 服务,或者转而使用其他企业位于LDAP 服务中的顾客信息,又或者别人要使用我们的 CustomerService,但他们使用不同的数据存储机制, 这个时候,就需要提供一个基于 LDAP 的数据访问对象: public class LdapCustomerDao implements ICustomerDao public Customer findCustomerByPK(String customerId) / TODO Auto-generated method stubreturn null;public void updateCus
22、tomerStatus(Customer customer) / TODO Auto-generated method stub即使数据访问接口实现类随着需求发生变化,可是客户端代码(这里的 CustomerService)却可以完全忽视这种变化, 唯一需要变动的地方可能只是 Factory 对象的几行代码甚至只是 IoC 容器配置文件中简单的 class 类型替换而已,而客户端代码无需任何变动。 所以,DAO 模式对屏蔽不同数据访问机制的差异性起到举足轻重的作用。 但是,图画真的像我描述的那么美好吗?! 1.1.2. 梦想照进现实(The reality of implementing th
23、e DAO pattern)其实,之前针对 DAO 的例子为了简化概念的描述,我们省略了部分细节,比如接口与实现之间的某种“依赖性“ 。不管是 JdbcCustomerDao 还是 LdapCustomerDao,我们都省略了最基本的东西数据访问机制特定的代码。 当我们把这些特定于数据访问机制的代码引入的时候,问题就产生了,最明显的莫过于特定于数据访问机制的异常处理。 当我们把具体的 JDBC 代码充实到我们的 JdbcCustomerDao 的时候,看看哪里不对头那: public Customer findCustomerByPK(String customerId) Connection
24、 con = null;trycon = getDataSource().getConnection();/.Customer cust = .;return cust; catch (SQLException e) / throw or handle it here?finallyreleaseConnection(con);private void releaseConnection(Connection con) / TODO Auto-generated method stub使用 JDBC 进行数据访问,当期间出现问题的时候,JDBC API 会通过抛出 SQLException 的
25、方式来表明问题的发生,而 SQLException 属于“checked exception”, 所以,我们的 DAO 实现类需要捕获这种异常,并进行处理。 那么,该 DAO 实现类中捕获的 SQLException 进行如何的处理那?直接在 DAO 实现类处理掉?可是这样的话,客户端代码如何得知在数据访问期间发生了问题? 所以,我们只好先直接将SQLException 抛给客户端,进而,DAO 实现类的相应方法签名也需要修正为抛出 SQLException: public Customer findCustomerByPK(String customerId) throws SQLExcep
26、tion.相应的,DAO 接口定义中的相应方法签名也需要修改: public interface ICustomerDao Customer findCustomerByPK(String customerId) throws SQLException;void updateCustomerStatus(Customer customer);/.可是,这样就解决问题了吗? 问题 1 我们的数据访问接口对于客户端来说是通用的,不管我们的数据访问对象因为数据访问机制的不同而如何变更,客户端代码不应该受其牵连。 但是,现在因为使用 JDBC 做数据访问需要抛出特定的 SQLException,客户端
27、代码就需要捕捉该异常并做相应的处理。这是与数据访问对象模式的设计初衷相背离的。 问题 2 当我们引入另一种数据访问机制的时候,问题更是接踵而来。当我们再加入LdapCustomerDao 实现的时候,LdapCustomerDao 需要抛出 NamingException, 如果要保证 findCustomerByPK 方法是实现(implements )了 ICustomerDao 中的方法,那么我们就得更改 ICustomerDao 的方法签名: Customer findCustomerByPK(String customerId) throws SQLException,NamingE
28、xception;糟糕不是吗?我们又把统一的访问接口给改了,相应的客户端代码又要捕捉NamingException 做相应的处理,如果随着不同数据访问对象实现的增多,以及考虑数据访问对象中其他数据访问方法,这种糟糕的情况不得继续下去吗? 也就是说,因为数据访问机制的不同,我们的数据访问接口的定义现在变成了空中楼阁,我们无法最终定义并确定下这个接口,不是吗? 1.1.3. 发现问题,解决问题 (How to get through?)问题出现了,我们就应该尝试解决问题,因为数据访问对象模式所描述的场景我们实在不忍舍弃。 那么如何来避免以上问题那? 1. 既然将 SQLException 在 DA
29、O 实现类内部直接处理掉这条路走不通,而将SQLException 直接抛出又不可行,那我们将 SQLException 或者其他特定的数据访问异常进行封装后再抛出又会如何? 那么,以什么类型的异常进行封装然后再抛出那?是“checked exception”还是“unchecked exception”? 大部分的或者说所有的数据访问操作,其抛出的异常对于客户端来说属于系统的 Fault,客户端是无法有效处理的,比如数据库操作失败,无法取得相应资源等, 客户端对于这些情况最有效方式就是不做处理,因为客户端代码通常对于系统的 Fault 也无法处理(当然如果必要,捕捉后处理也是可以的,比如捕捉
30、相应异常后重试等), 所以,将SQLException 以及其他特定于数据访问机制的异常以“unchecked exception”进行封装然后抛出是比较合适的。 因为“unchecked exception”不需要编译器检查,ICustomerDao 的数据访问方法就可恢复其本来面目而实现“大同”: Customer findCustomerByPK(String customerId)各个 DAO 实现类内部只要将 SQLException 及其他特定的数据访问异常以“unchecked exception”进行封装: public Customer findCustomerByPK(St
31、ring customerId) Connection con = null;trycon = getDataSource().getConnection();/.Customer cust = .;return cust; catch (SQLException e) throw new RuntimeException(e);finallyreleaseConnection(con);现在,统一数据访问接口定义的问题解决! 2. 以单一的 RuntimeException 形式将特定的数据访问异常转换后抛出虽然解决了统一数据访问接口的问题,但是,该方案依然不够周全。 以 SQLExcept
32、ion 为例,各个数据库提供商通过 SQLException 来表达具体的错误信息的时候,所采用的方式是不同的,比如,有的数据库提供商采用 SQLException 的 ErrorCode 作为具体的错误信息标准, 有的数据库提供商则通过 SQLException 的 SqlState 来返回详细的错误信息,即使我们将 SQLException 封装后抛出给客户端对象,当客户端对象需要了解具体的错误信息的时候, 依然需要根据数据库提供商的不同,采取不同的信息提取方式,要知道,将这种错误信息的具体处理分散到各个客户端对象中处理又是何等的糟糕?!我们应该向客户端对象屏蔽这种差异性! 那么,如何来屏
33、蔽这种差异性那?答案当然是异常的分类转译(Exception Translation)! a. 首先,我们不应该将对特定的数据访问异常的错误信息提取工作下放给客户端对象进行处理,而是应该由 DAO 实现类或者某个工具类以统一的方式进行处理。我们暂且让具体的 DAO 实现类来做这个工作吧,那么对于我们的JdbcCustomerDao 来说,捕获异常后的处理就类似于: b. tryc. d. /.e. f. catch (SQLException e) g. h. if(isMysqlVendor()i. j. / 按照 Mysql 数据库的规则分析错误信息(e)然后抛出k. throw new RuntimeException(e);l. m. if(isOracleVendor()n.