从JDBC到commons-dbutils

1、前言

玩过Java web的人应该都接触过JDBC,正是有了它,Java程序才能轻松地访问数据库。JDBC很多人都会,但是为什么我还要写它呢?我曾经一度用烂了JDBC,一度认为JDBC不过如此,后来,我对面向对象的理解渐渐深入,慢慢地学会了如何抽象JDBC代码,再后来,我遇到了commons-dbutils这个轻量级工具包,发现这个工具包也是对JDBC代码的抽象,而且比我写的代码更加优化。在这个过程中,我体会到了抽象的魅力,我也希望通过这篇文章,把我的体会分享出来。

文章大致按一定的逻辑进行:JDBC如何使用-----这样使用有什么问题------如何改进-----分析commons-dbutils的原理

2、JDBC如何使用

这一小节通过一个例子来说明JDBC如何使用。

我们大致可以讲JDBC的整个操作流程分为4步:

1、获取数据库连接

2、创建statement

3、执行sql语句并处理返回结果

4、释放不需要的资源

下面是一个小例子(省略了try-catch代码):

String username="root";
String password="123";
String url="jdbc:mysql://localhost/test";
Connection con=null;
Statement st=null;
ResultSet rs=null;

//1、获取连接
Class.forName("com.mysql.jdbc.Driver");         con=DriverManager.getConnection(url,username,password);

//2、创建statement
String sql="select * from test_user";
st=con.createStatement();

//3、执行sql语句并处理返回结果
rs=st.executeQuery(sql);
while(rs.next())
 {
    //对结果进行处理
  }

//4、释放资源
rs.close();
st.close();
con.close();

以上的例子是查询的一种用法,除了用Statement外,还可以用PreparedStatement,后者是前者的子类,在前者的基础上增加了预编译和防止sql注入的功能。另外,查询和增删改是不同的用法,查询会返回ResultSet而增删改不会。

3、这样写代码有什么问题

3.1、这样写代码会造成大量重复劳动,比如获取连接,如果每个执行sql的方法都要写一遍相同的代码,那么这样的重复代码将充斥整个DAO层。

3.2、这样的代码可读性比较差,几十行代码真正和业务相关的其实就几行

3.3、大量重复代码会造成一个问题,那就是可维护性变差,一旦某个常量改变了,那么就需要把每个方法都改一遍

3.4、数据库连接是重量级资源,每调用一次方法都去创建一个连接,性能会存在瓶颈

4、如何改进

针对前面的问题中的1、2、3,改进的方法就是抽象,把可重用的代码抽象出去,单独组成一个模块,模块与模块之间实现解耦。由于整个JDBC操作流程分为4步,因此可以从这4步中下手去抽象。

4.1、获取数据库连接

我当时的解决方案是一次初始化很多连接放入list,然后用的时候取,现在的通用方法就是连接池,比如DBCP、C3P0等等。有兴趣的人可以去看看它们的源代码,看看是如何实现的

4.2、创建statement

我当时使用PreparedStatement进行处理,因为PreparedStatement会缓存已经编译过的sql

4.3、执行sql语句并处理返回结果

这块可以使用反射,将得到的结果封装成Java bean对象

4.4、释放资源

使用动态代理,改变connection的close方法的行为,将connection放回连接池

5、commons-dbutils的原理

虽然我做出了改进,但距离真正的解耦还差得远,而commons-dbutils作为commons开源项目组中的一个成员,在这方面做得还算不错,通过阅读它的源代码,可以学习如何抽象和解耦JDBC的操作流程。

5.1、整体结构

先看一下它有哪些类:

一共有27个类,但真正常用的是三大组件十几个类:门面组件、结果处理组件和行处理组件,其中门面组件提供程序入口,并进行一些参数检验等,结果处理组件则是核心所在,因为返回的结果可以是map,可以是list可以是JavaBean,这一块的变化很大,所以抽象出一个组件出来应对这些变化,行处理组件是从结果处理组件中分离出来的,它是结果处理组件的基础,无论哪种处理器,最终都要与一行数据打交道,因此,单独抽象出这一组件。

类名 描述
门面组件
QueryRunner 执行增删改查的入口
结果处理组件
ResultSetHandler 用于处理ResultSet的接口
AbstractKeyedHandler 将返回结果处理成键值对的抽象类
KeyedHandler
处理数据库返回结果,封装成一个Map,数据库表的一个列名为key,通常可以用主键,数据库中的一行结果以Map的形式作为value

BeanMapHandler 处理数据库返回结果,封装成一个Map,和KeyedHandler的唯一的不同是,每一行结果以Javabean的形式作为value
AbstractListHandler 将返回结果处理成链表的抽象类
ArrayListHandler
将返回结果处理成链表,这个链表的每个

元素都是一个Object数组,保存了数据库中对应的一行数据

ColumnListHandler
如果要取单独一列数据,可以用这个handler,用户指定列名,它返回这个

列的一个list

