PreparedStatement 和 元数据处理

PreparedStatement 和 元数据处理

1. PreparedStatement

interface java.sql.PreparedStatement extends Statement
	数据库 SQL 语句预处理搬运工对象

获取 PreparedStatement 对象涉及到的方法
	java.sql.Connection 调用
		java.sql.PreparedStatement prepareStatement(String sql);
			通过 数据库 Connection 连接对象调用 prepareStatement 传入参数是预处理 SQL 语句,得到 
			PreparedStatement

方法:
	void setObject(int index, Object value);
		给予 SQL 语句赋值操作,index 是下标,value是给予对应下标参数的赋值数据
	int executeUpdate();
		执行 update insert delete
	java.sql.ResultSet executeQuery();
		执行 select 
		
PreparedStatement 可以避免 SQL 注入问题

2. SQL注入问题

package com.qfedu.a_jdbc;

import util.JdbcUtils;

import java.sql.*;

/**
 * SQL 注入演示
 *
 * @author Anonymous 2022/4/2 9:17
 */
public class Demo1 {
    private static String username = "张三";
    private static String password = "北平没有王乾帅' or 1=1 -- ";

    public static void main(String[] args) {
        testStatement();
        testPreparedStatement();
    }

    public static void testStatement() {
        // 1. 获取数据库连接对象
        Connection connection = JdbcUtils.getConnection();

        // 2. 准备 Statement 和 ResultSet 引用数据类型变量
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            // 3. 通过 Connection 数据连接对象获取 Statement
            statement = connection.createStatement();

            // 4. SQL 语句准备
            String sql = "select * from javaee_2203.user where name = '" + username + "' and password = '" + password + "'";
            System.out.println(sql);

            // 5. 执行SQL 语句得到查询结果集对象
            resultSet = statement.executeQuery(sql);

            // 6. 判断是否查询到对应数据
            if (resultSet.next()) {
                System.out.println("Statement login success");
            } else {
                System.err.println("Statement login failed");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.close(connection, statement, resultSet);
        }

    }

