一、概述
1.1 前言
关于Apache的DbUtils中间件或许了解的人并不多,大部分开发人员在生成环境中更多的是依靠Hibernate、Ibatis、Spring JDBC、JPA等大厂提供的持久层技术解决方案,或者是企业内部自己研发的持久层技术。但无论如何,使用这些技术的初衷和本质都是为了能够减少企业开发成本,提高生产效率,降低耦合。
放眼企业级项目,Hibernate等ORM产品是首选,而互联网领域,大部分开发人员往往并不会在生产环境中上这些ORM技术,原因很简单,要的就是效率,其次都不重要。对于刚接触SQL和JDBC的开发人员,最引以为傲的就是希望能够在日后编写复杂的SQL语句,以及会使用诸如Hibernate、Ibatis等第三方持久层技术,并且极力的撇清与传统JDBC技术的关系,但笔者不得不认为,这是一种普遍业界存在的“病态”!
如果是企业级的项目,尤其是跟金融相关的业务,SQL语句或许会非常复杂,并且关联着事物。但互联网项目却并非如此,在互联网项目中,看你牛不牛逼并不是取决于你能否写出一条复杂的SQL语句,而是看你能否将原本一条复杂的SQL语句拆散成单条SQL,一句一句的执行;并且脱离Hibernate等ORM产品后,能否使用传统的JDBC技术完成一条简单的CRUD操作,这才是牛逼!是的,你没有听错,互联网确确实实就是这么玩,还原最本质的东西,才是追求性能的不二选择。
1.1 DbUtils简介
如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC开发
,本案例我们讲采用apache commons组件
一个成员:Apache Commons DbUtils
。
Apache Commons DbUtils 工具是一个轻量级的持久层解决方案,是个小巧的JDBC轻量级封装的工具包,其最核心的特性是在JDBC的基础上做了一层封装,让开发人员能够以一种高级API的方式使用JDBC技术完成原本复杂的CRUD操作。而且其还对结果集的封装,可以直接将查询出来的结果集封装成JavaBean。DBUtils旨在简化JDBC代码混乱与重复,因此有助于抽取出重复代码,以便开发人员只专注于与数据库相关的操作。
1.2 入门应用程序
构建JDBC应用程序涉及以下六个步骤
- 导入包
- 需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用
import java.sql.*
就足够了。
- 需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用
- 注册JDBC驱动程序
- 需要初始化驱动程序,以便可以打开与数据库的通信通道。
- 打开连接
- 需要使用 DriverManager.getConnection() 方法创建一个Connection 对象,该对象表示与数据库的物理连接。
- 执行查询
- 需要使用类型为 Statement 的对象来构建和提交SQL语句到数据库。
- 从结果集中提取数据
- 要求您使用适当的 ResultSet.getXXX() 方法从结果集中检索数据。
- 清理环境
- 需要显式关闭所有数据库资源而不依赖于JVM的垃圾收集。
二、重要类详述
在正式开始之前,我们做下准备工作,首先创建一个使用的表,SQL如下:
create table users(
id int auto_increment primary key COMMENT '主键ID',
userCode varchar(40) null COMMENT '用户ID',
userName varchar(40) null COMMENT '用户姓名',
email varchar(60) null COMMENT '邮箱',
createTime date COMMENT '创建时间'
);
2.1 DbUtils
DbUtils提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,提供有用方法的类,它里面所有的方法都是静态的。这个类里的重要方法有:
方法 | 说明 |
---|---|
void close(…) | 关闭连接 |
void closeQuietly(…) | 关闭连接,并忽略异常 |
commitAndClose(Connection conn) | 在连接内提交SQL,然后关闭连接 |
commitAndCloseQuietly(Connection conn) | 在连接内提交SQL,然后关闭连接,并忽略异常 |
loadDriver(ClassLoader classLoader, String driverClassName) | |
loadDriver(String driverClassName) | 载入并注册JDBC驱动,如果成功就返回true,失败返回false。 使用该方法,无需捕捉ClassNotFoundException异常 |
printStackTrace(SQLException e) | |
printStackTrace(SQLException e, PrintWriter pw) | |
printWarnings(Connection conn) | |
printWarnings(Connection conn, PrintWriter pw) | |
rollback(Connection conn) | 回滚操作 |
rollbackAndClose(Connection conn) | |
rollbackAndCloseQuietly(Connection conn) |
对于DbUtils 有基本的了解后,就开始正式的使用吧!既然上面说过DbUtils 是操作数据库的,以mysql为例,那么让我们来看下是如何连接数据库的,见如下:
public class CommonsDbutilsTest {
// 数据库连接对象Connection
private Connection connection = null;
// DbUtils核心工具类对象
QueryRunner queryRunner;
// 数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/dbutils";
// 连接数据库用的驱动
private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
// 数据库用户名
private static final String USER = "test";
// 数据库密码
private static final String PASSWORD = "test";
@Before
public void before() throws SQLException {
// Step 1: 加载数据库驱动
DbUtils.loadDriver(JDBC_DRIVER);
// Step 2: 获取数据库连接对象
connection = DriverManager.getConnection(URL, USER, PASSWORD);
// Step 3: 创建DbUtils核心工具类对象
queryRunner = new QueryRunner();
}
/**
* 关闭Connection数据库连接对象
*/
@After
public void after() {
DbUtils.closeQuietly(connection);
}
}
接下来开始对数据库的基本操作吧!
@Test
public void insert() throws SQLException {
String sql = "insert into users(userCode,userName,email,createTime) values(?,?,?,?)";
Object[] params = {"dllwh", "独泪了无痕", "duleilewuhen@sina.com", new Date()};
queryRunner.update(connection, sql, params);
queryRunner.insert(connection, sql, new MapHandler(), params);
}
@Test
public void delete() throws SQLException {
String sql = "delete from users where id=?";
queryRunner.update(sql, 1);
}
@Test
public void update() throws SQLException {
String sql = "update users set name=? where id=?";
Object[] params = {"ddd", 5};
queryRunner.update(connection, sql, params);
}
@Test
public void testBatch() throws SQLException {
String sql = "insert into users(userCode,userName,email,createTime) values(?,?,?,?)";
Object[][] params = new Object[10][];
for (int i = 0; i < 10; i++) {
params[i] = new Object[]{"aa" + i, "123", "aa@sina.com", new Date()};
}
queryRunner.batch(connection, sql, params);
}
2.2 ResultSetHandler
ResultSetHandler
是DbUtils中的一个接口,该接口的实现类可用于将JDBC查询语句返回的结果(也就是ResultSet
),将数据转变并处理为任何一种形式。
ResultSetHandler接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。因此任何ResultSetHandler的执行需要一个结果集(ResultSet)作为参数传入,然后才能处理这个结果集,再返回一个对象。因为返回类型是java.lang.Object,所以除了不能返回一个原始的Java类型之外,其它的返回类型并没有什么限制。
由于ResultSetHandler接口中只有一个抽象方法,所以如果是Java 8版本的话也可以使用Lambda表达式来简化代码:
/**
* 此接口的实现将 ResultSet 转换为其他对象
* T: 目标类型(类型参数),也就是 ResultSet 转换为的对象的类型
*/
public interface ResultSetHandler<T> {
/**
* 将 ResultSet 转换为一个对象
*
* @param rs 要转换的 ResultSet
*
* @return 如果 ResultSet 包含0行,那么实现返回 null 也是合法的
* @throws 数据库访问出错将会抛出 SQLException 异常
*/
T handle(ResultSet rs) throws SQLException;
}
DbUtils提供了一些常用的ResultSetHandler
实现类,可以简化查询,一般情况下不需要像上面那样自己来实现ResultSetHandler
接口。ResultSetHandler 接口的实现类:
2.2.1 ArrayHandler
ArrayHandler会返回一个数组,用于将结果集第一行
数据转换为数组。
@Test
public void arrayHandler() throws SQLException {
String sql = "select * from users";
Object[] result = queryRunner.query(connection, sql, new ArrayHandler());
System.out.println(Arrays.asList(result));
}
2.2.2 ArrayListHandler
ArrayListHandler会返回一个集合,把结果集中的每一行
数据都转成一个对象数组,再存放到List中。
public void arrayListHandler() throws SQLException {
String sql = "select * from users";
List<Object[]> resultList = queryRunner.query(connection, sql, new ArrayListHandler());
resultList.forEach(object -> System.out.println(Arrays.asList(object)));
}
2.2.3 BeanHandler
BeanHandler实现了将结果集第一行
数据转换为Bean对象,即数据库查询的结果只有一条记录,在实际应用中非常方便。
@Test
public void beanHandler() throws SQLException {
String sql = "SELECT userName FROM users where id = ?";
Object[] params = {1};
Person person = queryRunner.query(connection, sql, new BeanHandler<Person>(Person.class), params);
System.out.println(person);
}
2.2.4 BeanListHandler
使用BeanListHandler查询对象,将结果集中的每一行
数据都封装到一个对应的JavaBean实例中,然后将JavaBean添加到List中。
@Test
public void beanListHandler() throws SQLException {
QueryRunner queryRunner = new QueryRunner();
String sql = "SELECT userName FROM users";
List<Person> resultList = (List<Person>) queryRunner.query(connection, sql, new BeanListHandler(Person.class));
resultList.forEach(person -> System.out.println(person.getUserName()));
}
2.2.5 BeanMapHandler
BeanMapHandler将结果集中的每一行
数据都封装到一个JavaBean里,再把这些JavaBean再存到一个Map里,Map中每条数据对应查询结果的一条数据,key为数据的主键或者唯一索引,value是数据通过反射机制转成的Java对象。
@Test
public void beanMapHandler() throws SQLException {
String sql = "select * from users";
// 使用userCode列作为Map的key
Map<String, Person> result = queryRunner.query(connection, sql, new BeanMapHandler<String, Person>(Person.class, "userCode"));
System.out.println(result);
}
2.2.6 ColumnListHandler
ColumnListHandler会返回一个集合,集合中的数据为结果集中指定列的数据,缺省值为第一列。
@Test
public void columnListHandler() throws SQLException {
String sql = "select * from users";
List resultList = (List) queryRunner.query(connection, sql, new ColumnListHandler());
resultList.forEach(column -> System.out.println(column));
List resultList2 = (List) queryRunner.query(connection, sql, new ColumnListHandler("userCode"));
resultList2.forEach(column -> System.out.println(column));
}
2.2.7 KeyedHandler
KeyedHandler将结果集中的每一行
数据都封装到一个MapA(key是行号,value是行数据)里,再把这些MapA再存到一个MapB(key为指定的列,value是对应里的行值即单值)里
@Test
public void keyedHandler() throws SQLException {
String sql = "select * from users";
// Key指定为id列
Map<Integer, Map> resultMap = (Map) queryRunner.query(connection, sql, new KeyedHandler("id"));
for (Map.Entry<Integer, Map> me : resultMap.entrySet()) {
int id = me.getKey();
Map<String, Object> innermap = me.getValue();
for (Map.Entry<String, Object> innerme : innermap.entrySet()) {
String columnName = innerme.getKey();
Object value = innerme.getValue();
System.out.println(columnName + "=" + value);
}
System.out.println("----------------");
}
}
2.2.8 MapHandler
MapHandler会将结果集中的第一行
数据封装到一个Map里,key是列名,value就是对应的值。
@Test
public void mapHandler() throws SQLException {
String sql = "select * from users";
Map<String, Object> resultMap = (Map) queryRunner.query(connection, sql, new MapHandler());
for (Map.Entry<String, Object> me : resultMap.entrySet()) {
System.out.println(me.getKey() + "=" + me.getValue());
}
}
2.2.9 MapListHandler
MapListHandler会将结果集中的每一行
数据都封装到一个Map里,然后再存放到List。集合中的数据为对应行转换的键值对,键为列名。
@Test
public void mapListHandler() throws SQLException {
QueryRunner queryRunner = new QueryRunner();
String sql = "SELECT * FROM users";
List<Map> resultList = (List) queryRunner.query(connection, sql, new MapListHandler());
resultList.forEach(map -> System.out.println(map.get("userName")));
}
2.2.10 ScalarHandler
ScalarHandler会返回一个对象,用于读取结果集中第一行指定列的数据或返回一个统计函数的值,比如count(*)。该实现类会自动推导数据库中数据的类型,注意类型的转换
@Test
public void scalarHandler() throws SQLException {
String sql = "select count(*) from users";
System.out.println(queryRunner.query(connection, sql, new ScalarHandler(1)));
System.out.println(queryRunner.query(connection, sql, new ScalarHandler<Long>()));
}
2.3 QueryRunner
org.apache.commons.dbutils.QueryRunner 类是DBUtils库中的中心类。它使用可插入策略执行SQL查询以处理ResultSet,而且这个类是线程安全的。
这个类使执行SQL查询简单化了,它与ResultSetHandler串联在一起有效地履行着一些平常的任务,它能够大大减少你所要写的编码。QueryRunner类提供了两个构造器:其中一个是一个空构造器,另一个则拿一个javax.sql.DataSource来作为参数。因此,在你不用为一个方法提供一个数据库连接来作为参数的情况下,提供给构造器的数据源(DataSource)被用来获得一个新的连接并将继续进行下去。
该类有多种构造方法,但是常用的大致可分为两种,如下所见:
方法 | 说明 |
---|---|
QueryRunner() | 创建核心类,没有提供数据源,在进行具体操作时,需要手动提供Connection |
QueryRunner(DataSource ds) | 创建核心类,并提供数据源,内部自己维护Connection |
该类中的还有几个比较常用的方法,由于其构造方法的不同,其或多或少的存在着不同,以无参构造 new QueryRunner()
为例说明,见如下:
方法 | 说明 |
---|---|
batch(Connection conn,String sql,Object[][] params) | 批量执行INSERT、UPDATE或DELETE |
execute(Connection conn,String sql,Object… params) | |
execute(Connection conn,String sql,ResultSetHandler<T> rsh,Object… params) | |
insert(Connection conn,String sql,ResultSetHandler<T> rsh) | 执行一个插入查询语句 |
insert(Connection conn,String sql,ResultSetHandler<T> rsh,Object… params) | 执行一个插入查询语句 |
insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[][] params) | 批量执行插入语句 |
query(Connection conn,String sql,ResultSetHandler<T> rsh) | 执行一个查询操作,并将查询结果封装到对象中。 此方法会自行处理PreparedStatement和ResultSet的创建和关闭 |
query(Connection conn,String sql,ResultSetHandler<T> rsh,Object… params) | |
update(Connection conn,String sql) | 用来执行一个更新(插入、更新或删除)操作 |
update(Connection conn,String sql,Object param) | 用来执行一个更新(插入、更新或删除)操作 |
update(Connection conn,String sql,Object… params) | 用来执行一个更新(插入、更新或删除)操作 |
2.4 AsyncQueryRunner
org.apache.commons.dbutils.AsyncQueryRunner 类有助于执行具有异步支持的长时间运行的SQL查询。 这个类是线程安全的。 该类支持与QueryRunner相同的方法,但它返回Callable对象,在之后可以使用它来检索结果。