还原最本质的JDBC轻量级封装的工具包

在这里插入图片描述

一、概述

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应用程序涉及以下六个步骤

  1. 导入包
    • 需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用 import java.sql.* 就足够了。
  2. 注册JDBC驱动程序
    • 需要初始化驱动程序,以便可以打开与数据库的通信通道。
  3. 打开连接
    • 需要使用 DriverManager.getConnection() 方法创建一个Connection 对象,该对象表示与数据库的物理连接。
  4. 执行查询
    • 需要使用类型为 Statement 的对象来构建和提交SQL语句到数据库。
  5. 从结果集中提取数据
    • 要求您使用适当的 ResultSet.getXXX() 方法从结果集中检索数据。
  6. 清理环境
    • 需要显式关闭所有数据库资源而不依赖于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对象,在之后可以使用它来检索结果。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
一、SBORM 介绍 1、目前只考虑支持 mysql; 2、基于spring jdbc的上层封装,底层jdbc操作基于JdbcTemplate,对于使用spring jdbc的人会有一点价值,比较简洁的封装可以节省很多重复劳动,具体节省多少可以看看example; 3、实现一套简单的ORM(直接使用spring rowmapper,insert自己实现),可以基于对象进行crud和相对复杂(感觉比hibernate强大一点)的sql操作; 4、基于对象指定查询的字段,大部分时候可以忘掉表结构进行业务开发; 5、支持简单的数据库路由,读写分离(半自动,需要指定取writer还是reader,默认规则reader采用随机的方式,当然也可以手动指定); 6、支持简单的分表,主要是针对一定规则的分表,比如百分表、千分表,也可以自己指定分表后缀; 7、简单的单表查询(比如所有条件是and或者or结构),基本实现0sql代码编写(类似HibernateTemplate selectByExample、findByCriteria、find等方法); 8、简单的单表排序支持,支持多个排序条件组合; 9、对于复杂的sql查询,提供获取jdbctemplate实例进行操作,类似spring jdbc的常规用法; 10、提供Entity代码生成接口,Entity并非简单的pojo(尽可能不要去修改此类),引入字段常量类,方便查询的时候指定选择字段,从而更好实现查询条件的封装; 二、为什么写SBORM? 1、hibernate:过于臃肿,使用不够灵活,优化难(其实主要是因为很少用),HQL感觉就是个渣,在 mysql几乎一统天下的背景下,跨数据库级别的兼容吃力不讨好。Hibernate的对象化关联处理确实挺强大,但是使用起来坑太多,有多少人敢在项目 中大范围使用真不知道,屠龙刀不是人人都提的起啊。 2、mybatis:轻量级,基于xml的模式感觉不利于封装,代码量不小,基于xml维护也麻烦(个人观点, 现在注解模式貌似也挺不错),感觉mybatis更适合存在dba角色的年代,可以远离代码进行sql调优,复杂的查询拼装起来也更加优雅(java基本 就是if else ...),但是对于查询业务简单但是数据库集群环境的场景有点憋屈(其实对mybatis使用也不多,瞎评论^_^)。 3、spring jdbc:小巧,灵活,足够优秀,个人比较喜欢使用,但是代码量偏大,原生的接口重复劳动量大,比如insert、mapper之类的; SBORM只是针对spring jdbc的一些不方便的地方,做了一些封装,更加简化日常的开发工作,基于spring jdbc的RowMapper自动实现对象映射,也勉强算的上叫ORM,只是大部分功能已经由spring jdbc实现了。 平时不太喜欢使用hibernate和mybatis,主要是使用spring jdbc,写这个东西的出发点主要是平时使用spring jdbc觉 得比较麻烦,重复性的代码偏多,一方面通过自动mapper降低返回结果处理工作量,另一方面参考hibernate对象化查询条件的模式,写了一个 QueryBudiler,使得更多简单的单表查询可以通过对象组织查询、更改逻辑,避免过多去写相似性的SQL语句,减少DAO接口量。 三、一些亮点 1、Entity的设计:很多人看了也许会说,这个不是POJO,不是纯粹的Java Bean,显得很另类。但是有多人在开发过程中(特别是在写sql的时候),经常要去看看表结构设计?还有多少次因为改了表某个字段,还得遍历去查找哪些 sql使用了这个字段?多少次看到在代码中直接传入字段名作为查询参数感到别扭?如果将表结构字段都用java对象去描述,能够解决这些问题,就不必要在 乎是不是POJO了,后面看example的时候应该能体会这么做的一些好处,至少我觉得是挺方便的,将大部分查询脱离表结构设计。 2、简单的数据库路由:如果分库结构不是太复杂(比如简单的读写分离、或者多个库集成),BaseDao可以自 动进行路由(比如读写分离,根据业务模式指定读、写库),如果非默认的路由规则,也可以通过手动设置的模式,进行数据库路由。数据库路由直接由 Entity指定,所有的路由都是根据Entity识别,也就是说查询也是围绕Entity展开的,避免类似使用spring jdbc的时候,各种 template实例跳来跳去,硬编码引入,写一个业务还得看看到底该用哪个template,尤其是多个数据库共用一个template实例的时候。 3、QueryBuilder:单表查询基本上都可以实现零Sql(除非查询条件特别复杂的),更新、删除等操作也可以通过QueryBuilder进行批量处理,不局限于根据主键来处理。 4、分表操作的支持:对于分表操作和常规的使用没有区别,只是指定分表规则,mybatis好像也可以通过制定参数实现分表处理,没搞清楚hibernate对这个是怎么处理的(hibernate好像是bean和表一对一绑定的)? 标签:sborm

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独泪了无痕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值