MapListHandler
和ArrayListHandler不同的是,链表的每个元素是个Map,这个Map代表数据库里的一行数据

ArrayHandler
将一行数据处理成object数组

BeanHandler
将一行数据处理成一个Java bean

BeanListHandler
将所有数据处理成一个list,list的元素时Java bean

MapHandler
将一行结果处理成一个Map

MapListHandler
将所有结果处理成一个list,list的元素时Map

ScalarHandler
这个类常常用于取单个数据,比如某一数据集的总数等等


行处理组件

RowProcessor 用于处理数据库中一行数据的接口
BasicRowProcessor 基本的行处理器实现类
BeanProcessor 通过反射将数据库数据转换成Javabean
工具类
DbUtils 包含很多JDBC工具方法

5.2 执行流程

无论是增删改查,都需要调用QueryRunner的方法,因此QueryRunner就是执行的入口。它的每个方法,都需要用户提供connection、handler、sql以及sql的参数,而返回的则是用户想要的结果,这可能是一个List,一个Javabean或者仅仅是一个Integer。

1、以查询为例,QueryRunner内部的每一个查询方法都会调用私有方法,先去创建 PreparedStatement,然后执行sql得到ResultSet,然后用handler对结果进行处理,最后释放连接,代码如下:

 1  private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
 2             throws SQLException {
 3         if (conn == null) {
 4             throw new SQLException("Null connection");
 5         }
 6
 7         if (sql == null) {
 8             if (closeConn) {
 9                 close(conn);
10             }
11             throw new SQLException("Null SQL statement");
12         }
13
14         if (rsh == null) {
15             if (closeConn) {
16                 close(conn);
17             }
18             throw new SQLException("Null ResultSetHandler");
19         }
20
21         PreparedStatement stmt = null;
22         ResultSet rs = null;
23         T result = null;
24
25         try {
26             stmt = this.prepareStatement(conn, sql); //创建statement
27             this.fillStatement(stmt, params);  //填充参数
28             rs = this.wrap(stmt.executeQuery()); //对rs进行包装
29             result = rsh.handle(rs);  //使用结果处理器进行处理
30
31         } catch (SQLException e) {
32             this.rethrow(e, sql, params);
33
34         } finally {
35             try {
36                 close(rs);
37             } finally {
38                 close(stmt);
39                 if (closeConn) {
40                     close(conn);
41                 }
42             }
43         }
44
45         return result;
46     }

2、每个handler的实现类都是以抽象类为基础,看代码(以AbstractListHandler为例):

 1     @Override
 2     public List<T> handle(ResultSet rs) throws SQLException {
 3         List<T> rows = new ArrayList<T>();
 4         while (rs.next()) {
 5             rows.add(this.handleRow(rs));
 6         }
 7         return rows;
 8     }
 9
10     /**
11      * Row handler. Method converts current row into some Java object.
12      *
13      * @param rs <code>ResultSet</code> to process.
14      * @return row processing result
15      * @throws SQLException error occurs
16      */
17     protected abstract T handleRow(ResultSet rs) throws SQLException;
handle方法都是一样的,这个方法也是QueryRunner内部执行的方法,而不一样的在handleRow这个方法的实现上。这里用到了模板方法的设计模式,将不变的抽象到上层,易变的下方到下层。

  3、每个handleRow的实现都不一样,但最终都会使用行处理器组件,行处理器是BasicRowProcessor,有toArray,toBean,toBeanList,toMap这些方法toArray和toMap是通过数据库的元数据来实现的,而toBean和toBeanList则是通过反射实现,具体可以去看源代码实现,应该是比较好理解的。

5.3、和数据源的结合 从上面可以看出,dbutils抽象了2、3、4(JDBC 4步骤),而没有把连接的获取抽象,其实,连接的获取和维护本身就有其他组件提供,也就是datasource数据源,dbutils只负责2、3、4,不该它管就不管,这样才能做到解耦。在构造QueryRunner的时候,可以选择传入一个数据源,这样,在调用方法的时候,就不需要传入connection了。

5.4、总结 使用dbutils再加上DBCP数据源,可以极大的简化重复代码,提高代码可读性和可维护性,以下是使用dbutils的一个小例子:
 1 /**
 2      * 获取常用地址
 3      * */
 4     public List<CommonAddr> getCommAddrList(int memID) {
 5         String sql = "SELECT `addrID`, `addr`, `phone`, `receiver`, `usedTime` "
 6                 + "FROM `usr_cm_address` WHERE `memID`=? order by usedTime desc";
 7
 8         try {
 9             return runner.query(sql, new BeanListHandler<CommonAddr>(CommonAddr.class),memID);
10         } catch (SQLException e1) {
11             logger.error("getCommAddrList error,e={}",e1);
12         }
13         return null;
14     }

如果用最原始的JDBC来写,光把数据库结果转换成List估计都要十几行代码吧。

6、尾声

从JDBC到dbutils,实现的功能没有变,但是代码却简洁了,程序与程序之间的关系也更清晰了,这,也许就是面向对象的精髓吧~

 
时间: 2024-10-06 01:17:20