    public static void testPreparedStatement() {
        // 1. 获取数据库连接对象
        Connection connection = JdbcUtils.getConnection();

        // 2. 准备数据库资源必要的引用数据类型变量
        ResultSet resultSet = null;
        PreparedStatement statement = null;

        /*
        3. 准备 SQL 语句,SQL语句带有参数,符合 PreparedStatement 与预处理 SQL 语句搬运工要求
        ? 就是 SQL 语句参数,参数存在下标,并且下标为 1 开始!!!
        */
        String sql = "select * from javaee_2203.user where name = ? and password = ?";

        try {
            /*
             4. 通过 Connection 数据库连接对象和 SQL 语句,预处理得到 PreparedStatement 对象
             预处理 SQL 语句过程是已经将 SQL 语句交给 MySQL 数据做准备!!!需要后期提供的 SQL 参数
             在给予 SQL 参数的过程汇总,MySQL 数据库语句对 SQL 语句进行了校验操作,避免 SQL 注入
             问题。
             */
            statement = connection.prepareStatement(sql);

            // 5. 给予 SQL 语句参数赋值操作
            statement.setObject(1, username);
            statement.setObject(2, password);

            // 6. 通过 PreparedStatement 对象执行 SQL 语句 得到 ResultSet 结果集对象
            resultSet = statement.executeQuery();

            // 7. 判断是否查询到对应数据
            if (resultSet.next()) {
                System.out.println("PreparedStatement login success");
            } else {
                System.err.println("PreparedStatement login failed");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.close(connection, statement, resultSet);
        }
    }

}

3. PreparedStatement 完成 CRUD 操作

3.1 PreparedStatement 操作 insert
@Test
public void testInsert() {
    // 1. 获取数据连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备 PreparedStatement 用于数据类型变量
    PreparedStatement statement = null;

    // 3. 准备用户预处理 PreparedStatement 对象使用的 SQL 语句
    String sql = "insert into javaee_2203.student(name, age, score) VALUES (?,?,?)";

    try {
        /*
        4. 预处理 SQL 语句,得到 PreparedStatement 对象
        java.sql.PreparedStatement prepareStatement(String sql);
		    通过 数据库 Connection 连接对象调用 prepareStatement 传入参数是预处理 SQL 语句,得到
		    PreparedStatement
         */
        statement = connection.prepareStatement(sql);
        int parameterCount = statement.getParameterMetaData().getParameterCount();

        System.out.println(parameterCount);
        /*
        5. 给予 SQL 语句赋值参数操作,通过 PreparedStatement 完成
        void setObject(int parameterIndex, Object value);
	        给予 SQL 语句赋值操作,index 是下标,value是给予对应下标参数的赋值数据
         */
        statement.setObject(1, "大哥");
        statement.setObject(2, "15");
        statement.setObject(3, "100");

        /*
        6. 执行 SQL 语句,得到 MySQL 数据库对应数据表的影响行数
        int executeUpdate();
	        执行 update insert delete
         */
        int affectedRows = statement.executeUpdate();

        System.out.println("数据库受到影响的行数 : " + affectedRows);

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.close(connection, statement);
    }
}
3.2 PreparedStatement 操作 update
@Test
public void testUpdate() {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备 PreparedStatement 预处理 SQL 搬运工对象
    PreparedStatement statement = null;

    // 3. 准备 SQL 语句
    String sql = "update javaee_2203.student set name = ? where id = ?";

    try {
        /*
        4. 预处理 SQL 语句得到 PreparedStatement 对象
        java.sql.Connection 调用
            java.sql.PreparedStatement prepareStatement(String sql);
                通过 数据库 Connection 连接对象调用 prepareStatement 传入参数是预处理 SQL 语句,得到
                PreparedStatement
         */
        statement = connection.prepareStatement(sql);

        /*
        5. 通过 PreparedStatement 对象设置 SQL 语句对应参数
        void setObject(int index, Object value);
	        给予 SQL 语句赋值操作,index 是下标,value是给予对应下标参数的赋值数据
         */
        statement.setObject(1, "雪珂");
        statement.setObject(2, 10);

        /*
        6. 执行 SQL 语句,得到 MySQL 对应数据表受到影响的行数
        int executeUpdate();
	            执行 update insert delete
         */
        int affectedRows = statement.executeUpdate();
        System.out.println("数据库受到影响的行数 : " + affectedRows);

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.close(connection, statement);
    }
}
3.3 PreparedStatement 操作 delete
@Test
public void testDelete() {
    // 1. 数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备 PreparedStatement 数据库预处理引用数据类型变量
    PreparedStatement statement = null;

    // 3. 准备 SQL 语句
    String sql = "delete from javaee_2203.student where id = ?";

    try {
        /*
        4. 通过数据连接对象 预处理 SQL 语句,得到 PreparedStatement 对象
        java.sql.Connection 调用
            java.sql.PreparedStatement prepareStatement(String sql);
                通过 数据库 Connection 连接对象调用 prepareStatement 传入参数是预处理 SQL 语句,得到
                PreparedStatement
         */
        statement = connection.prepareStatement(sql);

        /*
        5. 给予 SQL 语句参数
        void setObject(int index, Object value);
	        给予 SQL 语句赋值操作,index 是下标,value是给予对应下标参数的赋值数据
         */
        statement.setObject(1, 9);

        /*
        6. 执行 SQL 语句
        int executeUpdate();
	        执行 update insert delete
         */
        int affectedRows = statement.executeUpdate();

        System.out.println("数据库受到影响的行数 : " + affectedRows);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.close(connection, statement);
    }
}
3.4 PreparedStatement 操作 select 一行数据
@Test
public void testSelectOne() {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备数据库操作所需的 PreparedStatement 和 ResultSet
    ResultSet resultSet = null;
    PreparedStatement statement = null;

    // 3. 准备 SQL 语句
    String sql = "select * from javaee_2203.student where id = ?";

    try {
        // 4. 预处理 SQL 语句,得到 PreparedStatement 对象
        statement = connection.prepareStatement(sql);

        // 5. 给予 SQL 语句参数
        statement.setObject(1, 10);

        // 6. 执行 SQL 语句,得到查询结果集对象
        resultSet = statement.executeQuery();

        Student stu = null;
        // 7. 解析结果集
        if (resultSet.next()) {
            // 8. 根据字段名称获取和期望数据类型获取对应 内容
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            float score = resultSet.getFloat("score");

            stu = new Student(id, name, age, score);
        }

        System.out.println(stu);

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.close(connection, statement, resultSet);
    }

}
3.5 PreparedStatement 操作 select 多行数据
@Test
public void testSelectAll() {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备 PreparedStatement 和 ResultSet 引用数据类型变量
    PreparedStatement statement = null;
    ResultSet resultSet = null;

    // 3. 准备预处理 SQL 语句内容
    String sql = "select * from javaee_2203.student";

    try {
        // 4. 预处理 SQL 语句 得的 PreparedStatement 对象
        statement = connection.prepareStatement(sql);

        // 5. 无需参数赋值,直接执行 SQL 语句得到结果集对象
        resultSet = statement.executeQuery();

        // 6. 多行数据使用 while 解析
        ArrayList<Student> list = new ArrayList<>();

        while (resultSet.next()) {
            // 7. 根据字段名称和期望数据类型,得到对应内容
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            float score = resultSet.getFloat("score");

            list.add(new Student(id, name, age, score));
        }

        // 8. 展示数据喽!!!
        list.forEach(System.out::println);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JdbcUtils.close(connection, statement, resultSet);
    }
}

4. 元数据

4.1 参数元数据
通过 java.sql.Connection 数据库连接对象获取 PreparedStatement 对象,需要提供 SQL 语句。
在 PreparedStatement 对象是包含 SQL 语句内容。
并且 PreparedStatement可以通过 setXXX(int parameterIndex, XXX value) 对 SQL 语句进行赋值操作。

分析:
	1. PreparedStatement 包含 SQL 全部内容
	2. PreparedStatement 是已知参数个数,才可以执行 setXXX(int parameterIndex, XXX value) 执行

断点分析
	PreparedStatement 对象中有以下内容
4.2 update 方法封装
/**
 * 用户调用,给予执行 SQL 语句和对应参数,返回值是数据表受到影响的行数
 *
 * @param sql             SQL 语句
 * @param parameterValues SQL 语句对应的参数,数据类型为 Object... Object 类型不定长参数
 * @return 返回值是数据表受到影响的行数
 * @throws SQLException SQL 异常
 */
public int update(String sql, Object... parameterValues) throws SQLException {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 准备 PreparedStatement 预处理 SQL 搬运工对象
    PreparedStatement statement = null;

    // 3. 处理 SQL 语句,SQL 语句有可能为 null,使用断言判断 sql 是否为 null
    assert sql != null;
    statement = connection.prepareStatement(sql);

    // 5. 通过 PreparedStatement对象 获取 【参数元数据】。从【参数元数据】得到参数个数
    int parameterCount = statement.getParameterMetaData().getParameterCount();

    /*
    6. 进行参数赋值操作
        a. SQL 语句参数个数和参数数组长度不一致
        b. SQL 语句参数个数为 0
        以上两个条件不需要赋值,!取反剩下条件都是需要赋值的条件
     */
    if (!(parameterCount != parameterValues.length || 0 == parameterCount)) {
        for (int i = 0; i < parameterCount; i++) {
            statement.setObject(i + 1, parameterValues[i]);
        }
    }
    // 7. 执行 SQL
    int i = statement.executeUpdate();

    // 8. 关闭资源
    JdbcUtils.close(connection, statement);

    return i;
}
4.3 结果集元数据
 /*
    需要通过 ResultSet 得到 字段名称,同时利用字段名获取对应的数据内容。
    ResultSet 中有两组方法
        XXX getXXX(String columnName);
        XXX getXXX(int columnIndex);

    ResultSet 结果集中是否会包含字段名称???
        一定包含,因为 resultSet 可以利用字段名称获取对应数据,字段名称在 ResultSet 一定存在。
    是否会包含字段个数???
        一定包含, 因为 resultSet 可以利用字段下标获取对应数据,ResultSet 一定知道字段个数
     */
    // 5.2 获取 ResultSet 结果集元数据
    ResultSetMetaData metaData = resultSet.getMetaData();

