Spring JDBC最佳实践(1)

Spring提供了两种使用JDBC API的最佳实践,一种是以JdbcTemplate为核心的基于Template的JDBC的使用方式,另一种则是在JdbcTemplate基础之上的构建的基于操作对象的JDBC的使用方式。

基于Template的JDBC的使用方式
该使用方式的最初设想和原型,需要追溯到Rod Johnson在03年出版的Expert One-on-One J2EE Design and Development,在该书的Practical Data Access(数据访问实践)中,Rod针对JDBC使用中的一些问题提出了一套改进的实践原型,并最终将该原型完善后在Spring框架中发布。

JDBC的尴尬
JDBC作为Java平台的访问关系数据库的标准,其成功是 有目共睹的。几乎所有java平台的数据访问,都直接或者间接的使用了JDBC,它是整个java平台面向关系数据库进行数据访问的基石。
作为一个标准,无疑JDBC是很成功的,但是要说JDBC在使用过程当中多么的受人欢迎,则不尽然了。JDBC主要是面向较为底层的数据库操作,所以在设计的过程当中 ,比较的贴切底层以提供尽可能多的功能特色。从这个角度来说,JDBC API的设计无可厚非。可是,过于贴切底层的API的设计,对于开发人员则未必是一件好事。即使执行一个最简单的查询,开发人员也要按照API的规矩写上一大堆雷同的代码,如果不能合理的封装使用JDBC API,在项目中使用JDBC访问数据所出现的问题估计会使人发疯!
对于通常的项目开发来说,如果层次划分很明确,数据访问逻辑一般应该在DAO层中实现。根据功能模块的划分,可能每个开发人员都会分得或多或少的实现相应的DAO的任务,假设开发人员A在分得了DAO实现任务后进行开发,他或许开发了如下所示的代码:

01 package com.google.spring.jdbc;
02  
03 import java.sql.Connection;
04 import java.sql.SQLException;
05 import java.sql.Statement;
06  
07 import javax.sql.DataSource;
08  
09 import org.apache.commons.logging.Log;
10 import org.apache.commons.logging.LogFactory;
11  
12 public class DaoWithA implements IDao
13 {
14  
15     private final Log logger = LogFactory.getLog(DaoWithA.class);
16     private DataSource dataSource = null;
17      
18     public DataSource getDataSource()
19     {
20         return dataSource;
21     }
22  
23     public void setDataSource(DataSource dataSource)
24     {
25         this.dataSource = dataSource;
26     }
27  
28     @Override
29     public int updateSomething(String sql)
30     {
31         int count;
32         Connection conn = null;
33         Statement stmt = null;
34         try
35         {
36             conn = getDataSource().getConnection();
37             stmt = conn.createStatement();
38             count = stmt.executeUpdate(sql);
39             stmt.close();
40             stmt = null;
41         }
42         catch (SQLException e)
43         {
44             throw new RuntimeException(e);
45              
46         }
47         finally
48         {
49             if(stmt!=null)
50             {
51                 try
52                 {
53                     stmt.close();
54                 }
55                 catch (SQLException ex)
56                 {
57                     logger.warn("fail to close statement:"+ex);
58                 }
59             }
60             if(conn!=null)
61             {
62                 try
63                 {
64                     conn.close();
65                 }
66                 catch (Exception ex)
67                 {
68                     logger.warn("failed to close Connection:"+ex);
69                 }
70             }
71         }
72         return count;
73     }
74  
75 }

而B所负责的DAO的实现中,可能也有类似的更新的操作。无疑,B也要像A这样,在他的DAO实现类中写一大堆同样的JDBC代码,类似的情况还可能扩展到C、D等开发人员。如果每个开发人员都能严格的按照JDBC的编程规范开发还好,但是事实是,一个团队中的开发人员是有差别的。 
这其实只是API的使用过程中的一个插曲,当你看到应用程序中成百的使用JDBC实现类的时候,会发现如下的问题: 
1、Statement使用完没有关闭,而是想着让Connection关闭的时候一并关闭,可是并非所有的驱动程序都有这样的行为。 
2、创建了多个ResultSet或者Statement,只清理了最外层的,忽视了里层的。 

3、忘记关闭Connection。
JDBC规范在指定数据库访问异常的时候也没有能够进行的很彻底:
1、将异常类型定义为SQLException是一个值得商榷的地方。
2、SQLExcpetion没有采用将具体的异常情况子类化,以进一步抽象不同的数据访问的情况,而是采用ErrorCode的方式来区分访问过程中所出现的不同异常情况,其实这也没什么,只要能区分出具体的错误就行,但是JDBC规范却把ErrorCode的规范留给了数据库提供商,这导致了不同的数据库供应商对应了不同的ErrorCode,进而应用程序在捕获到SQLException后,还要看当前用的是什么数据库。
针对以上问题,Spring提供了相应的解决方案帮助我们提高开发效率!

