实战派JSP教程-commons-dbutils工具类库

在“三层架构”一章中,我们曾自己封装了executeAddOrUpdateOrDelete()和executeQuery()等方法,并讨论过:对于“增删改”的通用方法executeAddOrUpdateOrDelete(String sql ,Object[] os)来说,只要传入sql参数和置换参数os,就能实现相应的增删改功能;但对于查询方法executeQuery(String sql, Object[] os),为了能够“通用”,我们只能封装到结果集ResultSet,而不能继续封装成对象或集合等类型。在本小节,我们换一种方式,通过使用commons-dbutils类库可以实现:无论是“增删改”还是“查”都可以得到彻底的封装。

commons-dbutils是Apache组织提供的一个JDBC工具类库,极大的简化了JDBC的代码量,并且不会影响程序的性能。

读者可以通过Apache官网下载commons-dbutils类库:

http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi

与下载其他类库一样,Binaries提供了可供使用类库及说明文件,Source提供了类库的源代码;并且Binaries和Source都提供了.tar.gz(Linux系统)和.zip(Windows系统)两种格式的压缩包供读者下载,如图7.4所示。

图下载页面

本节使用的是最新版commons-dbutils-1.6进行讲解。

commons-dbutils类库主要包含了两个类和一个接口,如表所示。

表commons-dbutils类库

以下,是详细的说明。

1.DbUtils类

DbUtils是一个工具类,提供了关闭连接、事务提交/回滚、注册JDBC驱动程序等常用方法。DbUtils类中的方法都是public static修饰的(除了构造方法),常用方法如表所示。

表DbUtils常见方法

2.QueryRunner类

QueryRunner类主要用于执行增删改查等SQL语句。特别的,如果执行的是查询SQL,还需要结合ResultSetHandler接口来处理结果集。QueryRunner类的常用方法如表所示。

表QueryRunner常见方法

3.ResultSetHandler接口及其实现类

ResultSetHandler接口用于处理ResultSet结果集,它可以将结果集中的数据封装成单个对象、数组、List、Map等不同形式。

ResultSetHandler接口有很多不同的实现类,如图所示。

图ResultSetHandler实现类

本小节会对其中常用的10个实现类做详细讲解。

讲解前,需要先导入DbUtils包(commons-dbutils-1.6.jar),并使用我们之前编写过的两个类和一张表,如下:

数据源工具类DataSourceUtil

实体类Student(JavaBean)

数据库中的student表

表中的数据如图所示。

图student表

接下来,结合QueryRunner类的query()方法,进行具体演示。

(1)ArrayHandler和ArrayListHandler

ArrayHandler类可以把结果集中的第一行数据封装成Object[]。例如,可以将student表中的第一行数据,封装成一个Object[]类型的stu对象,封装后的效果类似于Object[] stu = new Object[],如下:

运行结果如图所示。

图运行结果

可以发现,ArrayHandler只能封装结果集中的第一行数据,如果想封装结果集中的全部数据,就需要使用ArrayListHandler。

ArrayHandler类可以把结果集中每一行数据都封装成一个Object[]对象,然后再将所有的Object[]组装成一个List对象,如下:

运行结果如图所示。

图运行结果

(2)BeanHandler、BeanListHandler和BeanMapHandler

ArrayHandler和ArrayListHandler是将结果集中的数据封装成Object[]对象,而BeanHandler、BeanListHandler和BeanMapHandler可以将结果集中的数据封装成JavaBean对象,并且通过泛型指定具体的JavaBean类型。

BeanHandler

BeanHandler类可以把结果集中的第一行数据封装成JavaBean。例如,可以将student表中的第一行数据,封装成一个Student类型的stu对象,封装后的效果类似于Student stu = new Student(15,”王五”,23),如下:

运行结果如图所示。

图运行结果

可以发现,BeanHandler只能封装结果集中的第一行数据,如果想封装结果集中的全部数据,就需要使用BeanListHandler。

BeanListHandler

BeanListHandler类可以把结果集中每一行数据都封装成一个JavaBean对象,然后再将所有的JavaBean对象组装成一个List对象,如下:

运行结果如图所示(只显示了部分结果)。

图运行结果

BeanMapHandler

与BeanListHandler类似,BeanMapHandler也会把结果集中每一行数据都封装成一个JavaBean对象,但不同的是:BeanMapHandler会将所有的JavaBean对象组装成一个Map对象,如下:

运行结果如图所示。

图运行结果

说明:

问:此程序中,Map的key值为什么是BigDecimal类型,而不是Integer?

答:本程序采用的是Oracle数据库,stuNo列在表中的类型是:NUMBER(3)。Oracle在处理NUMBER类型时比较特殊:如果发现存储的是整数(如果数字15),则会默认映射为BigDecimal类型,而不是Integer。

(3)MapHandler、MapListHandler和KeyedHandlerMapHandler

MapHandler可以将结果集中的第一条数据封装到Map对象中,并且key是字段名,value是字段值,如下:

