1. SQL语句的执行过程——Statement直接执行的弊病:

1) SQL语句和编程语言一样,仅仅就会普通的文本字符串,首先数据库引擎无法识别这种文本字符串,而底层的CPU更不理解这些文本字符串(只懂二进制机器指令),因此SQL语句在执行之前肯定需要编译的;

2) SQL语句的执行过程:提交SQL语句 -> 数据库引擎对SQL语句进行编译得到数据库可执行的代码 -> 执行SQL代码;

3) 现在再来看Statement的执行机制:

i. Statement的execute系列方法直接将SQL语句作为参数传入并提交给数据库执行;

ii. 也就是说每提交一次都需要先经过编译然后再执行;

iii. 那么有一个最大的问题就是如果一条SQL语句需要再短时间内被反复执行,那么每次都需要经过编译这样不是效率非常非常低吗??

!!可能你会问哪有需要反复大量执行的相同语句呢?仔细一想可能是的,因此上面说的并不完全精确,精确地讲应该是反复执行一系列模型相似的语句,比如:

  1. 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次):

  1. public class Test {
  2. private String driver;
  3. private String url;
  4. private String user;
  5. private String pass;
  6. private void insertUseStatement() throws Exception {
  7. long start = System.currentTimeMillis();
  8. try ( // 过了try块会直接释放连接资源
  9. Connection conn = DriverManager.getConnection(url, user, pass);
  10. Statement stmt = conn.createStatement()
  11. ) {
  12. for (int i = 0; i < 100; i++) {
  13. stmt.executeUpdate("insert into student_table values(" + "null, '姓名" + i + "', 1)");
  14. }
  15. System.out.println("使用Statment耗时:" + (System.currentTimeMillis() - start));
  16. }
  17. }
  18. private void insertUsePreparedStatement() throws Exception {
  19. long start = System.currentTimeMillis();
  20. try ( // 因此需要在另一个方法中重新连接
  21. Connection conn = DriverManager.getConnection(url, user, pass);
  22. PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null, ?, 1)")
  23. ) {
  24. for (int i = 0; i < 100; i++) {
  25. pstmt.setString(1,  "姓名" + i);
  26. pstmt.executeUpdate();
  27. }
  28. System.out.println("使用PreparedStatement耗时:" + (System.currentTimeMillis() - start));
  29. }
  30. }
  31. public void init() throws Exception {
  32. Properties props = new Properties();
  33. props.load(new FileInputStream("mysql.ini"));
  34. driver = props.getProperty("driver");
  35. url = props.getProperty("url");
  36. user = props.getProperty("user");
  37. pass = props.getProperty("pass");
  38. insertUseStatement();
  39. insertUsePreparedStatement();
  40. }
  41. public static void main(String[] args) throws Exception {
  42. new Test().init();
  43. }
  44. }

!!可以看到预编译比直接提交少用很多时间;

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语句的更多相关文章

  1. JDBC连接MYSQL,批量执行SQL语句或在执行一个SQL语句之前执行一个SQL语句

    conn = MysqlJdbcUtils.getConnection(); Statement ps=conn.createStatement(); ps.addBatch("trunca ...

  2. PreparedStatement预编译的sql执行对象

    一.预编译,防sql注入 其中,设置参数值占位符索引从1开始:在由sql 连接对象创建 sql执行对象时候传入参数sql语句,在执行对象在执行方法时候就不用再传入sql语句: 数据库索引一般是从1开始 ...

  3. 用jdbc连接数据库并简单执行SQL语句

    一:版本一.这种存在一个问题就是每执行一次操作都会创建一次Connection链接和且释放一次链接 1:创建pojo对象(OR映射,一个pojo类对应一张数据库表)   package com.yin ...

  4. JDBC进阶之PreparedStatement执行SQL语句(MySQL)

    一.什么是PreparedStatement           参阅Java API文档,我们可以知道,PreparedStatement是Statement的子接口(如图所示),表示预编译的 SQ ...

  5. 【JDBC】预编译SQL与防注入式攻击

    在JDBC编程中,常用Statement.PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedS ...

  6. 加载执行预编译的Sql :prepareStatement

    1.获得连接:Connection con = null; con = DBUtil.getConnection(); 2.写sql语句:String sql=""; 3.用连接加 ...

  7. JDBC——Statement执行SQL语句的对象

    Statement该对象用于执行静态SQL语句并返回它产生的结果.表示所有的参数在生成SQL的时候都是拼接好的,容易产生SQL注入的问题 PreparedStatement对象是一个预编译的SQL语句 ...

  8. statement 对象执行sql语句

    statement对象执行sql语句    关于Statement.它是Java执行数据库操作的一个重要步骤,可以执行一些简单的SQL语句,从而完成对数据库的操作.它有两个子接口,分别是Prepare ...

  9. JAVA基础知识之JDBC——编程步骤及执行SQL

    JDBC编程步骤 下面以mysql数据库为例, 1.加载驱动 首先需要下载数据库的驱动jar文件,并且在eclipse包中加入到class path中去, 例如mysql的驱动文件 mysql-con ...

随机推荐

  1. uva-188-枚举

    题意:直接模拟 注意,w[i]不能是0 #include <string> #include<iostream> #include<map> #include< ...

  2. 省市区联动JS脚本

    省市区联动JS脚本 /* ***说明:省市区联动JS脚本 ***作者:Jerry Yuan  */ var province=[{id:0,name:'选择省'},{id:11,name:" ...

  3. yii表单输入元素

    InputElement http://www.yiichina.com/api/CFormInputElement CFormInputElement 可以代表以下基于type属性的表单输入类型: ...

  4. 转载:C++函数中new一块内存,作为返回值

    转载来自:http://blog.itpub.net/7728585/viewspace-2123621/ 今天遇到一个问题,C++编程时,函数中new一块内存,然后将申请内存的指针作为返回值.怎么d ...

  5. react-native react-navigation StackNavigator android导航栏 标题下居中

    navigationOptions:({ navigation }) => ({ , textAlign:'center' }}) 如上设置即可,如果有返回箭头,那么右边也要加一个 占位的或者设 ...

  6. vue eslint 代码自动格式化

    vue-cli 代码风格为 JavaScript Standard Style 代码检查规范严格,一不小心就无法运行,使用eslint的autoFixOnSave可以在保存代码的时候自动格式化代码 V ...

  7. 机器学习入门-数值特征-进行二值化变化 1.Binarizer(进行数据的二值化操作)

    函数说明: 1. Binarizer(threshold=0.9) 将数据进行二值化,threshold表示大于0.9的数据为1,小于0.9的数据为0 对于一些数值型的特征:存在0还有其他的一些数 二 ...

  8. vue 踩坑-事件修饰符

    (1).stop // 阻止事件继续传播 即阻止冒泡过程 (2).prevent //阻止默认事件发生 即event.preventdefault(): 实例: 阻止了a标签的默认刷新 (3).cap ...

  9. MySQL性能分析(转)

    第一步:检查系统的状态 通过操作系统的一些工具检查系统的状态,比如CPU.内存.交换.磁盘的利用率.IO.网络,根据经验或与系统正常时的状态相比对,有时系统表面上看起来看空闲,这也可能不是一个正常的状 ...

  10. C语言复习:内存模型1

    数据类型本质分析 数据类型概念 "类型"是对数据的抽象; 类型相同的数据有相同的表现形式/存储格式以及相关的操作; 程序中使用的所有数据都必定属于某一种数据类型; 数据类型本质思考 ...