04-数据库操作对象Statement对象和PreparedStatement对象的区别,SQL注入的优缺点

news2025/1/14 20:38:45

Statement对象和查询结果集

Statement对象相关的方法

Connection接口中获取数据库操作对象Statement对象的方法

方法名功能
Statement createStatement()创建Statement对象

Statement对象执行增删改查的SQL语句(不含占位符"?")的方法,JDBC中的SQL语句不需要提供分号结尾

方法名功能
int executeUpdate(insert/delete/update)执行dml语句(增删改),返回受影响的行数
ResultSet executeQuery(select)执行dql语句(查询),返回 ResultSet 结果集对象
Connection conn = null;
Statement stmt = null;
//1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode","root","333");
//3、获取数据库操作对象
stmt = conn.createStatement();
//4、执行SQL语句	
// String sql = "delete from dept where deptno = 40";
String sql = "update dept set dname = '销售部', loc = '天津' where deptno = 20";
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "修改成功" : "修改失败");

ResultSet查询结果集的方法

当我们使用数据库操作对象执行查询语句时会生成ResultSet结果集对象(包含查询到的数据),该对象保持一个光标指向当前的数据行(最初光标位于第一行之前)

在这里插入图片描述

ResulrSet结果集的常用方法: 取出当前行中字段的值时可以以结果集中字段的名称(语义更明确)或字段所在索引(下标从1开始)作为依据获取

方法名功能
boolean next()最初光标位于第一行之前,执行next方法会让光标向下一行移动,如果没有下一行返回 false
boolean previous()向上移动一行,如果没有上一行则返回false
String getString(列的索引/字段名)不管结果集中字段的数据类型是什么,都以String的形式取出字段值
Date getDate(列的索引/字段名)以Date的形式取出字段的值
int getInt(列的索引/字段名)以int的形式取出字段的值
Xxx getXxx( 列的索引/字段名 )以指定的数据类型取出结果集中的数据 , 前提是该数据类型可以正常转换
Object getObject( 列的索引/字段名 )以对象的形式取出结果集中的数据
String sql = "select empno as a,ename,sal from emp";
// 专门执行DQL语句的方法
rs = stmt.executeQuery(sql);// 处理查询结果集
while(rs.next()){
	// 以结果集中列的下标获取,JDBC中所有下标从1开始,不是从0开始
	String empno = rs.getString(1);
	String ename = rs.getString(2);
	String sal = rs.getString(3);
	System.out.println(empno + "," + ename + "," + sal);

	// 以结果集中列的名称获取,列名称不是数据库表中的列名称而是查询结果集的列名称
	// 除了以String类型取出之外,还能以特定的类型取出
    int empno = rs.getInt("a");
	String ename = rs.getString("ename");
	double sal = rs.getDouble("sal");
	System.out.println(empno + "," + ename + "," + (sal + 200));
}

SQL注入问题

SQL注入的优缺点

SQL注入问题: 在对SQL语句拼接时不使用占位符,而是将用户提供的非法信息直接拼接到到要执行的SQL语句当中,会导致原SQL语句的含义被扭曲了

  • 如将用户名zhangsan及其密码123456' or '1'='1(两个用or连接的条件去掉两边的引号)拼接到SQL语句当中
  • 字符串拼接变量或表达式的方法: 先加双引号中间加两个+号,两个加号中间加表达式
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
// select * from t_user where loginName = 'zhangsan' and loginPwd = '123456' or '1'='1';
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";

// 完成了sql语句的拼接后发给DBMS,然后DBMS对拼接好的sql语句进行编译
rs = stmt.executeQuery(sql);

SQL注入的用途:凡是业务方面要求进行SQL语句拼接的必须使用Statement对象,如用户在控制台输入desc/asc决定降序/升序

// 用户在控制台输入desc就是降序,输入asc就是升序
Scanner s = new Scanner(System.in);
System.out.println("输入desc或asc,desc表示降序,asc表示升序");
System.out.print("请输入:");
String keyWords = s.nextLine();

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root","333");

String sql = "select ename from emp order by ename ? " ;
// 给占位符传值后的结果select ename from emp order by ename 'desc或asc'(sql语法错误)
ps = conn.prepareStatement(sql);
ps.setString(1, keyWords);

// 使用Statement对象进行sql语句拼接时不会出现问题
stmt = conn.createStatement();
String sql = "select ename from emp order by ename " + keyWords;
rs = stmt.executeQuery(sql);
// 遍历结果集
while(rs.next()){
    System.out.println(rs.getString("ename"));
}

