4. 自定义DBUtils

Java
464
0
0
2022-11-12

4. 自定义DBUtils

前言

在上一章节,我们使用 Apache-DBUtils 实现了数据库的增删查改,的确使用起来很方便。但是除了方便之余,我们还要思考一下这个 Apache-DBUtils 是如何实现的。

例如在查询的时候,提供的是可变参数的,那么如何将这些可变参数进行参数的设置,进行查询的呢?

queryRunner.update(sql, "参数1", "参数2", "参数3", "参数4");

还有如何在查询的时候获取字段名,字段个数的呢?

下面我们来自己写一个简单的自定义 DBUtils,看看实现的基本原理。

元数据

1. 存在的问题

在上面我们描述中,可以发现我们想要实现一个自定义的 DBUtils 工具的话,首先要可以知道如何知道查询的 字段名、参数个数等。

例如:假设我查询的SQL语句是 select * from user,那么我怎么去知道这个表有哪些字段,参数到底有多少个?

2.问题的解决

我们想要知道查询的 字段名, 参数个数 等, 这些可以通过数据库的元数据库进行获得.

元数据在建立框架和架构方面是特别重要的知识,下面可以使用数据库的元数据来创建自定义JDBC工具包, 模仿DBUtils.

下面先来介绍一下什么是元数据。

3. 什么是元数据

元数据(MetaData),即定义数据的数据。

打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。

歌曲:凉凉

作词:刘畅

演唱: 杨宗纬 张碧晨

时长: 3分28秒

...

简单来说: 数据库的元数据就是 数据库、表、列的定义信息。

主要我们会使用到的元数据有以下的两种:

① 由PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。

② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。

3.1 ParameterMetaData

3.1.1概述

ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData可用于获取有关PreparedStatement对象和其预编译sql语句 中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型

img

获得ParameterMetaData:

ParameterMetaData parameterMetaData =  preparedStatement.getParameterMetaData()
3.1.2ParameterMetaData相关的API
  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)

所以如果使用 Mysql 数据库,我们只能使用如下:

//4. 获得参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
// 获得sql语句里面参数的个数
int parameterCount = parameterMetaData.getParameterCount();
System.out.println("parameterCount = "+parameterCount);
3.1.3实例代码

img

    /**
     * ParameterMetaData相关的API
     * @throws Exception
     */ 
    @Test 
    public void test03() throws Exception {

        //0 根据druid.properties创建配置文件对象 
        Properties properties = new Properties();
        // 关联druid.properties文件 
        InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(is);
        //1. 创建DataSource 
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //2. 从数据源(连接池)获得连接对象 
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        // 3. 设置SQL语句 
        // 使用 prepareStatement 来解决SQL注入 
        String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数 
        PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement

        //4. 获得参数的元数据 
        ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
        // 获得sql语句里面参数的个数 
        int parameterCount = parameterMetaData.getParameterCount();
        System.out.println("parameterCount = "+parameterCount);

    }

3.2 ResultSetMetaData

3.2.1概述

ResultSetMetaData 是由 ResultSet 对象通过 getMetaData 方法获取而来,ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

img

获得 ResultSetMetaData 的两种方式:

// 方式一:这种方式最干脆
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData(); // 通过 preparedStatement 获取

// 方式二:
// 1. 设置SQL语句
// 使用 prepareStatement 来解决SQL注入
String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数
PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
//设置参数
preparedStatement.setObject(1, 2);

// 2. 获取结果集元数据
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // 通过结果集 resultSet 获取
3.2.2 resultSetMetaData 相关的API
  • getColumnCount(); 获取结果集中列项目的个数
  • getColumnName(int column); 获得数据指定列的列名
  • getColumnTypeName();获取指定列的SQL类型
  • getColumnClassName();获取指定列SQL类型对应于Java的类型
3.2.3 实例代码

