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