    // 5.3 获取结果集中字段个数
    int columnCount = metaData.getColumnCount();
    
    // 获取字段名称
    String fieldName = metaData.getColumnName(i);
4.4 数据库查询结果为符合JavaBean规范对象方式
/**
 * 通用查询方法,根据用户提供的 SQL 语句和 SQL 对应参数得到 Class<T> 约束数据类型 List 集合
 *
 * @param sql             SQL 语句
 * @param cls             用于约束当前查询结果集对应的数据行 JavaBean 规范类型
 * @param parameterValues 对应的 SQL 语句参数
 * @param <T>             泛型约束,用于约束当前查询结果集对应的 JavaBean 规范数据类型,通过反射Class对象和泛型约束决定
 * @return List集合中带有泛型约束的 JavaBean 规范对象
 */
public <T> List<T> query(String sql, Class<T> cls, Object... parameterValues)
        throws SQLException, NoSuchMethodException, IllegalAccessException,
        InvocationTargetException, InstantiationException, NoSuchFieldException {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 预处理 SQL 语句 得到 PreparedStatement 对象
    assert sql != null;
    PreparedStatement statement = connection.prepareStatement(sql);

    // 3. 赋值 SQL 语句参数
    // 3.1 获取 SQL 语句对应参数个数
    int parameterCount = statement.getParameterMetaData().getParameterCount();

    // 3.2 利用循环对应的 Statement 进行 SQL 语句赋值操作
    if (!(0 == parameterCount || parameterCount != parameterValues.length)) {
        for (int i = 0; i < parameterCount; i++) {
            statement.setObject(i + 1, parameterValues[i]);
        }
    }

    // 4. 执行 SQL 语句,得到 ResultSet 结果集对象
    ResultSet resultSet = statement.executeQuery();

    // 5. 进行解析数据,映射数据行到指定 JavaBean 规范对象中
    /*
     BeanUtils 工具类 setProperty(Object bean, String fieldName, String value)
        给予符合 JavaBean 规范对象,指定成员变量赋值对应数据内容,数据内容为字符串类型
        方法内部可以完成字符串类型转其他任意类型。
     */
    // 5.1 准备一个 List 集合
    List<T> list = new ArrayList<>();

    /*
    需要通过 ResultSet 得到 字段名称,同时利用字段名获取对应的数据内容。
    ResultSet 中有两组方法
        XXX getXXX(String columnName);
        XXX getXXX(int columnIndex);

    ResultSet 结果集中是否会包含字段名称???
        一定包含,因为 resultSet 可以利用字段名称获取对应数据,字段名称在 ResultSet 一定存在。
    是否会包含字段个数???
        一定包含, 因为 resultSet 可以利用字段下标获取对应数据,ResultSet 一定知道字段个数
     */
    // 5.2 获取 ResultSet 结果集元数据
    ResultSetMetaData metaData = resultSet.getMetaData();

    // 5.3 获取结果集中字段个数
    int columnCount = metaData.getColumnCount();

    // 5.4 利用 while 循环开始解析 ResultSet 结果集
    while (resultSet.next()) {
        // while 循环执行一次,对应处理一行数据。 一行数据对应一个 泛型约束 T 类型数据
        T t = cls.getConstructor().newInstance();

        for (int i = 1; i <= columnCount; i++) {
            // 获取字段名称
            String fieldName = metaData.getColumnName(i);
            // 根据字段名称获取对应数据
            String value = resultSet.getString(fieldName);

            // 利用 BeanUtils 工具类,给予执行 JavaBean 规范类对象,指定成员变量名称赋值对应数据
            BeanUtils.setProperty(t, fieldName, value);
        }

        // for 循环执行完毕,数据行中的所有数据解析到 JavaBean 规范对象中
        list.add(t);
    }

    return list;
}

5. 查询结果数据分析

5.1 查询数据对应 Java 数据存储形式
JavaBean 对象是大家可以接受的方式,但是不是所有的查询结构都可以做成对象方式来存储
例如:
	多表查询结果
	单表统计结果
	单表某一个字段数据

有没有什么类型建议???
	ResultSet ==> 方便 Java 操作的数据
	ResultSet ==> JavaBean 
	ResultSet ==> Map
	ResultSet ==> Object[]

六个方法:	
	T queryBean
	List<T> queryBeanList
	Map<String, Object> queryMap
	List<Map<String, Object>> queryMapList
	Object[] queryArray
	List<Object[]> queryArrayList

需要封装一个 ResultSetHandler 处理器接口,插件式编程
5.2 查询结果数据行对应 Map<String, Object> 双边队列
public Map<String, Object> queryMap(String sql, Object... parameterValues);
Map 双边队列
	Key => String 类型 键 ==> 字段
	Value = > Object 类型 值 ==> 字段对应数据 Object 类型包容万象,使用方便。

一行数据 ==> Map 双边队列
id name age gender
1 段某某 16
Map<String, Object>
	数据形式
		"id"=1,
		"name"="段某某",
		"age"=16,
		"gender"="男"
/**
 * 查询一行数据,数据内容最终转换为 Map 双边队列。Map Key 对应数据类型为 String 类型
 * 对应字段名称,Map Value 对应 Object 类型,对应字段数据内容
 *
 * @param sql             String 字符串 SQL 语句
 * @param parameterValues 对应 SQL 语句的参数数组
 * @return Map双边队列
 */
public Map<String, Object> queryMap(String sql, Object... parameterValues) throws SQLException {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 预处理 SQL 语句,得到 PreparedStatement 对象
    assert sql != null;
    PreparedStatement statement = connection.prepareStatement(sql);

    // 3. 给予 SQL 语句赋值参数操作
    int parameterCount = statement.getParameterMetaData().getParameterCount();

    if (!(0 == parameterCount || parameterCount != parameterValues.length)) {
        for (int i = 0; i < parameterCount; i++) {
            statement.setObject(i + 1, parameterValues[i]);
        }
    }

    // 4. 执行 SQL 语句得到 ResultSet 结果集对象
    ResultSet resultSet = statement.executeQuery();

    // 5. 解析数据
    // 5.1 获取结果集元数据
    ResultSetMetaData metaData = resultSet.getMetaData();
    // 5.2 获取查询结果集中的字段个数
    int columnCount = metaData.getColumnCount();

    // 5.3 准备查询结果对应的 Map 双边队列
    HashMap<String, Object> map = new HashMap<>();
    if (resultSet.next()) {
        for (int i = 1; i <= columnCount ; i++) {
            /*
            metaData.getColumnName(i) 获取指定下标字段名称
            resultSet.getObject(i) 对应下标的数据内容
             */
            map.put(metaData.getColumnName(i), resultSet.getObject(i));
        }
    }

    return map;
}
5.3 查询结果数据行对应 Object[] 数组
id name age gender
1 段某某 16
查询结果数据行,每一行数据值,对应一个 Object类型数组
Object[] values = {1, "段某某", 16, "男"};

查询结果解析到使用过程,完全明确字段的查询顺序,查询目标,直接按照数组下标使用。

public Object[] queryArray(String sql, Object... parameterValues) throws SQLException 
/**
 * 查询一行数据,数据内容按照查询字段顺序,仅保留数据转换为一个 Object 类型数组
 *
 * @param sql             String 类型 SQL 语句
 * @param parameterValues 对应 SQL 语句的 Object 数组参数
 * @return Object类型数组
 * @throws SQLException SQL 异常
 */
public Object[] queryArray(String sql, Object... parameterValues) throws SQLException {
    // 1. 获取数据库连接对象
    Connection connection = JdbcUtils.getConnection();

    // 2. 预处理 SQL 语句得到 PreparedStatement 对象
    assert sql != null;
    PreparedStatement statement = connection.prepareStatement(sql);

    // 3. 赋值 SQL 语句参数
    int parameterCount = statement.getParameterMetaData().getParameterCount();
    if (!(0 == parameterCount || parameterCount != parameterValues.length)) {
        for (int i = 0; i < parameterCount; i++) {
            statement.setObject(i + 1, parameterValues[i]);
        }
    }

    // 4. 执行 SQL 语句,得到结果集对象
    ResultSet resultSet = statement.executeQuery();

    // 5.解析过程
    // 5.1 获取字段个数
    int columnCount = resultSet.getMetaData().getColumnCount();

    // 5.2 根据字段个数创建 Object 类型数组
    Object[] arr = new Object[columnCount];

    if (resultSet.next()) {
        for (int i = 0; i < arr.length; i++) {
            // 根据下标从查询结果集中获取数据,存储到对应的数组下标位置
            arr[i] = resultSet.getObject(i + 1);
        }
    }

    JdbcUtils.close(connection, statement, resultSet);
    return arr;
}
posted @ 2022-05-16 23:41  qtyanan  阅读(124)  评论(0编辑  收藏  举报