简化JDBC的数据库访问
问题的产生:
在一些小型应用中,JDBC扮演着一个让人进退两难的角色。
最近我遇到了这样一个问题:项目不大,犯不着使用Hibernate之类的持久化技术,但JDBC带来的大量重复代码着实让维护工作举步维艰。
传统的数据库Query方法是这样的:
构造SQL语句 -> 从连接池中获取Connection -> 创建Statement -> 执行SQL查询请求 -> 处理ResultSet -> 关闭ResultSet以及Statement -> 释放Connection
大致的代码如下:
String SQL = "SELECT name, pass FROM table WHERE id = '"+ id +"'";
Connection conn = ConnectionPool.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.exeQuery(SQL);
while(rs.next()){
//.......
}
rs.close();
stmt.close();
ConnectionPool.release(conn);
这样带来一个问题:由于每次查询都要有这么一个套路,那么一旦改变了获取和释放Connection的策略,维护的工作量是巨大的。
如果你也像我一样想减少Ctrl-c和Ctrl-v的次数,那么请继续看下去吧。
分析:
事实上我们所关注的只有两个地方
1. 构造SQL语句
2. 处理ResultSet过程
其余部分都是简单的重复,按照重构的原则,因该尽量减少重复的代码,而把更多的注意力集中在对ResultSet的处理上。
假设我们有一个叫做doQuery的函数,用来统一处理 Query过程。
也许你会想到把构造好的SQL传递给doQuery,然后返回一个ResultSet,再另行处理ResultSet,这样的doQuery看起来会是这样:
public ResultSet doQuery (String SQL){
Connection conn = ConnectionPool.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.exeQuery(SQL);
stmt.close(); //ResultSet也同时被关闭了
ConnectionPool.release(conn);
return rs;
}
看起来不错,但是这样做你是无法处理ResultSet的,因为在Statement关闭的同时ResultSet也被关闭了。所以必须在同一个函数里创建、处理并关闭ResultSet。
解决方案:
解决方案是把构造好的SQL和处理ResultSet的过程同时传递给doQuery。可是,怎么实现呢?别忘了Java可是OO的。OO的第一大特性是什么?对,封装。我们可以把ResultSet的处理过程封装起来。
具体方法如下:
首先我们需要为每一次查询请求定义一个Query接口。接口里面定义两个方法:
interface Query{
public String getSQL(); // 获得这个查询的SQL
public Object doProcess(ResultSet rs); // 封装了对ResultSet的处理过程
}
下面轮到我们传说中的doQuery函数出场了:
class DBTool{
public static Object doQuery (Query query){
Connection conn = ConnectionPool.getConnection();
Statement stmt = conn.createStatement();
String SQL = query.getSQL(); // 获得这次查询的SQL
ResultSet rs = stmt.exeQuery(SQL); // 在这里创建了ResultSet实例
Object o = query.doProcess(rs); // 完成对ResultSet的处理
rs.close(); // 这里关闭ResultSet就不会有任何问题了 :)
stmt.close();
ConnectionPool.release(conn);
return o;
}
}
为了表达清楚,上面是一个简单的框架,省略了try – catch 以及一些容错判断的部分。
万事具备了,怎么使用呢?难道每次查询还要写一个实现了Query的类吗?
当然不用!要不然岂不是多此一举。还记得Swing里面处理Listener的方法吗。我们可以用匿名内部类来实现:
Object o = DBTool. doQuery (
new Query(){ // 一个实现了Query接口的匿名内部类
public String getSQL(){
String SQL = "SELECT name, pass FROM table WHERE id < '"+ id +"'";
return SQL;
}
public Object doProcess(ResultSet rs){
Vector vector = new vector();
User user = null;
while(rs.next()) {
user = new User ();
user.setName (rs.getString ("name"));
user.setPass (rs.getString("pass"));
vector.add(user);
}
return vector;
}
} // End new Query()
);
// 虽然有可能 抛出 ClassCastException 但这里 返回值的 类型 应该是明确的
Vector vector = (Vector)o;
代码中只剩下我们所关心的构造SQL和处理ResultSet的部分,是不是清爽多了:)
小结:
doQuery里采用了一个“Don’t call us, we will call you”的模版方法模式,这样就可以把我们所关心的代码部分独立出来了。
同样,我们还可以在DBTool里实现doUpdate()和doExecute()这两个方法,由于不牵涉到ResultSet,所以实现起来要简单得多。这里就不多说了。
这样我们用一个DBTool来帮助我们简化所有的数据库操作,省去了大量重复的代码,快哉快哉 :)
.蓝山咖啡.
2004.8.21 夜