img

    /**
     * resultSetMetaData  相关的API
     * @throws Exception
     */ 
    @Test 
    public void test04() throws Exception {

        //0 根据druid.properties创建配置文件对象 
        Properties properties = new Properties();
        // 关联druid.properties文件 
        InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(is);
        //1. 创建DataSource 
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //2. 从数据源(连接池)获得连接对象 
        Connection connection = dataSource.getConnection();
        System.out.println(connection);

        // 3. 设置SQL语句 
        // 使用 prepareStatement 来解决SQL注入 
        String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数 
        PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
        
        //4. 获取结果集元数据 
        ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();

        int columnCount = resultSetMetaData.getColumnCount();
        System.out.println("获取结果集中列项目的个数 columnCount = " + columnCount); // 获取结果集中列项目的个数

        for (int i = 1; i <= columnCount; i++) {
            //获取每列的列名 
            String columnName = resultSetMetaData.getColumnName(i);
            System.out.println("获取每列的列名: " + columnName);
        }
    }

4.小结

  1. 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型...
  2. mysql元数据:
  • ParameterMetaData
  • ResultSetMetaData

自定义DBUtils增删改

1.需求

模仿DBUtils, 完成增删改的功能

2.分析

  1. 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
  2. 定义int update(String sql, Object...params)方法
//1.从dataSource里面获得connection
//2.根据sql语句创建预编译sql语句对象
//3.获得参数元数据对象, 获得参数的个数
//4.遍历, 从params取出值, 依次给参数? 赋值
//5.执行
//6.释放资源

3.实现

3.1 创建 MyQueryRunner 类

/**
 * @author Aron.li
 * @date 2021/1/26 23:36
 */
public class MyQueryRunner {

    //定义连接池 
    private DataSource dataSource;

    //构造器: 传入一个连接池 
    public MyQueryRunner(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 执行增删改的SQL语句的方法
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */ 
    public int update(String sql,Object... params) throws SQLException {
        //1. 获取连接对象 
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句 
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数 
        //3.1 获取参数的个数 
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData(); // 获取参数元数据 
        int parameterCount = parameterMetaData.getParameterCount(); // 获取参数的个数 
        //3.2 设置参数 
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句 
        int num = pstm.executeUpdate(); // 返回受影响的行数 
        //5. 关闭资源
        pstm.close();
        connection.close();

        return num;
    }

}

3.2 使用 MyQueryRunner 类新增一条数据

/**
 * 使用 MyQueryRunner 类新增一条数据
 */
@Test
public void test09() throws SQLException {
    //1. 获取Druid连接池 
    DataSource dataSource = DruidUtil.getDataSource();

    //2. 创建自定义 MyQueryRunner 类 
    MyQueryRunner queryRunner = new MyQueryRunner(dataSource);

    //3. 设置插入数据的SQL 
    String sql = "insert into user values (null,?,?,?,?)";

    //4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句 
    int i = queryRunner.update(sql, "特朗普", "tlbsb", "USA", "123456");
    System.out.println(i);
}

3.3 使用 MyQueryRunner 类 更新一条数据

/**
 * 使用 MyQueryRunner 类更新一条数据
 */
@Test
public void test10() throws SQLException {
    //1. 获取Druid连接池 
    DataSource dataSource = DruidUtil.getDataSource();

    //2. 创建自定义 MyQueryRunner 类 
    MyQueryRunner queryRunner = new MyQueryRunner(dataSource);

    //3. 设置插入数据的SQL 
    String sql = "update user set name=? where id = ?";

    //4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句 
    int i = queryRunner.update(sql, "奥巴马", 8);
    System.out.println(i);
}

3.4 使用 MyQueryRunner 类 删除一条数据

/**
 * 使用 MyQueryRunner 类删除一条数据
 */
@Test
public void test11() throws SQLException {
    //1. 获取Druid连接池 
    DataSource dataSource = DruidUtil.getDataSource();

    //2. 创建自定义 MyQueryRunner 类 
    MyQueryRunner queryRunner = new MyQueryRunner(dataSource);

    //3. 设置插入数据的SQL 
    String sql = "delete from user where id = ?";

    //4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句 
    int i = queryRunner.update(sql, 8);
    System.out.println(i);
}

4.小结

