Java操作数据库(二)——结果集映射,DbUtils的快速上手

2,272 阅读16分钟

一、前情提要

在第一篇文章 Java操作数据库(一)——JDBC的快速入门 里面,我们介绍了JDBC的快速上手。

因为只是快速入门,所以有些东西没有介绍,比如:JDBC如果使用连接池JDBC如何开启事务等等。

因此,这篇文章会先进行上述信息的补充。


由于原生JDBC开发起来非常麻烦,select获取的结果集还要逐列获值。

因此,DbUtils就出现啦,它可以自动帮我们把结果封装成对象,使用起来非常方便。


二、JDBC的补充知识

2.1 连接池(DataSource)

连接池,就是用于管理JDBC创建的Connection(连接)的一个容器,大家可以类比成我们Java中的线程池。

  • 作用:可以减少连接的重复创建和销毁,尽可能地提高程序执行的效率。
  • 原理(简):连接池会提前创建一定数量的连接,当调用连接的close方法,这个连接会回到连接池而不是销毁


连接池有许多种类:比如DBCPC3P0DruidHikari等等。

我们这里就选择使用阿里巴巴Druid(德鲁伊)

首先,我们需要引入Druid(推荐使用Maven[Maven和jar包二选一即可]

  • jar包下载地址(提取码:8b5l)

  • Maven依赖:
     <dependency>
         <groupId>com.alibaba</groupId>
         <artifactId>druid</artifactId>
         <version>1.1.22</version>
     </dependency>
    

接下来,我们使用连接池来修改上篇文章中的JDBC工具类

前文的配置我们是直接写死在Java代码中,但是在一般的开发中,这些配置信息不应该放在Java代码中,而是放在一个专门存储配置信息的文件中(比如.properties.yml)等等。

配置文件放置位置(可自行调整):

  • 非Maven项目:src -> config包,并创建一个druid.properties文件。
  • Maven项目:直接在resources中创建druid.properties文件。

druid.properties

# =============================数据库相关配置(自行调整)=====================================
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/travel?useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
username=root
password=123456


#==============================Druid连接池相关配置(自行调整,还有其他配置可以设置)=================================
# 初始连接数量
initialSize=10
# 最大连接数量
maxActive=20
# 获取连接最大等待时间
maxWait=3000


JDBCUtil.java

哈哈,这个工具类是不是有点简陋了……(哈哈因为close等等琐事DbUtils帮我们做了OwO)

最后还会封装有个BaseDao喔,那个才是重头戏(●'◡'●)~

public class JDBCUtil {
    /**
     * 连接池
     */

    static DataSource dataSource = null;

    /**
     * 静态代码块,用来初始化连接池
     */

    static {
        try {
            //1.通过类加载器将properties文件变成输入流
            InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties properties = new Properties();
            // 加载输入流
            properties.load(is);
            //2.创建指定参数的数据库连接池
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前连接池
     * @return
     */

    public static DataSource getDataSource() {
        return dataSource;
    }

    /**
     * 获取Connection
     * @return Connection
     */

    public static Connection getConnection(){
        Connection connection = null;
        try {
            // 建立Connection
            connection = dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }


    /**
     * 获取Connection,并开启事务
     * @return Connection
     */

    public static Connection getConnectionWithTransaction(){
        Connection connection = null;
        try {
            // 建立Connection
            connection = dataSource.getConnection();
            // 关闭自动提交,即开启事务。(!!!需要自己写完业务相关的代码,手动提交)
            connection.setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
}

2.2 JDBC开启事务

(上面的工具类有相应的方法)

  1. 获取connection

  2. 关闭自动提交,即开启事务

    java connection.setAutoCommit(false);

  3. 执行流程

    java try{ // 业务操作

    ¨K46K

    }catch{ // 回滚 connection.rollback(); }finally{ // 关闭连接 connection.close(); }


三、DbUtils的快速入门

终于轮到我们的主角DbUtils登场啦(ง •_•)ง!

DbUtils是属于Apache旗下的开源项目,官网:DbUtils



3.1 简介

我们先来简单看下,DbUtils是干什么的。

这图大家简单看下就好,我下面会总结一下(噗,就是为了证明我是从官网找的233)

简而言之:DbUtils可以高效地使用JDBC进行开发,让你更加专注于查询和更新数据(可以自动完成实体类等的映射)。

特点:

  • Small—— DbUtils内容不多,上手非常快,主要有两个核心:QueryRunnerResultSetHandler
  • Transparent—— 你只需要负责操作(写sql)即可,DbUtils会执行sql返回结果并关闭资源
  • Fast—— 引入DbUtils即可使用,不需要额外操作。

3.2 `QueryRunner`和`ResultSetHandler`

QueryRunnerResultSetHandlerDbUtils的两个核心。


3.2.1 QueryRunner介绍

QueryRunner的作用主要是:处理sql语句,进行增删改查等。

常用构造器:

// Constructor for QueryRunner that takes a DataSource to use.
// 传入一个连接池给QR使用
QueryRunner(DataSource ds)

常用CRUD的API我会在章节4进行演示。


3.2.2 ResultSetHandler介绍

`ResultSetHandler的作用主要是:完成结果映射(使用反射),比如我们查出的结果可以直接是一个User实体类。

常用映射 描述
BeanHandler 结果集 -> 实体类 (select * from user where id = 1 等等)
BeanListHandler 结果集 -> 实体集合 (select * from user 等等)
ScalarHandler 结果集 -> 单个数据(比如select count(*) from user 等等)

常用构造器:

// Creates a new instance of BeanHandler.
// 这是BeanHandler的构造器,传入一个想要转换的实体类的Class类型,因为底层还是使用反射
// 其他类似
BeanHandler(Class<? extends T> type)


3.3 上手小案例

3.3.1 建立 user 表

大家简单建立个user表即可。

create database if not exists jdbc_demo

use jdbc_demo

create table user
(
    id bigint auto_increment,
    username varchar(64not null comment '帐号',
    password varchar(64not null comment '密码',
    constraint user_pk
        primary key (id)
)
comment '用户表';

INSERT INTO jdbc_demo.user (username, passwordVALUES ('user1''123');
INSERT INTO jdbc_demo.user (username, passwordVALUES ('user2''456');
INSERT INTO jdbc_demo.user (username, passwordVALUES ('user3''123456');


3.3.2 导入相关jar包


3.3.3 操作流程

这里分了 6 点,主要是为了大家能够方便理解(●'◡'●),在章节4我会压缩下流程。

// 1.获取连接池
DataSource dataSource = JDBCUtil.getDataSource();

// 2.建立QueryRunner,将连接池当成参数传进构造器中
QueryRunner queryRunner = new QueryRunner(dataSource);

// 3.创建相关的ResultSetHandle,形成结果映射
BeanHandler<User> userBeanHandler = new BeanHandler<>(User.class);

// 4.编写sql语句
String sql = "select * from jdbc_demo.user where id = ?";

// 5.执行qr的query方法
// 参数1:sql语句
// 参数2:ResultSetHandler
// 参数3:类似preparedStatement,完成对sql语句占位符的替换
// 这个query底层就是使用preparedStatement
User user = queryRunner.query(sql, userBeanHandler, 1);

// 6.查看结果
System.out.println(user);
// Perfect (●'◡'●)
// User{id=1, username='user1', password='123'}

哈哈,通过这个小案例,我们成功获取了User实体类的相关信息。

是不是感觉很方便→ v →,别着急,更精彩的还在后面(●'◡'●)!

4. 常用CRUD的API介绍

4.1 查找单个数据

使用ScalarHandler

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
Long query = queryRunner.query("select count(*) from jdbc_demo.user"new ScalarHandler<>());

// 3.输入
System.out.println(query);
// 得到 3 


4.2 查找单条数据

使用BeanHandler

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
User user = queryRunner.query(
    "select * from jdbc_demo.user where username = ?",
    new BeanHandler<>(User.class),
    "user2");

// 3.输入
System.out.println(user);
// 得到 User{id=2, username='user2', password='456'}


4.3 查找多条数据

使用BeanListHandler

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 查询,
// Tip:先写handler,再写sql语句,这样有提示
// 也可以使用where,这边只是举个例子
List<User> users = queryRunner.query(
        "select * from jdbc_demo.user",
        new BeanListHandler<>(User.class));

// 3.输出
System.out.println(users);
// 得到 
// [User{id=1, username='user1', password='123'}, 
// User{id=2, username='user2', password='456'}, 
// User{id=3, username='user3', password='123456'}]


4.4 增加

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 也可以使用where,这边只是举个例子
// 增删改 都是 update
int res = queryRunner.update("insert into jdbc_demo.user values (null,'user4','user4')");

// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)

我们可以看到数据库确实有新的数据。


4.5 更新

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 增删改 都是 update
int res = queryRunner.update(
    "update jdbc_demo.user set password = '123456' where username = ?",
    "user4");

// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)

可以看到,数据库的数据已经被修改了。


4.6 删除

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 操作,
// Tip:先写handler,再写sql语句,这样有提示
// 增删改 都是 update
int res = queryRunner.update(
        "delete from jdbc_demo.user where username = ? ",
        "user4");

// 3.输出
System.out.println(res);
// 得到 1 说明成功(●'◡'●)

可以看到,数据库中的user4已经被删除啦OvO


4.7 批量操作

这里我们介绍下批量插入, 删除和更新也是类似的。(记住数据库的url 要写上 rewriteBatchedStatements=true)

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());

// 2. 操作
// 使用batch方法,
// 参数1:批量操作的sql
// 参数2:二位数组,行数 -> 执行次数 ,列 -> 替换占位符
Object[][] params = new Object[10][];
for (int i = 0; i < params.length; i++) {
    params[i] = new Object[]{"lemonfish" + i, i};
}

// batch代表每行语句执行的结果
int[] batch = queryRunner.batch(
        "insert into jdbc_demo.user values (null,?,?)",
        params
);

// 3.输出
System.out.println(Arrays.toString(batch));
// 得到 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 说明全部成功(●'◡'●)

可以看到,10条数据被成功插入啦


4.8 事务操作

事务操作和之前的有一些区别,QueryRunner操作事务,需要用到connection(其实还是原生的JDBC),这时候用我们自己封装的工具类获取connection就行。

// 1.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();

// 2. 获取开启事务的connection
Connection connection = JDBCUtil.getConnectionWithTransaction();

// 3. 业务
// 3.1 删除、添加
try {
    int delete = queryRunner.update(connection,
                                    "delete from jdbc_demo.user where username = ? ",
                                    "user3");
    int insert = queryRunner.update(connection,
                                    "insert into jdbc_demo.user values (null,'user4','user4')");
    // 3.2 手动制造错误,查看回滚效果
    int error = 1 / 0;
    // 3.3 手动提交
    connection.commit();
catch (SQLException throwables) {
    // 3.4 进行回滚
    connection.rollback();
    throwables.printStackTrace();
finally{
    // 记住关闭
    connection.close();
}

rollback会在代码执行出错的情况下,回滚到connection执行sql语句前的状态。


5. 封装BaseDAO

哈哈,相信大家看到这里,是不是已经感觉相比原生JDBCDbUtils用起来已经很爽了呢。

别急,还有更爽的呢→ v →

我们接下来封装一个BaseDAO ,封装一些常用的增删改查操作,然后使用子类继承的方式即可使用。

增删改查,一句sql搞定


这个就是BaseDAO的代码,基本就是整合了下前面的API。

/**
 * 封装基本的增删改查,以复用代码
 */

public class BaseDAO<T{
    QueryRunner qr = new QueryRunner(JDBCUtil.getDataSource());

    /**
     * 增删改
     *
     * @param sql    sql语句
     * @param params 占位符
     * @return
     */

    public boolean update(String sql, Object... params) {
        int result = 0;
        try {
            result = qr.update(sql, params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return result > 0;
    }

    /**
     * 查询 单个值
     *
     * @param sql
     * @param params
     * @return
     */

    public Object selectScalar(String sql, Object... params) {
        try {
            return qr.query(sql, new ScalarHandler<>(), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 查询 单行
     *
     * @param sql
     * @param clazz
     * @param params
     * @return
     */

    public T selectOne(String sql, Class<T> clazz, Object... params) {
        try {
            return qr.query(sql, new BeanHandler<>(clazz), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查询 多行
     *
     * @param sql
     * @param clazz
     * @param params
     * @return
     */

    public List<T> selectList(String sql, Class<T> clazz, Object... params) {
        try {
            return qr.query(sql, new BeanListHandler<>(clazz), params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 批量操作 增删改
     * @param sql
     * @param params
     * @return
     */

    public int[] batch(String sql,Object[][] params){

        int[] batch = new int[0]; 
        try {
            batch = qr.batch(sql,params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return batch;
    }
}

使用方式

  1. 编写一个UserDAO继承BaseDAO

     public class UserDAO extends BaseDAO<User>{
         // 可以自行拓展,BaseDAO只是封装了最基本的增删改查
     }
    

  2. 使用实例

     // 1.获取UserDAO
     UserDAO userDAO = new UserDAO();
     // 2.使用
     User user = userDAO.selectOne("select * from user where username= ?", User.class, "user1");
     // 3. 操作结果
     System.out.println(user);
     // 输出结果:User{id=1, username='user1', password='123'}
    

哈哈!是不是简简单单一句sql就搞定查询啦OwO,增删改等等也是一样喔~~



DbUtils的底层实现主要是JDBC和反射(比如生成bean,然后调用setter等等),源码稍微看个大概不是很难,有兴趣的小伙伴可以自行阅读下。


6. DEMO 源码

为了大家方便拿到文章中的DEMO代码,我把DEMO源码放到 Github 上啦,有需要的小伙伴可以自行下载喔(包括所有本文出现过的代码)。


7. 写在最后

看到这里,相信你对Java操作数据库又有了进一步的认识啦(●'◡'●)

基本在接触持久层框架前,这算是比较好的处理办法啦。


如果有哪里写得不是很好,欢迎在评论区指出

希望大家都能一起进步,不要忘记顺手点个👍b( ̄▽ ̄)d噢~~