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;
}