从JDBC到commons-dbutils的相关文章

高性能jdbc封装工具 Apache Commons DbUtils 1.6

转载自原文地址:http://gao-xianglong.iteye.com/blog/2166444 前言 关于Apache的DbUtils中间件或许了解的人并不多,大部分开发人员在生成环境中更多的是依靠Hibernate.Ibatis.Spring JDBC.JPA等大厂提供的持久层技术解决方案,或者是企业内部自己研发的持久层技术.但无论如何,使用这些技术的初衷和本质都是为了能够减少企业开发成本,提高生产效率,降低耦合. 放眼企业级项目,Hibernate等ORM产品是首选,而互联网领域,大

高性能jdbc封装工具 Apache Commons DbUtils 1.6(转载)

转载自原文地址:http://gao-xianglong.iteye.com/blog/2166444 前言 关于Apache的DbUtils中间件或许了解的人并不多,大部分开发人员在生成环境中更多的是依靠Hibernate.Ibatis.Spring JDBC.JPA等大厂提供的持久层技术解决方案,或者是企业内部自己研发的持久层技术.但无论如何,使用这些技术的初衷和本质都是为了能够减少企业开发成本,提高生产效率,降低耦合. 放眼企业级项目,Hibernate等ORM产品是首选,而互联网领域,大

Apache Commons DbUtils

1       概述 Commons DBUtils类库是小型的设计于易于使用JDBC的类集合.JDBC资源清理是平凡的,容易出错,以至于这些类从你的代码中抽象出清理代码,剩下你最初真正想要使用JDBC做的代码:查询和更新数据. 使用DBUtils的一些优势: 没有资源泄露的可能性.正确的JDBC编码并不难,但它耗时且乏味.这常常会导致连接泄漏可能很难追踪. 清晰的持久化代码.需要持久化数据到数据库中的代码大幅减少.剩下的代码清晰的表达你的意图没有凌乱的资源清理. 从ResultSet自动填充J

Commons - dbutils

时间:2016-12-4 18:06 --基本类 自己写的JDBC小工具: public class JDBCUtils { // 使用的是配置文件的默认配置,即必须给出c3p0-config.xml配置文件 private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); /* * 使用连接池返回一个连接对象 */ public static Connection getConnection() thr

Java -- JDBC 学习--使用 DBUtils

Apache-DBUtils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能.API介绍:org.apache.commons.dbutils.QueryRunnerorg.apache.commons.dbutils.ResultSetHandler工具类org.apache.commons.dbutils.DbUtils 如下利用

Apache Commons DbUtils使用手册

Apache Commons DbUtils使用手册 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.介绍 DBUtils是个小巧的JDBC轻量级封装的工具包,其最核心的特性是在JDBC的基础上做了一层封装,主要是对结果集的封装,可以直接将查询出来的结果集封装成JavaBean,旨在简化JDBC代码混乱与重复.JDBC代码开发,存在很多难点:1)操作过程复杂,代码操作一个模式,大量的重复.2)结果集难以处理.3)到处都强制检查SQLExcepti

Java连接数据库 #04# Apache Commons DbUtils

索引 通过一个简单的调用看整体结构 Examples 修改JAVA连接数据库 #03# HikariCP中的代码 DbUtils并非是什么ORM框架,只是对原始的JDBC进行了一些封装,以便我们少写一些重复代码.就“用”而言,仅仅需要学习QueryRunner类和ResultSetHandler接口就可以了.它的显著特点就是超级轻量级,总代码量目测似乎还不到一万行. 通过一个简单的调用看整体结构 public class TestDbUtils { private static final Qu

java.lang.ClassNotFoundException: org.apache.commons.dbutils.QueryRunner

七月 28, 2017 11:06:33 下午 org.apache.catalina.core.StandardWrapperValve invoke严重: Servlet.service() for servlet [com.itheima.transfer.web.TransferServlet] in context with path [/WEB19] threw exception [Servlet execution threw an exception] with root ca

开源JDBC工具类DbUtils

本篇将会详细地介绍Apache公司的JDBC帮助工具类DbUtils以及如何使用.在上一篇中我们已经通过将以前对dao层使用JDBC操作数据库的冗余代码进行了简易封装形成自己的简单工具类JdbcUtils,而在这过程中很多都是借鉴和参考了DbUtils的代码,因此通过上一篇的学习,会让我们在对DbUtils进行更快速简单的认识. 俗话说学习一个开源的工具最好的方法就是看其官方文档,是的,在Apache官网中对DbUtils进行了详细的介绍:http://commons.apache.org/pr

java基础之JDBC九:DbUtils的简介及使用

DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能. 使用步骤: A: 获取可以执行SQL语句的对象. public QueryRunner(); public QueryRunner(DataSource ds); //如果需要用到连接池对象了, 会自动从数据库连接池中获取. B: 执行SQL语句, 获取结果集. *** query(String sql,ResultSetHandler rsh, Ob