  1. 先模拟DBUtils写出架子
  2. update()
  • 封装了PrepareStatement
  • 用到了参数元数据

自定义DBUtils查询多条数据封装到List<POJO>

在上面我自定义 MyQueryRunner 类中,我们已经实现了 增删改,那么再来实现一个 查询多条数据的 方法。

1.在 MyQueryRunner 类中,增加查询多条数据的方法 queryList

/**
 * @author Aron.li
 * @date 2021/1/26 23:36
 */
public class MyQueryRunner {

    //定义连接池 
    private DataSource dataSource;

    //构造器: 传入一个连接池 
    public MyQueryRunner(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 执行增删改的SQL语句的方法
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */ 
    public int update(String sql,Object... params) throws SQLException {
        //1. 获取连接对象 
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句 
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数 
        //3.1 获取参数的个数 
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData(); // 获取参数元数据 
        int parameterCount = parameterMetaData.getParameterCount(); // 获取参数的个数 
        //3.2 设置参数 
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句 
        int num = pstm.executeUpdate(); // 返回受影响的行数 
        //5. 关闭资源
        pstm.close();
        connection.close();

        return num;
    }

    /**
     * 查询到多条数据封装到List<T>
     * @param sql
     * @param clazz
     * @param params
     * @param <T>
     * @return
     * @throws Exception
     */ 
    public <T> List<T> queryList(String sql, Class<T> clazz, Object... params) throws Exception {
        //1. 获取连接对象 
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句 
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数 
        //3.1 获取参数的个数 
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
        int parameterCount = parameterMetaData.getParameterCount();
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句 
        ResultSet rst = pstm.executeQuery();
        //获取结果集元数据 
        ResultSetMetaData rstMetaData = rst.getMetaData();
        //获取结果集的列数 
        int columnCount = rstMetaData.getColumnCount();

        //1. 获取POJO中的所有方法
        Method[] methods = clazz.getMethods();

        //声明一个集合存储t
        List<T> tList = new ArrayList<>();
        while (rst.next()) {
            //创建出封装数据的POJO对象 
            T t = clazz.newInstance();

            for (int i = 1; i <= columnCount; i++) {
                //获取每列的名字(字段名) 
                String columnName = rstMetaData.getColumnName(i);
                //1. 获取每一行数据的各个字段的数据 
                Object columnValue = rst.getObject(columnName);

                //2. 调用对象的对应的set方法,往对象中设置数据 
                //遍历出POJO的每一个方法 
                for (Method method : methods) {
                    //获取方法名 
                    String methodName = method.getName();
                    //匹配方法名 
                    if (methodName.equalsIgnoreCase("set"+columnName)) {
                        //说明匹配到了对应的set方法,那么就调用这个set方法,设置columnValue
                        method.invoke(t,columnValue);
                    }
                }

            }

            tList.add(t);
        }

        //关闭资源
        rst.close();
        pstm.close();
        connection.close();
        return tList;
    }

}

2. 测试查询多条数据

img

/**
 * 使用 MyQueryRunner 类查询多条数据
 */
@Test
public void test12() throws Exception {
    //1. 获取Druid连接池 
    DataSource dataSource = DruidUtil.getDataSource();
    //2. 创建自定义 MyQueryRunner 类 
    MyQueryRunner queryRunner = new MyQueryRunner(dataSource);
    //3. 设置查询多条数据的SQL 
    String sql = "select * from user where id > ?";
    //4. 使用QueryRunner对象调用queryList查询多个数据
    List<User2> userList = queryRunner.queryList(sql, User2.class, 2);
    //5. 遍历结果 
    for (User2 user : userList) {
        System.out.println(user);
    }
}