当前位置: 首页 > backend >正文

PrepareStatement用法(附源码解析)

PrepareStatement 基本用法

1. 加载驱动

首先在pom.xml 中引入 mysql 依赖

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.23</version>
</dependency>

通过以下代码加载驱动

Class.forName("com.mysql.cj.jdbc.Driver");

2. 获取 Connection 对象

数据库连接需要数据库链接、用户名、密码三个参数,建议配置在application.properties 中,方便统一管理(当然也可以直接在使用的地方用字符串变量)

#PrepareStatement
#数据库链接
pstmt.dbUrl="jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true"
#用户名
pstmt.dbUser=root
#密码
pstmt.dbPwd=123456

通过以下代码获取connection

Connection connection = DriverManager.getConnection(dbUrl,dbUser,dbPwd);

3. 获取 PrepareStatement 对象

根据需求创建Sql语句字符串,在需要加入参数的地方用 ? 占位符代替

//查询符合指定年龄和性别的员工姓名
String sql = "SELECT a.name FROM staff_info a WHERE a.age = ? and a.sex = ?";

接下来,创建 PrepareStatement对象对sql进行预编译

PreparedStatement pstmt = connection.prepareStatement(sql);

4. 设置参数

PrepareStatement对象提供多种参数类型方法,使用时依据数据库字段类型一一对应,设置时需注意第一个参数为占位符? 的索引且从 1 开始,例如之前sql的设置应为

pstmt.setInt(1,35);
pstmt.setString(2,"男");

5. SQL执行

单条语句执行

//单次查询
pstmt.executeQuery();
//单次更新
pstmt.executeUpdate();

批量执行

若一次要插入大量数据,应尽量减少jdbc的调用次数,使用批量执行来优化

//第一批
pstmt.setInt(1,35);
pstmt.setString(2,"男");
//加入批次
pstmt.addBatch();//第二批
pstmt.setInt(1,24);
pstmt.setString(2,"女");
pstmt.addBatch();//统一批量执行
pstmt.executeBatch();

6. 完整示例

@Component
public class PrepareStatementUtils {private  final Logger LOGGER = LoggerFactory.getLogger(PrepareStatementUtils.class);@Value("${pstmt.dbUrl}")private  String dbUrl;@Value("${pstmt.dbUser}")private  String dbUser;@Value("${pstmt.dbPwd}")private  String dbPwd;public void prepareStatementExecute() {try {//加载 mysql 驱动Class.forName("com.mysql.cj.jdbc.Driver");//获取链接Connection connection = DriverManager.getConnection(dbUrl,dbUser,dbPwd);//获取preparestatement对象String sql = "SELECT a.name FROM staff_info a WHERE a.age = ? and a.sex = ?";PreparedStatement pstmt = connection.prepareStatement(sql);//单次执行pstmt.setInt(1,35);pstmt.setString(2,"男");ResultSet rs = pstmt.executeQuery();//批量执行String sql2 = "insert into staff_info(staff_id,staff_name,staff_age,staff_sex) values(?,?,?,?)";PreparedStatement pstmt1 = connection.prepareStatement(sql2);//第一批pstmt1.setString(1,"001");pstmt1.setString(2,"小谭");pstmt1.setInt(3,35);pstmt1.setString(4,"男");//加入批次pstmt1.addBatch();//第二批pstmt1.setString(1,"002");pstmt1.setString(2,"小高");pstmt1.setInt(3,24);pstmt1.setString(4,"女");pstmt1.addBatch();//统一执行pstmt1.executeBatch();} catch (ClassNotFoundException | SQLException e) {LOGGER.error(e.getMessage());}}
}

SQL注入攻击(附源码解析)

SQL注入攻击: 通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它是目前黑客对数据库进行攻击的最常用手段之一。

1. Statement 发生 SQL 注入的情况

现存在以下语句查询指定年龄的职工信息,其中age为入参

String sql = "SELECT * FROM staff_info a WHERE a.staff_age ="+age;
ResultSet rs = statement.executeQuery(sql);

若age值为24 or 1=1

String age = "24 or 1=1";

最后的拼接语句为

SELECT * FROM staff_info a WHERE a.staff_age = 36 OR 1=1;

会返回该表的所有职工信息;常见的SQL注入还存在于恶意删表,例如:

String age = "24; drop table staff_info";

该语句会删除职工信息表,造成严重后果,因此防止SQL注入是必备操作。

2. PrepareStatement 防止SQL注入源码解析

同样存在以下语句查询指定年龄和性别的职工信息,其中age和sex为入参

String sql = "SELECT * FROM staff_info a WHERE a.staff_age = ? and a.staff_sex = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);

同样以SQL注入的形式设置sex参数为 male' or 1='1

pstmt.setInt(1,36); 
pstmt.setString(2,"male' or 1='1");

打印一下看看PrepareStatement预编译的语句,

System.out.println(pstmt);

从以下结果可知,PrepareStatement会在占位符的两侧加上 ' ,但你可能已经发现到目前为止预编译拼接的sql仍然是存在SQL注入风险的sql,若被直接执行,也会返回表的全部信息,别着急,我们接着看;

接下来执行SQL;

ResultSet rspre = pstmt.executeQuery();

通过 debug 进入 com.mysql.cj.jdbc.ClientPreparedStatementexecuteQuery()方法

可以看出,在 fillSendPacket() 方法将sql转换为字节的过程中,给占位符中包含的单引号',额外加上一个单引号 ' ,最后执行的sql就变为了:

SELECT * FROM staff_info a WHERE a.staff_age =  36 and a.staff_sex = 'male'' or 1=''1'

该查询返回信息为空,避免了SQL注入;接下来我们继续debug,到底是哪个操作加上了单引号'

找到 buligComQuery()方法,有如下代码,其中:

  1. staticSqlStrings[] :存储的是以占位符? 划分开的sql语句转换的字节数组,不包含参数,即
  • staticSqlStrings[0]= SELECT * FROM staff_info a WHERE a.staff_age =
  • staticSqlStrings[1] = and a.staff_sex =
  1. bindValues[] :绑定的参数数组

从以上源码可以看出,sql语句和参数共同组装成 sendPacket 发给远程数据库做执行,而防SQL注入的处理,肯定就在 writeAsText() 流程中;

我们继续往下,来到 com.mysql.cj.protocol.a.StringValueEncoder 类中,该类用于处理String参数,于是可以定位到在将参数转换为字节数组的 getBytes() 方法中对输入的字符串进行了 StringUtils.escapeString() 处理:

在这里插入图片描述

既然已经发现字符串的工具类调用,不出意外我们马上就要找到答案了
在这里插入图片描述

果然,功夫不负有心人,我们终于找到了它。

可以看到方法中对各类特殊字符都做了转义处理,常规的添加斜杠 \ 如换行符\n ;而我们这里涉及的单引号 ' ,转义时会再添加一个 ', 这也解释了上述最终执行sql 的生成。

Ps:最近干活用到PrepareStatement,网上提到的PrepareStatement源码解析多为老驱动(做法为添加斜杠\ ),实际使用时发现转义后其实添加的是单引号' ,正好就顺道学习下源码,也借此回顾一下SQL注入。

欢迎访问个人博客:DunkingCurry’ s Blog

http://www.xdnf.cn/news/11442.html

相关文章:

  • ubuntu使用VNC实现远程桌面
  • android drawtext 方法,8.2.13 drawText方法:绘制字符串
  • Windows系统字体优化方案(系统字体替换)
  • AAV相关研究最新进展(2023年5月)
  • 针对centos(Linux)多网卡bond绑定模式及其操作
  • LinearLayout
  • 29 | 分布式高可靠之流量控制:大禹治水,在疏不在堵
  • 【Android OpenGL开发】OpenGL ES与EGL介绍
  • ContentProvider的相关知识总结
  • 新加坡云服务器推荐 - 适合跨境外贸等业务
  • 2 v11补丁安装_老款macbook机型欺骗补丁强制安装macOS11 Big Sur图文详解
  • Linux下权限的修改-JDK的配置-文件的常见操作
  • Java学习 布局管理器之GridLayout(网格布局)
  • 企业最新几种好用的数据同步工具对比
  • Qt 多线程的几种实现方式
  • Linux系统三步安装QQ
  • C/C++网络编程
  • JSTL-核心标签库
  • 动态域名内网穿透(永久免费)
  • 设计模式七大原则-迪米特法原则
  • 安全小课堂丨什么是暴力破解?如何防止暴力破解
  • 谷歌浏览器、Yandex浏览器使用体验分享
  • 光流法(optical flow)简介
  • 13800138000来电?手机管家:小心诈骗
  • Windows 10 离线安装 .NET Framework 3.5 的方法和技巧
  • WISP模式
  • waterdrop介绍
  • CreateThread()函数及_beginthreadex()函数
  • C++ ofstream和ifstream详细用法
  • 使用Latex制作分享,演讲,Presentation用的Slides,PPT——Beamer教程