运行结果如图所示。

图运行结果

MapListHandler

MapListHandler可以将结果集中的每一条数据都封装到Map对象中,并且key是字段名,value是字段值;然后再将所有的Map对象组装成一个List对象,如下:

运行结果如图所示(只显示了部分结果)。

图运行结果

KeyedHandler

KeyedHandler可以将结果集中的每一条数据都封装到Map对象中,并且key是字段名,value是字段值;然后再将所有的Map对象组装成一个范围更大的Map对象,并通过KeyedHandler的构造方法指定某一字段值为key,如下:

运行结果如图所示(只显示了部分结果)。

图运行结果

(4)ColumnListHandler

ColumnListHandler可以把结果集中某一列的值封装到List集合中,如下:

运行结果如图所示。

图运行结果

(5)ScalarHandler

如果执行的是单值查询,如select count(*) from student或select name from student where id = 15等结果为单值得查询,就需要使用ScalarHandler类,如下:

运行结果如图所示。

图运行结果

以上就是ResultSetHandler接口的10个实现类的具体用法。为了方便读者对比记忆,现做以下总结,如表所示。

4.增删改操作

现在,我们再对QueryRunner类中,用于增删改的update()方法做以演示,如下:

运行结果如图所示。

图运行结果

5.手动处理事务

(1)ThreadLocal

在学习事务处理之前,我们有必要先了解一下ThreadLocal类。

ThreadLocal可以为变量在每个线程中都创建一个副本,每个线程可以访问自己内部的副本变量。因此,ThreadLocal被称为线程本地变量(或线程本地存储)。

先看下面一个例子:

这段代码在单线程中使用没有任何问题;但如果是在多线程中使用,就存在线程安全问题,例如:因为conn是静态全局变量(用于共享),那么就有可能在一个线程使用conn操作数据库时,另外一个线程也同时在调用closeConnection()关闭链接;如果多个线程同时进入if语句,那么在getConnection()方法中就会多次创建conn对象。对于这样的线程问题,读者可能会想到用“线程同步”来解决:将conn变量、getConnection()和closeConnection()进行同步处理。对于本例,“线程同步”虽然可以解决问题,但却会造成极大的性能影响:当一个线程在使用conn访问数据库时,其他线程只能等待。

我们仔细来分析这个问题:本例的线程安全问题,实质是因为conn变量、getConnection()和closeConnection()都是共享的static变量(或方法)而造成的,那么此三者如果不是共享的static呢?实际上,一个线程只需要维护自己的conn变量,而不需要关心其他线程是否对各自的conn进行了修改。因此,不是staitc也可以,如下:

以上,将conn及相关方法的static修饰符去掉,然后在每个使用conn的方法中(如insert())都创建局部变量。这样一来,因为每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是,由于在方法中需要频繁地开启和关闭数据库连接,就会导致服务器压力非常大,并且严重影响程序执行性能。

如何既不影响性能,也能避免线程安全问题?使用ThreadLocal!ThreadLocal在每个线程中对该变量会创建一个副本;即每个线程内部都会有一个该变量的副本,该副本在线程内部任何地方都可以共享使用,但不同线程的副本之间互不影响。

ThreadLocal类中有以下几个方法,如表所示。

ThreadLocal类的具体使用,我们会在“手动处理事务”中进行演示。

(2)手动处理事务

前面讲过,如果使用QueryRunner类的无参构造,我们就需要手动管理事务;如果使用有参构造QueryRunner(DataSource ds),DbUtils就会替我们自动管理事务。之前演示的增删改查,使用的都是有参构造,即自动管理事务;以下,就来讲解如何使用无参构造来实现手动的事务管理。

通过模拟一个“银行转账”的事务,演示具体的步骤:

创建银行账户表

创建银行账户表account,并增加两条数据,如图所示。

图account表

创建实体类

创建与account表对应的实体类Account.java,如下:

创建JDBC工具类

创建JDBCUtil类,用于提供创建连接、开启事务、提交事务、关闭连接等方法。我们知道,一个事务对应一个Connection,但一个事务可能涉及多个DAO操作。如果DAO操作中的Connection是从连接池获取,那么多个DAO操作就会用到多个Connection,这样是无法完成一个事务的(一个事务用到了多个Connection)。因此,需要使用ThreadLocal类。

我们可以生成一个Connection对象,然后放在ThreadLocal中,那么这个线程中的任何对象都可以共享这个Connection对象,最后在线程结束后删除这个连接。这样就保证了一个事务一个连接。如下:

创建DAO层

创建用于模拟用户查询、转入、转出等数据库操作的DAO层,如下:

接口:

实现类:

创建Service层

模拟转账业务操作,如下:

测试

编写main()方法,测试转账业务,如下

测试之前,accout表的数据如图所示。

图account表

执行main()方法进行测试,运行结果如图所示。

图运行结果

此时,account表的数据如图所示。

图account表

可以看出,转账功能已经成功实现了。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180612A1AW2P00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券