为了解决JDBC API在实际使用中的各种尴尬的局面,spring提出了org.springframework.jdbc.core.JdbcTemplate作为数据访问的Helper类。JdbcTemplate是整个spring数据抽象层提供的所有JDBC API最佳实践的基础,框架内其它更加方便的Helper类以及更高层次的抽象,全部的构建于JdbcTemplate之上。抓住了JdbcTemplate,就抓住了spring框架JDBC API最佳实践的核心。
概括的说,JdbcTemplate主要关注一下两个事情:
1、封装所有的基于JDBC的数据访问的代码,以统一的格式和规范来使用JDBC API。所有的基于JDBC API的数据访问全部通过JdbcTemplate,从而避免了容易出错的数据访问方式。
2、对SQLException所提供的异常信息在框架内进行统一的转译,将基于JDBC的数据访问异常纳入Spring自身的异常层次之中,统一了数据接口的定义,简化了客户端代码对数据访问异常的处理。
Spring主要是通过模板方法对基于JDBC的数据访问代码进行统一的封装,所以我们可先看下模板方法:
模板方法主要是用于对算法的行为或者逻辑进行封装,即如果多个类中存在相似的算法逻辑或者行为逻辑,可以将这些逻辑提取到模板方法中实现,然后让相应的子类根据需要实现某些自定义的逻辑。
举个例子,所有的汽车,不管是宝马还是大众,他们的驾驶流程基本上是固定的。实际上,除了少数的实现细节有所不同之外,大部分的流程是相同的,基本上是如下所示的流程说明:
1、点火启动
2、踩刹车,挂前进的档位(不同的车在这一步会存在差异)
3、放下手动控制器(手刹)
4、踩油门启动车辆运行
此时,我们可以声明一个模板方法类,将确定的行为以模板的形式定义,而将不同的行为留给相应的子类来实现:

01 package com.google.spring.jdbc;
02  
03 public abstract class Vehicle
04 {
05      
06     public final void drive()
07     {
08         startTheEnginee();//启动
09         putIntoGear(); //前进
10         looseHandBrake();//放下手刹
11         stepOnTheGasAndGo();//踩油门前进
12     }
13      
14     protected abstract void putIntoGear();
15      
16     private void startTheEnginee()
17     {
18          
19     }
20      
21     private void looseHandBrake()
22     {
23          
24     }
25      
26     private void stepOnTheGasAndGo()
27     {
28          
29     }
30 }

drive()方法就是我们的模板方法,它被声明为final,表示该类是不能被子类重写的,车辆的自动挡和手动挡是不同的,所以留给了子类去实现: 
01 package com.google.spring.jdbc;
02  
03 public class VehicleAT extends Vehicle
04 {
05  
06     @Override
07     protected void putIntoGear()
08     {
09         //挂前进档位
10  
11     }
12  
13 }

01 package com.google.spring.jdbc;
02  
03 public class VehicleMT extends Vehicle
04 {
05  
06     @Override
07     protected void putIntoGear()
08     {
09         //踩离合器 挂前进档位
10  
11     }
12  
13 }

这样,每个子类实现特有的逻辑就可以了。

 JdbcTemplate的演化
如果回头看一下最初的使用JDBC API进行数据访问的代码。就会发现,不管这些代码是谁负责的,也不管数据访问的逻辑如何,除了小部分的差异之外,所有的这些代码几乎都是按照同一个流程走下来的,如下:
1、conn=getDataSource().getConnection();
2、stmt=conn.createStatement()或者ps=conn.prepareStatement();
3、stmt.executeUpdate(sql)或者ps.executeUpdate()  或者进行相应的查询。
4、stmt.close()  stmt=null
5、catch处理数据库访问异常
6、关闭数据库连接避免连接泄露导致系统崩溃
对于多个DAO中充斥着几乎相同的JDBC API的使用代码,我们也可以采用模板方法,多这些代码进行重构,避免因个人操作不当所出现的种种问题,我们要做的,就是将一些公共的行为提取到模板方法中去,而特有的操作,比如每次执行不同的更新,或者对不同的查询结果进行不同的处理,则放入具体的子类中,这样,我们就有个JdbcTemplate的雏形:

01 package com.google.spring.jdbc;
02  
03 import java.sql.Connection;
04 import java.sql.SQLException;
05 import java.sql.Statement;
06  
07 import javax.sql.DataSource;
08  
09 import org.springframework.dao.DataAccessException;
10  
11  
12 public abstract class JdbcTemplate
13 {
14     private DataSource dataSource;
15      
16      
17     public DataSource getDataSource()
18     {
19         return dataSource;
20     }
21  
22  
23     public void setDataSource(DataSource dataSource)
24     {
25         this.dataSource = dataSource;
26     }
27  
28  
29     public final Object execute(String sql)
30     {
31         Connection conn = null;
32         Statement stmt = null;
33         try
34         {
35             conn = this.getDataSource().getConnection();
36             stmt = conn.createStatement();
37             Object retValue = this.executeWithStatement(stmt, sql);
38             return retValue;
39         }
40         catch (SQLException e)
41         {
42             throw new RuntimeException(e);
43         }
44         finally
45         {
46             closeStatement(stmt);
47             closeConnection(conn);
48         }
49     }
50      
51     protected abstract Object executeWithStatement(Statement stmt,String sql);
52      
53     private final DataAccessException translateSQLException(SQLException e)
54     {
55         DataAccessException dataAccessException = null;
56         //进行相应的转译
57         return dataAccessException;
58     }
59      
60     private final void closeStatement(Statement stmt)
61     {
62         //关闭Statement
63     }

你可能感兴趣的:(Spring JDBC)