PreparedStatement对象和查询结果集

PreparedStatement相关方法

Connection接口中获取数据库操作对象PreparedStatement对象的方法,创建对象的同时对SQL语句进行预编译

方法名功能
PreparedStatement prepareStatement(sql)创建预处理对象

PreparedStatement接口继承了java.sql.Statement,是预编译的数据库操作对象(SQL语句含占位符),可以解决SQL注入问题

  • 预先对包含占位符的SQL语句进行编译,然后再给占位符传值,即使用户提供的信息中含有sql语句关键字也无法参与编译过程,最终被当作普通的字符处理

将预编译的SQL语句的参数用占位符?(不能使用单引号括起来)表示,一个?表示一个占位符,最终调用ps对象的setXxx()方法给占位符传值(下标从1开始)

  • 一个PreparedStatement对象每次只能预编译一条SQL语句并且在编译阶段会做类型的安全检查

数据库第一次执行SQL语句时会先进行编译,如果第二次执行时SQL语句没有任何变化则直接执行不再编译

  • Statement对象: 编译一次执行一次且每次执行的是一个完整的SQL语句
  • PreparedStatement对象(效率较高): 预先对含占位符SQL语句的编译,然后调用ps对象的setXxx()方法给占位符传值,同一个SQL模板编译一次可执行N次

PreparedStatement接口中的方法: 方法的参数中不能再写SQL语句,否则就会重新编译SQL语句

方法名功能
int executeUpdate()执行dml语句(增删改),返回受影响的行数
ResultSet executeQuery()执行dql语句(查询),返回 ResultSet 结果集对象
execute()执行任意的sql,返回布尔值
void setXxx(占位符索引 , 占位符的值)给占位符设置对应类型的值,占位符下标从1开始
void setString(占位符索引 , 占位符的值)给占位符设置的值在sql语句中被当作字符串处理(自动加引号)
void setInt(占位符索引 , 占位符的值)给占位符设置的值在sql语句中被当作int类型的数据处理(不会加引号)

执行DQL语句

// 一个?表示一个占位符,一个?将来可以接收一个值
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
// 程序执行到此处会将占位符的sql语句发送给DBMS,然后DBMS对该sql语句进行预编译
ps = conn.prepareStatement(sql);
// 给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setString(1, loginName);
ps.setString(2, loginPwd);
// 执行sql,ps已经预编译过了sql语句,如果再传就会重新编译sql语句
rs = ps.executeQuery();
// 处理结果集
if(rs.next()){
    // 登录成功
    loginSuccess = true;
}

执行DML语句

// 执行插入语句
String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, 60);
ps.setString(2, "销售部");
ps.setString(3, "上海");

// 执行更新语句
String sql = "update dept set dname = ?, loc = ? where deptno = ?";
ps2 = conn.prepareStatement(sql);
ps2.setString(1, "研发一部");
ps2.setString(2, "北京");
ps2.setInt(3, 60);

// 执行删除语句
String sql = "delete from dept where deptno = ?";
ps3 = conn.prepareStatement(sql);
ps3.setInt(1, 60);
System.out.println(count);

模糊查询

查找第二个字母包含A的员工

Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
// 使用工具类获取连接
conn = DBUtil.getConnection();

// 以下是错误的写法,?一定不能用单引号括起来
String sql = "select ename from emp where ename like '_?%'";
ps = conn.prepareStatement(sql);
ps.setString(1, "A");

// 正确写法
String sql = "select ename from emp where ename like ?";
ps = conn.prepareStatement(sql);
ps.setString(1, "_A%");
rs = ps.executeQuery();
while(rs.next()){
    System.out.println(rs.getString("ename"));
}

模拟用户登录功能(防止注入)

需求: 从数据库中查询用户信息,实现用户登陆功能

public class JDBCTest {
    public static void main(String[] args) {
        // 初始化一个界面
        Map<String,String> userLoginInfo = initUI();
        // 验证用户名和密码
        boolean loginSuccess = login(userLoginInfo);
        // 最后输出结果
        System.out.println(loginSuccess ? "登录成功" : "登录失败");
    }
}

第一步: 初始化用户界面,可以让用户输入的用户名和密码等登录信息

private static Map<String, String> initUI() {
    Scanner s = new Scanner(System.in);
    System.out.print("用户名:");
    String loginName = s.nextLine();
    System.out.print("密码:");
    String loginPwd = s.nextLine();
    Map<String,String> userLoginInfo = new HashMap<>();
    userLoginInfo.put("loginName", loginName);
    userLoginInfo.put("loginPwd", loginPwd);
    return userLoginInfo;
}

