[疯狂Java]JDBC:PreparedStatement预编译执行SQL语句
1. SQL语句的执行过程——Statement直接执行的弊病:
1) SQL语句和编程语言一样,仅仅就会普通的文本字符串,首先数据库引擎无法识别这种文本字符串,而底层的CPU更不理解这些文本字符串(只懂二进制机器指令),因此SQL语句在执行之前肯定需要编译的;
2) SQL语句的执行过程:提交SQL语句 -> 数据库引擎对SQL语句进行编译得到数据库可执行的代码 -> 执行SQL代码;
3) 现在再来看Statement的执行机制:
i. Statement的execute系列方法直接将SQL语句作为参数传入并提交给数据库执行;
ii. 也就是说每提交一次都需要先经过编译然后再执行;
iii. 那么有一个最大的问题就是如果一条SQL语句需要再短时间内被反复执行,那么每次都需要经过编译这样不是效率非常非常低吗??
!!可能你会问哪有需要反复大量执行的相同语句呢?仔细一想可能是的,因此上面说的并不完全精确,精确地讲应该是反复执行一系列模型相似的语句,比如:
- insert into table1 values(1, "Peter");
!你每次执行时只是values中的值不同,但是总体的语句还是insert into语句,那么你每次提交都需要编译岂不是会把大把时间浪费在编译上面了,非常不值;
2. PreparedStatement的预编译机制——类似于Properties配置文件:
1) 通过Connection(conn)还可以得到另一种SQL语句对象,即PreparedStatement,该方法就是:PreparedStatement Connection.prepareStatement(String sql);
2) 注意细节:这里就不是create了,而是准备一个SQL语句句柄,精确地讲是一个PreparedStatement语句句柄,并且创建该句柄时直接传入了SQL语句;
3) 预编译机制:
i. 调用prepareStatement时会直接将该SQL语句提交给数据库进行编译,得到的PreparedStatement句柄其实是一个预编译好的SQL语句;
ii. 之后调用PreparedStatement的execute方法(其execute系列方法都是无参的),就直接将该预编译的语句提交给数据库直接运行而不需要再编译一次了;
iii. 因此这种方法只需要编译一次就够了,后面就是直接提交执行无需再编译,因此效率最高;
4) 而预编译语句最大的特点就是支持占位符(支持的占位符就是?,代表任意长度的字符串),比如:insert into table1 values(null, ?, ?);
!!也就是说可以用带占位符的SQL语句来创建预编译SQL句柄:PreparedStatement pstmt = conn.prepareStatement("insert into table1 values(null, ?, ?)");
!!这样的语句也能通过,也可以成功编译,并且可以再后期决定这些占位符具体的值,即使改变这些值后依然不需要编译而直接提交运行;
5) 设定占位符具体的值:
i. 可以使用PreparedStatement的setXxx方法设定预编译语句中占位符的值;
ii. 其原型是这个模式的:void PreparedStatement setXxx(int parameterIndex, Xxx x);
iii. Xxx几乎涵盖了所有Java基础类型(String、int、double、Date等等);
iv. parameterIndex代表语句中第几个占位符(从1开始),而x就是具体设定的值;
6) 设置好占位符的值之后无需编译可以直接提交执行;
!!这种机制其实是跟Properties配置文件完全一样,修改值后无需编译即可运行!
7) 直接提交执行:
i. 使用PreparedStatement的execute系列方法即可,和Statement的execute系列方法相对应,只不过无需SQL语句参数了,因为已经存在预编译的SQL语句了,因此都是无参的,就表示直接提交执行;
ii. 方法:
a. ResultSet PreparedStatement.executeQuery();
b. int PreparedStatement.executeUpdate();
c. boolean PreparedStatement.execute();
!!返回值的意义和Statement的完全相同;
3. 比较直接提交和预编译运行的执行效率(各执行100次):
- public class Test {
- private String driver;
- private String url;
- private String user;
- private String pass;
- private void insertUseStatement() throws Exception {
- long start = System.currentTimeMillis();
- try ( // 过了try块会直接释放连接资源
- Connection conn = DriverManager.getConnection(url, user, pass);
- Statement stmt = conn.createStatement()
- ) {
- for (int i = 0; i < 100; i++) {
- stmt.executeUpdate("insert into student_table values(" + "null, '姓名" + i + "', 1)");
- }
- System.out.println("使用Statment耗时:" + (System.currentTimeMillis() - start));
- }
- }
- private void insertUsePreparedStatement() throws Exception {
- long start = System.currentTimeMillis();
- try ( // 因此需要在另一个方法中重新连接
- Connection conn = DriverManager.getConnection(url, user, pass);
- PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null, ?, 1)")
- ) {
- for (int i = 0; i < 100; i++) {
- pstmt.setString(1, "姓名" + i);
- pstmt.executeUpdate();
- }
- System.out.println("使用PreparedStatement耗时:" + (System.currentTimeMillis() - start));
- }
- }
- public void init() throws Exception {
- Properties props = new Properties();
- props.load(new FileInputStream("mysql.ini"));
- driver = props.getProperty("driver");
- url = props.getProperty("url");
- user = props.getProperty("user");
- pass = props.getProperty("pass");
- insertUseStatement();
- insertUsePreparedStatement();
- }
- public static void main(String[] args) throws Exception {
- new Test().init();
- }
- }
!!可以看到预编译比直接提交少用很多时间;
4. 预编译SQL的安全性能:
1) 首先最明显的一点就是Statement不支持占位符,因此SQL语句中包含可变内容时必须要进行字符串拼接,而字符串拼接不仅加大了编程的难度,降低了代码的可读性,而且非常容易发生因拼接错误而导致地极难发现的bug,因此从这点来看PreparedStatement更加安全;
2) 其次是字符串拼接容易埋下SQL注入的漏洞:
i. SQL注入是指黑客在应用程序端恶意地往查询信息中填写SQL语句实现入侵(因为客户端输入的要查询的信息往往都是一些正常信息,例如姓名、电话、学号等,没人会无聊地往里面输入代码之类的东西);
ii. 一个典型的例子:比如SQL语句的目的是select * from member_table where name = input_name and pass = input_pass; input_name和input_pass是用户在客户端输入框中输入的账号名和登陆密码,如果该查询语句能查询到该用户(即返回记录不为空)就表示该用户登陆成功;
如果用预编译占位符来表示该语句就是:select * from member_table where name = ? and pass = ?; // 然后后期用input_name和input_pass来填补占位符,这没什么问题
但如果用Statement拼接的方式来写该语句就是:"select * from member_table where name = '" + input_name + "' and pass = '" + input_pass +"'";,而此时如果黑客在任意一个输入框(账户名或者密码)中填入'or true or'(就比如账户名输入框吧),那么得到的结果就是:
select * from member_table where name = '' or true or '' and pass = '';
!!也就是说最后的逻辑表达式变成了name = ''、true、'' and pass = ''三者通过or连接在了一起,因为or了一个true因此整个where表达式的结果都是true,因此必然会select处记录,因此即使这样也可以正常登陆!!这就被成功入侵了
iii. 这最主要是由于不带占位符的拼接必须要用单引号'来包裹SQL字符串,而占位符的填写无需单引号,JDBC会自动将Java变量转换成纯字符串然后再自动加上SQL单引号填入占位符中,即使填入的变量是String str = "'Lala'",那么JDBC也会将其中的单引号' '转化成纯字符单引号处理,而不会被当做SQL的特殊字符单引号'来处理,因为在SQL中单引号'是字符串常量符号!
5. 占位符使用问题注意:
1) 占位符只能占位SQL语句中的普通值,决不能占位表名、列名、SQL关键字(select、insert等);
2) 原因很简单,以为PreparedStatement的SQL语句是要预编译的,如果关键字、列名、表名等被占位那就直接代表该SQL语句语法错误而无法编译,会直接抛出异常,因此只有不影响编译的部分可用占位符占位!!
[疯狂Java]JDBC:PreparedStatement预编译执行SQL语句的更多相关文章
- JDBC连接MYSQL,批量执行SQL语句或在执行一个SQL语句之前执行一个SQL语句
conn = MysqlJdbcUtils.getConnection(); Statement ps=conn.createStatement(); ps.addBatch("trunca ...
- PreparedStatement预编译的sql执行对象
一.预编译,防sql注入 其中,设置参数值占位符索引从1开始:在由sql 连接对象创建 sql执行对象时候传入参数sql语句,在执行对象在执行方法时候就不用再传入sql语句: 数据库索引一般是从1开始 ...
- 用jdbc连接数据库并简单执行SQL语句
一:版本一.这种存在一个问题就是每执行一次操作都会创建一次Connection链接和且释放一次链接 1:创建pojo对象(OR映射,一个pojo类对应一张数据库表) package com.yin ...
- JDBC进阶之PreparedStatement执行SQL语句(MySQL)
一.什么是PreparedStatement 参阅Java API文档,我们可以知道,PreparedStatement是Statement的子接口(如图所示),表示预编译的 SQ ...
- 【JDBC】预编译SQL与防注入式攻击
在JDBC编程中,常用Statement.PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedS ...
- 加载执行预编译的Sql :prepareStatement
1.获得连接:Connection con = null; con = DBUtil.getConnection(); 2.写sql语句:String sql=""; 3.用连接加 ...
- JDBC——Statement执行SQL语句的对象
Statement该对象用于执行静态SQL语句并返回它产生的结果.表示所有的参数在生成SQL的时候都是拼接好的,容易产生SQL注入的问题 PreparedStatement对象是一个预编译的SQL语句 ...
- statement 对象执行sql语句
statement对象执行sql语句 关于Statement.它是Java执行数据库操作的一个重要步骤,可以执行一些简单的SQL语句,从而完成对数据库的操作.它有两个子接口,分别是Prepare ...
- JAVA基础知识之JDBC——编程步骤及执行SQL
JDBC编程步骤 下面以mysql数据库为例, 1.加载驱动 首先需要下载数据库的驱动jar文件,并且在eclipse包中加入到class path中去, 例如mysql的驱动文件 mysql-con ...
随机推荐
- spring mvc 文件上传 ajax 异步上传
异常代码: 1.the request doesn't contain a multipart/form-data or multipart/mixed stream, content type he ...
- hadoop/etc/hadoop 下没有mapred-site.xml,只有mapred.xml.template
默认情况下,/usr/local/hadoop/etc/hadoop/文件夹下有mapred.xml.template文件,我们要复制该文件,并命名为mapred.xml,该文件用于指定MapRedu ...
- ROS 进阶学习笔记(13) - Combine Subscriber and Publisher in Python, ROS
Combine Subscriber and Publisher in Python, ROS This article will describe an example of Combining S ...
- ios自动监测更新
http://blog.csdn.net/davidsph/article/details/8931718
- kvm云主机使用宿主机usb设备
有些时候KVM客户机还是要使用USB设备,比如USB密钥等 KVM命令行参数 -usb 打开usb驱动程序,启动客户机usb支持-usbdevice devname 为客户机增加usb设备,devna ...
- C++ Sleep() sleep()
简介: 函数名: sleep 功 能: 执行挂起一段时间 用 法: unsigned sleep(unsigned seconds); 在VC中使用带上头文件 #include <windows ...
- jar 问题 : java.io.IOException: invalid header field
通过本文, 我们明白了什么是 jar的清单文件 MANIFEST.MF, 简单示例: E:\ws\Test\WEB-INF\classes>jar cvfm testCL.jar ListTes ...
- spark historyserver 页面反应很慢 jvm堆调参
我们的spark historyserver 最近页面打开很慢 jstat -gcutil pid 1000 发现full gc 相当严重 查看堆大小,发现默认堆1G,打算修改到4G jps -lvm ...
- idea 关闭自动保存,未保存星号提醒, springboot + freemarker 热部署
1,自动保存 File > setting 去掉下图勾选 2,未保存文件星号提示 File > Settings 3,spring boot 项目 热部署 3.1,pom文件添加依赖 &l ...
- 笨方法学python之读写文件、open函数的用法
一.python读写文件相关知识点 close:关闭文件 read:读取文件的内容//你可以把结果赋给一个变量 readline:只读取文件中的一行 truncate 美 /trʌŋ'ket/ :清空 ...