关于JDBC中查询方法的抽取

时间:2024-03-03 10:15:54

萌新的JAVA学习笔记[1]

先来张伊蕾娜镇场~~

简单描述

起初我们的查询方法时分为单个查询和全部查询,过于局限与繁琐,如此一来我们能不能想一个办法将所有类型的查询抽取出来并整合成为一个单独的工具方法,并使之具有较为方便的通用性。
-----------------------------------------------------------------------------------

思路整理

想要进行整合,首先我们得先寻找其中的差异点,以及共同点;

共同点:

都需要SQL语句;
都需要获取连接对象;
都需要获取结果集对象;
都需要获取语句对象;
都需要将从SQL语句中读到的数据信息赋给用于封装数据的对象;
以及都需要关闭资源。

差异点:

查询范围不同;
因为查询范围不同而导致的SQL语句不同;
传入的参数不同;
返回的值类型不同(但都可以用集合接收输出)。

如此一来,思路便逐渐清晰了起来

但同时又面临新的问题,因为通用性问题,我们并不知道我们所用来封装数据的时候所用到的对象的类型,这里很容易让人想到反射以及泛型;
同时,我们也并不知道当执行查询的时候所用到的SQL语句的参数是什么类型,这里我们可以用Object...params数组来解决。
那么,我们便开始吧~~

分步明晰

首先,是我们的方法所需要传入的参数,第一个就是我们执行SQL所需要使用的SQL语句了,这个需要调用者根据不同的查询范围自行传入;
第二个便是我们用来封装对象的类的字节码对象了,这个在我们使用内省操作字段的时候需要使用,因为不知道对象类型,所以我们这里使用泛型
最后我们再设置SQL语句执行所需要调用的参数,因为参数类型繁多,我们这里使用Object...params,代表传入的值可以有0-n个。
java String sql, Class<T> type, Object...params

再接着,是各个对象的声明(在外面声明,不然在之后没法关)以及用来接收封装数据对象的集合的创建,这里使用上面声明的泛型;

Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<T> list = new ArrayList<>();

然后是不厌其烦的获取连接对象以及语句对象,这里使用我们已经写好的获取连接对象的方法;

conn = JDBCUtil.getConnection();
ps = conn.prepareStatement(sql);

再接下来,是为传入的SQL语句的占位符"?"设置值,这里使用循环遍历params数组就行了;

for (int i = 0; i < params.length; i++) {
        ps.setObject(i + 1, params[i]);
}

下面我们来获取结果集对象,再对它进行遍历,使用getObject方法将其中的数据取出来,然后通过内省将数据封装进对象中,最后再用list集合接收;

rs = ps.executeQuery();
// 直到next方法返回false时结束
while (rs.next()) {
    // 获取这一行中的指定列的数据
    //使用反射创建对象
    T t = type.newInstance();
    //将数据从结果集中获取到,并设置给对象t
    //通常情况下,属性名和列名一样,所以我们可以根据属性名去获取对应列的值
    BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(),
            Object.class);
    PropertyDescriptor[] pds =
            beanInfo.getPropertyDescriptors();
    //操作每个属性
    for (PropertyDescriptor pd : pds) {
        //获取到属性名
        String name = pd.getName();
        //根据这个属性名从结果集中获取到数据
        Object value = rs.getObject(name);
        //获取到属性对应的set方法
        Method writeMethod = pd.getWriteMethod();
        writeMethod.invoke(t, value);
    }
    list.add(t);

因为这步中的代码有点长,所以我们来稍微解析一下循环中内容。
首先获取实例对象到获取类中的属性就不细说了,然后是遍历获取到的包含了所有属性的描述器数组;
获取属性名之后通过getObject得到我们所需要的属性所对应的值,再通过各个属性描述器获取到对应属性的set方法;
最后调用invok方法将遍历结果集得到的值传入,并将的到的对象存入集合中。

完整代码

/**
 * 处理查询操作
 * <T>:是声明泛型类型
 * List<T>, Class<T>使用声明好的T
 * @param sql 要执行的SQL语句
 * @param type 将每行数据封装的对象类型
 * @param params 执行的SQL需要的参数
 * @return 返回查询到的结果,统一放到List集合中
 */
public static <T> List<T> executeQuery(String sql, Class<T> type, Object...params){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    List<T> list = new ArrayList<>();
    try {
        conn = JDBCUtil.getConnection();
        ps = conn.prepareStatement(sql);
        //为占位符设值
        for (int i = 0; i < params.length; i++) {
            ps.setObject(i + 1, params[i]);
        }
        rs = ps.executeQuery();
        // 直到next方法返回false时结束
        while (rs.next()) {
            // 获取这一行中的指定列的数据
            //使用反射创建对象
            T t = type.newInstance();
            //将数据从结果集中获取到,并设置给对象t
            //通常情况下,属性名和列名一样,所以我们可以根据属性名去获取对应列的值
            BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(),
                    Object.class);
            PropertyDescriptor[] pds =
                    beanInfo.getPropertyDescriptors();
            //操作每个属性
            for (PropertyDescriptor pd : pds) {
                //获取到属性名
                String name = pd.getName();
                //根据这个属性名从结果集中获取到数据
                Object value = rs.getObject(name);
                //获取到属性对应的set方法
                Method writeMethod = pd.getWriteMethod();
                writeMethod.invoke(t, value);
            }
            list.add(t);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        JDBCUtil.close(conn, ps, rs);
    }
    return list;
}

总结

当我们没法明确获悉对象的具体类型应该要想到反射和泛型,寻找相似方法的共同性和差异性并将它们提取出来,这是一种很不错的思路。
最后天道酬勤,反射内省的使用并没有想象中那么复杂,多练多用,便是我们逐渐登高的唯一途径。


  1. 伊蕾娜世界第一! ↩︎