第二步: 实现用户登陆的业务逻辑

private static boolean login(Map<String, String> userLoginInfo) {
    // false表示失败,true表示成功
    boolean loginSuccess = false;
    // 单独定义变量
    String loginName = userLoginInfo.get("loginName");
    String loginPwd = userLoginInfo.get("loginPwd");
    // 获取预编译的数据库操作对象PreparedStatement
    Connection conn = null;
    PreparedStatement ps = null; 
    ResultSet rs = null;
    try {
        // 1、注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2、获取连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "333");
        // 3、获取预编译的数据库操作对象同时对SQL语句进行预编译
        String sql = "select * from t_user where loginName = ? and loginPwd = ?";
        ps = conn.prepareStatement(sql);
        // 给占位符?传值,下标从一开始
        ps.setString(1, loginName);
        ps.setString(2, loginPwd);
        // 4、执行sql
        rs = ps.executeQuery();
        // 5、处理结果集
        if(rs.next()){
            // 登录成功
            loginSuccess = true;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 6、释放资源
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    return loginSuccess;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1281455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【开源】基于JAVA的医院门诊预约挂号系统

项目编号&#xff1a; S 033 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S033&#xff0c;文末获取源码。} 项目编号&#xff1a;S033&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2…

【RotorS仿真系列】Ardrone模型介绍

ardrone是rotors仿真框架提供的一款机型&#xff0c;因为该机型与我们实际使用的机型参数相近&#xff0c;所以这里对它的参数做特别整理和记录。 一、模型参数总结 ardrone的gazebo模型如下图所示&#xff1a; 根据ardrone.yaml&#xff0c;其关键参数如下所示&#xff1a…

Python基础快速过一遍

文章目录 一、变量及基本概念1、变量2、变量类型3、变量格式化输出4、type()函数5、input()函数6、类型转换函数7、注释 二、Python运算/字符1、算数运算2、比较运算3、逻辑运算4、赋值运算符5、转义字符6、成员运算符 三、判断/循环语句1、if判断语句2、while循环语句3、for循…

51单片机应用从零开始(九)·数组

目录 1. 用字符型数组控制 P0 口 8 位 LED 流水点亮 2. 用 P0 口显示字符串常量 1. 用字符型数组控制 P0 口 8 位 LED 流水点亮 C语言中的字符型数组是一种数据类型&#xff0c;它是一个由字符组成的序列&#xff0c;以空字符\0结尾。在声明字符型数组时&#xff0c;需要指…

总结react中css的使用

1、css in js css in js有很多库&#xff0c;这里介绍styled-components styled-components 下载【vscode可以安装vscode-styled-components 插件&#xff0c;有代码提示】 npm i styled-components 1、然后为某个组件新建style.js文件&#xff0c;然后写一些样式。 impo…

SpringAMQP入门案例——发送消息

依赖 <!--SpringAMQP起步依赖--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> yml配置文件 自行修改 spring:rabbitmq:host: 192.168.220.130 # …

【力扣】——可获得的最大点数(滑动窗口)

几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有卡牌的点数之和。 给你一个整数数组 cardPoi…

「C++」C++11新特性

&#x1f4bb;文章目录 &#x1f4c4;前言右值引用概念右值引用的意义 lambada表达式包装器function包装器bind包装器 &#x1f4d3;总结 &#x1f4c4;前言 C标准10年磨一剑&#xff0c;于2011年迎来了它真正意义上的第二个标准&#xff0c;C11能更好地适用与系统开发和库开发…

高校人员信息管理系统C++

代码&#xff1a;https://mbd.pub/o/bread/ZZeZk5lx 一、基本内容论述 1、问题描述 某高校有四类员工&#xff1a;教师、实验员、行政人员、教师兼行政人员&#xff1b;共有的信息包括&#xff1a;编号、姓名、性别、年龄等。其中&#xff0c;教师还包含的信息有&#xff1a;所…

实现一个简单的网络通信下(udp)

时间过去好久了&#xff0c;先回忆一下上一篇博客的代码&#xff01;&#xff01; 目前来看&#xff0c;我们客户端发一条消息&#xff0c;我服务器收到这一条消息之后呢&#xff0c;服务器也知道了是谁给我发来的消息&#xff0c;紧接这就把这条消息放进buffer当中&#xff0c…

陀螺仪LSM6DSV16X与AI集成(1)----轮询获取陀螺仪数据

陀螺仪LSM6DSV16X与AI集成.1--轮询获取陀螺仪数据 概述视频教学样品申请通信模式管脚定义IIC通信模式速率生成STM32CUBEMX串口配置IIC配置CS和SA0设置串口重定向参考程序初始换管脚获取ID复位操作BDU设置设置量程和速率配置过滤链轮询读取数据主程序演示 概述 本文将介绍如何使…

【算法】单调栈题单——矩阵系列⭐

文章目录 题目列表84. 柱状图中最大的矩形&#xff08;单调栈找左右两边第一个更低的位置&#xff09;85. 最大矩形⭐⭐⭐⭐⭐解法1——使用柱状图的优化暴力方法解法2——单调栈 &#xff1a;归因到 84. 柱状图中最大的矩形 &#x1f402; 1504. 统计全 1 子矩形⭐解法1——枚…

关于媒体查询不能生效的原因

问题 今天写媒体查询&#xff0c;遇到了个问题&#xff0c;卡了很久&#xff0c;引入三个样式&#xff1a;mainPageCommon.css、mainPageBig.css、mainPageSmall.css。其中的两个样式可以生效&#xff0c;但是小尺寸的媒体查询不能生效&#xff0c;这里很奇怪&#xff01;&…

STM32F407-14.3.10-01PWM模式

PWM 模式 脉冲宽度调制模式可以生成一个信号&#xff0c;该信号频率由 TIMx_ARR⑩ 寄存器值决定&#xff0c;其占空比由 TIMx_CCRx⑤ 寄存器值决定。 通过向 TIMx_CCMRx 寄存器中的 OCxM⑰ 位写入 110 &#xff08;PWM 模式 1&#xff09;或 111 &#xff08;PWM 模式 2&#…

C++学习之路(十七)C++ 用Qt5实现一个工具箱(增加托盘图标并且增加显示和退出菜单)- 示例代码拆分讲解

上篇文章&#xff0c;我们用 Qt5 实现了在小工具箱中添加了《为屏幕颜色提取功能增加一个点击复制的功能》功能。今天我们增加一个比较正式点的功能&#xff0c;就是增加托盘图标并且增加显示和退出菜单&#xff08;越来越像回事了吧 &#x1f601; &#xff09;。下面我们就来…

ssm医院门诊互联电子病历管理信息系统源码和论文

摘 要 网络的广泛应用给生活带来了十分的便利。所以把医院门诊互联电子病历管理与现在网络相结合&#xff0c;利用java技术建设医院门诊互联电子病历管理信息系统&#xff0c;实现医院门诊互联电子病历的信息化。则对于进一步提高医院门诊互联电子病历管理发展&#xff0c;对…

【电机控制】PMSM无感foc控制(五)相电流检测及重构 — 单电阻采样

0. 前言 相电流采样再FOC控制中是一个关键的环节&#xff0c;鉴于成本和易用性&#xff0c;目前应用较多的相电流采样方式是分流电阻采样&#xff0c;包括单电阻、双电阻以及三电阻采样法。 本章节先讲解单电阻采样相电流的检测及重构技术&#xff0c;在下一章讲解双电阻和三电…

项目实战一-性能测试筑基

这里写目录标题 一、为什么程序会出现性能问题、性能问题是怎么出现的&#xff1f;二、功能测试和性能测试的区别是什么&#xff1f;三、核心性能指标1、用户角度核心a、响应时间&#xff1a;b、并发量 2、成本角度3、运维角度面试题、并发量和吞吐量得区别&#xff1f;a、吞吐…

Qt 如何操作SQLite3数据库?数据库创建和表格的增删改查?

# 前言 项目源码下载 https://gitcode.com/m0_45463480/QSQLite3/tree/main # 第一步 项目配置 平台:windows10 Qt版本:Qt 5.14.2 在.pro添加 QT += sql 需要的头文件 #include <QSqlDatabase>#include <QSqlError>#include <QSqlQuery>#include &…

【强化学习算法】Q-learning原理及实现

实现代码github仓库&#xff1a;RL-BaselineCode 代码库将持续更新&#xff0c;希望得到您的支持⭐&#xff0c;让我们一起进步&#xff01; 文章目录 1. 原理讲解1.1 Q值更新公式1.2 ε-greedy随机方法 2. 算法实现2.1 算法简要流程2.2 游戏场景2.3 算法实现 3. 参考文章 1. 原…