前言
关于Apache的DbUtils中间件或许了解的人并不多,大部分开发人员在生成环境中更 多的是依靠Hibernate、Ibatis、Spring JDBC、JPA等大厂提供的持久层技术解决方案,或者是企业内部自己研发的持久层技术。但无论如何,使用这些技术的初衷和本质都是为了能够减少企业开发 成本,提高生产效率,降低耦合。
放眼企业级项目,Hibernate等ORM产品是首选,而互联网领域,大部分开发人员往往并不会在生产环境中上这些ORM技术,原因很简单,要的就是效率,其次都不重要。对于刚接触SQL和JDBC的开发人员,最引以为傲的就是希望能够在日后编写复杂的SQL语句,以及会使用诸如Hibernate、Ibatis等第三方持久层技术,并且极力的撇清与传统JDBC技术的关系,但笔者不得不认为,这是一种普遍业界存在的“病态”!
如果是企业级的项目,尤其是跟金融相关的业务,SQL语句或许会非常复杂,并且关联着事物。 但互联网项目却并非如此,在互联网项目中,看你牛不牛逼并不是取决于你能否写出一条复杂的SQL语句,而是看你能否将原本一条复杂的SQL语句拆散成单条 SQL,一句一句的执行;并且脱离Hibernate等ORM产品后,能否使用传统的JDBC技术完成一条简单的CRUD操作,这才是牛逼!是的,你没有 听错,互联网确确实实就是这么玩,还原最本质的东西,才是追求性能的不二选择。
笔者本章不会提及垂直分库、水平分区等数据库概念,以及数据路由中间件等技术(请阅读笔者博 文《剖析淘宝TDDL—Matrix层分库分表实现》),因为这些内容与本章内容无关,但间接来看,笔者之前提及的单条SQL、使用JDBC完成基本的 CRUD操作就可以在最大程度上满足一个互联网场景的持久层操作。以Hibernate为例,简单来说需要经历HQL->SQL->DBMS 等编译过程,中间还冗余着缓存、对象等开销,希望大家记住,封装层次越高,性能越低!这个是无可争议的事实。笔者希望大家接下来,暂时“忘记”掉你所会的持久层技术,耐心的听笔者为你介绍Apache的DbUtils技术,或许你会有意想不到的收获。
目录
一、Apache Commons DbUtils简介;
二、下载与安装DbUtils;
三、使用DbUtils完成CRUD操作;
四、C3P0连接池集成DbUtils;
五、常用包、类讲解;
六、自动封装结果集;
七、事物管理;
一、Apache Commons DbUtils简介;
Apache的DbUtils工具是一个轻量级的持久层解决方案,天生为性能而生,它简单的对JDBC进行了必要的操作封装,让开发人员能够以一种高级API的方式使用JDBC技术完成原本复杂的CRUD操作。换句话说,DbUtils天生就不是一个复杂的技术,它只是一个简单的JDBC上层封装, 对开发人员而言,大概只需半小时就能够完全掌握DbUtils技术的使用,是的,它就是这么简单与方便,它是互联网项目的宠儿,选择DbUtils技术作 为持久层的解决方案,或许能够让你从原本复杂的Hibernate操作中解脱出来,或者是你觉得Ibatis不够好用,DbUtils也是你选择的理由之 一。总之,使用它,你将会感到惊艳,它是如此的简单和干净,如此的纯粹和高效!并且DbUtils是采用商业友好的开源协议,大家甚至可以下载它的源码, 进行二次开发,以此满足企业自身的需要。
二、下载与安装DbUtils;
当大家对DbUtils的项目背景有所了解后,接下来本节内容笔者将会告诉你它的下载和安装。大家可以登录http://commons.apache.org/站点下载DbUtils工具的最新版本,笔者使用的版本为1.6.0,在此大家需要注意,为了避免在开发过程中出现异常,建议大家下载、使用与笔者本篇博文一致的版本。
当大家成功下载好DbUtils相关的构件后,我们可以将其添加到项目中的 ClassPath目录下,当然笔者后续小节会提及DbUtils与C3P0连接池的集成,因此,大家最好将C3P0所需的构件以及数据库驱动(笔者使用 Mysql)一起添加到项目中。使用DbUtils时关联的构件,如下所示:
3、使用DbUtils完成CRUD操作;
废话不多说,使用DbUtils操作数据库之前,首先要做的事情就是获取Connection。那么为了方便,笔者使用硬编码的方式将数据源的配置信息coding在代码中(生产环境中,有可能是配置在项目的配置文件中、数据库中、Diamond中等),如下所示:
- /**
- * 数据源信息
- *
- * @author gaoxianglong
- */
- public class ConnectionManager {
- public static Connection getConnection() {
- Connection conn = null;
- try {
- Class.forName("com.mysql.jdbc.Driver");
- conn = DriverManager.getConnection(
- "jdbc:mysql://ip:port/dbName", "userName",
- "passWord");
- } catch (Exception e) {
- e.printStackTrace();
- }
- return conn;
- }
- }
当编写好ConnectionManager之后,接下来要做的事情就是获取 Connection,然后就能够使用DbUtils进行CRUD操作了。或许细心的读者已经发现,使用DbUtils其实是非常简单的,需要会的不多, 仅仅只需要掌握JDBC操作以及简单的CRUD操作即可(互联网场景下同样也是这么要求)。
- @Test
- public void testInsert() {
- final String SQL = "insert into test_1 values(?, ?)";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection2();
- int result = new QueryRunner().update(conn, SQL, new Object[] {
- "JohnGao1", "123" });
- if (0 < result)
- System.out.println("数据插入成功...");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
- @Test
- public void testUpdate() {
- final String SQL = "update test_1 set password= ? where username = ?";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection();
- int result = new QueryRunner().update(conn, SQL, new Object[] {
- "321", "JohnGao1" });
- if (0 < result)
- System.out.println("数据更新成功...");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
- @Test
- public void testDelete() {
- final String SQL = "delete from test_1 where username like ?";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection();
- int result = new QueryRunner().update(conn, SQL, "%JohnGao%");
- if (0 < result)
- System.out.println("数据删除成功...");
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
- @Test
- public void testQuery() {
- final String SQL = "select * from test_1";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection();
- Test_1Bean test1Bean = new QueryRunner().query(conn, SQL,
- new BeanHandler(Test_1Bean.class));
- if (null != test1Bean) {
- System.out.println(test1Bean.getUsername());
- System.out.println(test1Bean.getPassword());
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
四、C3P0连接池集成DbUtils;
在生产环境中,开发人员在对数据库进行CRUD操作的时候,由于数据库的链接是有限的,因此不得不使用连接池来实现资源复用,以此降低数据库的性能瓶颈(尽管这么说有些不太友好,因为并发环境下,单靠连接池是不能够解决问题的,而常用的方案更多是诸如Redis之类的内存数据库抗住70%传统DBMS数据的受访压力、数据库先做垂直分库,再做水平分区,当然Master/Sleave是必不可少的,经过这些步骤之后,才能够说基本上解决了理论上可能出现的数据库在高并发环境下的瓶颈)。
废话不多说,在之前的ConnectionManager中添加进连接池相关的代码,当然为了方便,笔者同样还是使用硬编码的方式,如下所示:
- public static ComboPooledDataSource dataSource;
- static {
- try {
- dataSource = new ComboPooledDataSource();
- dataSource.setUser("userName");
- dataSource.setPassword("passWord");
- dataSource.setJdbcUrl("jdbc:mysql://ip:port/dbName");
- dataSource.setDriverClass("com.mysql.jdbc.Driver");
- dataSource.setInitialPoolSize(10);
- dataSource.setMinPoolSize(5);
- dataSource.setMaxPoolSize(50);
- dataSource.setMaxStatements(100);
- dataSource.setMaxIdleTime(60);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 从连接池中获取数据源链接
- *
- * @author gaoxianglong
- *
- * @return Connection 数据源链接
- */
- public static Connection getConnection2() {
- Connection conn = null;
- if (null != dataSource) {
- try {
- conn = dataSource.getConnection();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- return conn;
- }
当成功在ConnectionManager中添加好所需的C3P0连接池配置后,接下来要做的事情就是考虑如何使用C3P0与DbUtils之间的集成。其实最简单的做法就 是直接将之前获取Connection的getConnection()方法更换为上述代码中的getConnection2()即可,同样可以使用在创 建QueryRunner实例时,将数据源的DataSource传递过去,这样即可避免在执行CRUD操作时,还需要在方法中指明 Connection。
当然究竟应该怎么做,完全取决于你自己,如果希望方便,那么笔者建议你在创建QueryRunner实例时,直接将C3P0的DataSource传递过去,但这样做的弊端很明显,如果在特殊的场景下,需要手动控制事物时,那么这种操作是极其不便的,因为Connection并不可控。那么为了解决事物控制的问题,当然是Connection可控最好。
五、常用包、类讲解;
相信大家已经从上述DbUtils的CRUD示例中发现了QueryRunner的身影,那么笔者接下来就将会针对DbUtils中诸如QueryRunner等常用类型进行深入讲解。
在DbUtils中,最常用的3个包为org.apache.commons.dbutils、org.apache.commons.dbutils.handlers以及org.apache.commons.dbutils.wrappers。
org.apache.commons.dbutils包下的常用类,如下所示:
1、DbUtils : 提供如关闭连接、装载 JDBC 驱动程序等常规工作的工具类;
2、QueryRunner : 该类简单化了 SQL 查询,它常与与 ResultSetHandler 组合在一起使用;
org.apache.commons.dbutils.handlers包下的常用类,如下所示:
1、ArrayHandler :将ResultSet中第一行的数据转化成对象数组;
2、ArrayListHandler:将ResultSet中所有的数据转化成List,List中存放的是Object[];
3、BeanHandler :将ResultSet中第一行的数据转化成类对象;
4、BeanListHandler :将ResultSet中所有的数据转化成List,List中存放的是类对象;
5、ColumnListHandler :将ResultSet中某一列的数据存成List,List中存放的是Object对象;
6、KeyedHandler :将ResultSet中存成映射,key为某一列对应为Map。Map中存放的是数据;
7、MapHandler :将ResultSet中第一行的数据存成Map映射;
8、MapListHandler :将ResultSet中所有的数据存成List。List中存放的是Map;
9、ScalarHandler :将ResultSet中一条记录的其中某一列的数据存成Object;
org.apache.commons.dbutils.wrappers包下的常用类,如下所示:
1、SqlNullCheckedResultSet :该类是用来对sql语句执行完成之后的的数值进行null的替换;
2、StringTrimmedResultSet :去除ResultSet中中字段的左右空格;
六、自动封装结果集;
在org.apache.commons.dbutils.handlers包下的类型,大部分都是与查询结果集相关的。试想一下,利用传统的JDBC进行查询时,返回的数据我们需要对ResultSet进行迭代,这是相当麻烦的,且不利于维护,因为我们需要手动编写与之相关的数据封装。但是使用DbUtils之后,我们要做的事情仅仅只是告诉DbUtils我们需要什么样的数据即可,关于数据封装这种通用的控制逻辑,则无需开发人员参与,这极大的节省了开发人员的时间,提升了生产效率。
简单来说,笔者在开发过程中使用最广泛的就是BeanListHandler以及
MapListHandler
封装的结果集。简单来说,BeanListHandler将会查询后的数据封装到一个对应的POJO中(可以看做是一个无状态的实体
Bean),MapListHandler
会将查询后的数据封装为一个List,List中存储的就是一个个的Map集合,通过key-value的方式获取封装后的数据集。先来看看
MapListHandler 的使用,如下所示:
- @Test
- public void testQuery4() {
- final String SQL = "select * from test_1 where username like ?";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection2();
- List<Map<String, Object>> values = new QueryRunner().query(conn,
- SQL, new Object[] { "%JohnGao%" }, new MapListHandler());
- if (null != values) {
- for (int i = 0; i < values.size(); i++) {
- Map<String, Object> map = values.get(i);
- System.out.println(map.get("username"));
- System.out.println(map.get("password"));
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
如果你喜欢类似于实体Bean的操作方式,那么BeanListHandler无疑使最好的
选择。一旦我们使用BeanListHandler作为数据返回后的结果集封装,那么DbUtils便会将查询后的结果集一个字段一个字段的映射到指定的
POJO中,当然前提就是字段名称是必须一致的,否则DbUtils将无法完成数据封装。BeanListHandler的使用示例,如下所示:
- @Test
- public void testQuery3() {
- final String SQL = "select * from test_1 where username like ?";
- try {
- if (null == conn || conn.isClosed())
- conn = ConnectionManager.getConnection();
- List<Test_1Bean> test1Beans = new QueryRunner().query(conn, SQL,
- new Object[] { "%JohnGao%" }, new BeanListHandler(
- Test_1Bean.class));
- if (null != test1Beans) {
- for (Test_1Bean test1Bean : test1Beans) {
- System.out.println(test1Bean.getUsername());
- System.out.println(test1Bean.getPassword());
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- close(conn);
- }
- }
在此大家需要注意,为了方便演示,笔者在此并没有提供对应的POJO。如果有需要,大家可以编写一个与数据库表字段相同的POJO来完成查询结果集的字段映射封装操作。
七、事物管理;
说起事物管理,这其实是一个非常复杂与繁琐,且是最容易出错的场景,尤其是在手动管理事物操
作上。当然本节所提及的事物管理仍然是建立在基于手动管理的事物操作上。对于JDBC操作,如果希望事物不要手动提交,那么在获取Connection的
时候,一定需要将设置conn.setAutoCommit(false);这样一来事物就不会自动进行提交,当我们手动执行conn.commit()
方法的时候,事物才会进行提交。这种方式对于DbUtils其实是一样的,之前也说过,DbUtils仅仅只是对JDBC做了一个轻量级的上层封装,那么
必然可以和JDBC进行混用,一旦我们在程序中设定了事物后,接下来的事物管理操作就依赖与开发人员自身了,DbUtils将不会再参与事物的管理。
对于大多数开发人员而言,事物控制的不好,将会导致业务出现问题,脏数据等情况是非常常见的,但从另一个层面来说,手动的事物管理其实是最灵活和方便的。在此需要提醒大家,如果是使用Mysql数据库,只有将数据库引擎设置为InnoDB后,才会支持事物!
最后笔者在啰嗦一下,使用完资源后,我们一定要记得及时释放掉资源,以此避免无用资源长时间
挂起。那么在DbUtils中,你将有2种方式结束掉Connection,第一个是使用DbUtils.close()方法。其次,你将可以直接使用
close()方法关闭Connection的链接。