声明和PreparedStatement之间的区别


222

“ Prepared Statement”是Statement的稍微强大一点的版本,并且应始终至少与Statement一样快捷且易于处理。
准备好的语句可能已参数化

大多数关系数据库通过四个步骤来处理JDBC / SQL查询:

  1. 解析传入的SQL查询
  2. 编译SQL查询
  3. 规划/优化数据采集路径
  4. 执行优化的查询/获取并返回数据

对于发送到数据库的每个SQL查询,一个Statement将始终执行上述四个步骤。一条Prepared Statement预执行上述执行过程中的步骤(1)-(3)。因此,在创建Prepared Statement时,会立即执行一些预优化。这样做的目的是减轻执行时数据库引擎的负担。

现在我的问题是-“使用预处理语句还有其他好处吗?”


12
根据我的说法,最有效的方法是可以动态设置查询的参数
Hussain Akhtar Wahid'Ghouri'2012年

Answers:


198

优点PreparedStatement

  • SQL语句的预编译和数据库侧缓存可提高整体执行速度,并能够批量重用同一SQL语句。

  • 通过内置对引号和其他特殊字符的转义,自动防止SQL注入 攻击。请注意,这要求您使用任何PreparedStatement setXxx()方法来设置值

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    preparedStatement.setString(1, person.getName());
    preparedStatement.setString(2, person.getEmail());
    preparedStatement.setTimestamp(3, new Timestamp(person.getBirthdate().getTime()));
    preparedStatement.setBinaryStream(4, person.getPhoto());
    preparedStatement.executeUpdate();

    因此不要通过字符串连接来内联SQL字符串中的值。

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • 简化的SQL字符串非标准的Java对象的设置,例如DateTimeTimestampBigDecimalInputStreamBlob)和ReaderClob)。在大多数这些类型上,您不能toString()像在简单类型中那样“仅仅”执行a Statement。您甚至可以将其全部重构为PreparedStatement#setObject()在循环内部使用,如下面的实用程序方法所示:

    public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            preparedStatement.setObject(i + 1, values[i]);
        }
    }

    可以如下使用:

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    setValues(preparedStatement, person.getName(), person.getEmail(), new Timestamp(person.getBirthdate().getTime()), person.getPhoto());
    preparedStatement.executeUpdate();

4
具有描述性和解释性的文字,加上参考资料和示例,是一个很好的答案。+1
XenoRo

1
@RD这可能是正确的,因为一条准备好的语句需要两次到数据库的往返:第一个准备,第二个执行。但是,我会对其进行测试。我认为该计划仍将被缓存在数据库服务器中Statement,但是可能值得测试。
布兰登

2
我不能肯定地说与Java,但总体一份声明中确实没有预制棒“内置的报价和其他特殊字符转义”; 相反,它执行可执行SQL和数据的分离,并在将SQL转换为查询计划后将参数作为单独的信息包发送到DBMS。
IMSoP 16/09/13

@BalusC-感谢您的详细说明。
CodeBee..18 / 12/24

49
  1. 它们是预编译的(一次),因此可以更快地重复执行动态SQL(参数更改)。

  2. 数据库语句缓存可提高数据库执行性能

    数据库存储先前执行的语句的执行计划的缓存。这使数据库引擎可以将计划重用于先前已执行的语句。由于PreparedStatement使用参数,因此每次执行时它都会显示为相同的SQL,因此数据库可以重用以前的访问计划,从而减少了处理。语句将参数“内联”到SQL字符串中,因此对于数据库而言不会显示为相同的SQL,从而阻止了缓存的使用。

  3. 二进制通信协议意味着更少的带宽和更快的对数据库服务器的通信调用

    准备好的语句通常通过非SQL二进制协议执行。这意味着数据包中的数据较少,因此与服务器的通信速度更快。根据经验,网络操作比磁盘操作要慢一个数量级,而磁盘操作比内存中的CPU操作要慢一个数量级。因此,通过网络发送的数据量的任何减少都会对整体性能产生良好的影响。

  4. 它们通过转义提供的所有参数值的文本来防止SQL注入。

  5. 它们提供了更强的查询代码和参数值之间的分隔(与串联的SQL字符串相比),提高了可读性,并帮助代码维护人员快速了解查询的输入和输出。

  6. 在Java中,可以调用getMetadata()和getParameterMetadata()分别反映结果集字段和参数字段

  7. 在Java中,通过setObject,setBoolean,setByte,setDate,setDouble,setDouble,setFloat,setInt,setLong,setShort,setTime,setTimestamp来智能地接受Java对象作为参数类型-它将转换为JDBC类型的格式,该格式对于DB而言是正确的(而不仅仅是toString) ()格式)。

  8. 在Java中,通过setArray方法接受SQL ARRAY作为参数类型

  9. 在Java中,分别通过setClob / setNClob,setBlob,setBinaryStream,setCharacterStream / setAsciiStream / setNCharacterStream方法将CLOB,BLOB,OutputStreams和Readers接受为参数“提要”

  10. 在Java中,允许通过setURL,setRowId,setSQLXML和setNull方法为SQL DATALINK,SQL ROWID,SQL XML和NULL设置特定于DB的值

  11. 在Java中,从Statement继承所有方法。它继承了addBatch方法,并另外允许通过addBatch方法添加一组参数值以匹配该批处理的SQL命令。

  12. 在Java中,特殊类型的PreparedStatement(子类CallableStatement)允许执行存储过程-支持高性能,封装,过程编程和SQL,数据库管理/逻辑维护/逻辑调整,以及专有DB逻辑和功能的使用


当它们都是接口时,所有这些奇迹怎么可能?!?!
拉斐尔

1
“奇迹”通过标准的工厂方法得以实现,这些方法返回(特定于供应商的)接口实现:Connection.createStatementConnection.prepareStatement。这种设计迫使您针对接口进行工作,因此您无需了解特定的实现类,并避免与此类实现类不必要的紧密耦合。所有这些都通过Java jdbc文档和Java文档中的示例进行了说明。:)
Glen Best

您的“经验法则”部分毫无意义,不是吗?
bhathiya-perera,

38

PreparedStatement在防止SQL注入攻击方面是非常好的防御(但不是万无一失)。绑定参数值是防止“小鲍比表”进行不必要访问的好方法。


6
那么如何通过准备好的语句执行SQL注入?
Michael Borgwardt 2010年

2
Michael,作为参数传递给准备好的语句的变量将由JDBC驱动程序自动转义。
CodeBee..2010年

3
您能否举一个示例,说明如何对预备语句执行SQL注入攻击?您是否假设数据库代码中有错误?
Peter Recore

2
是的,但是远远超出了“相当愚蠢”的范围。真是愚蠢。没有一点知识的人不会这样做。
duffymo

2
此外,许多数据库供应商不支持参数化的列名(认为ORDER BY)在某些地方(想和/或数字常量LIMITOFFSET以及其他分页解决方案),所以这些可以通过SQL注入,即使预处理语句和参数被用于任何被攻击可能。
dnet 2012年

31

与语句相比,PreparedStatement的一些好处是:

  1. PreparedStatement帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。
  2. PreparedStatement允许我们使用参数输入执行动态查询。
  3. PreparedStatement提供了不同类型的setter方法,以设置查询的输入参数。
  4. PreparedStatement比Statement快。当我们重用PreparedStatement或将其批处理方法用于执行多个查询时,它会变得更加可见。
  5. PreparedStatement帮助我们使用setter方法编写面向对象的代码,而使用Statement,则必须使用String Concatenation创建查询。如果要设置多个参数,则使用String串联编写Query看起来非常难看且容易出错。

http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example上了解有关SQL注入问题的更多信息


我读了你的文章,真的很好。我现在的问题是,为什么有人会使用Statement ?!即使是静态查询?!
pedram bashiri

我一直使用PreparedStatement,但我不知道Statement可能会带来更多好处的任何特定情况。
Pankaj

13

没有什么要补充的,

1-如果要循环执行查询(超过1次),由于提到的优化,准备好的语句可能会更快。

2-参数化查询是避免SQL注入的好方法。参数化查询仅在PreparedStatement中可用。


10

陈述是静态的,准备的陈述是动态的。

声明适用于DDL,适用于DML的已编制语句。

语句较慢,而准备好的语句较快。

更多差异(存档)


7

无法在语句中执行CLOB。

并且:(OraclePreparedStatement)ps


7

正如mattjames所引用

JDBC中对Statement的使用应100%本地化以用于DDL(ALTER,CREATE,GRANT等),因为这些是唯一不能接受BIND VARIABLES的语句类型。对其他所有类型的语句(DML,查询),都应使用PreparedStatement或CallableStatements。因为这些是接受绑定变量的语句类型。

这是事实,规则,法律-随时随地使用准备好的陈述。在几乎没有地方使用STATEMENTS。


5

sql注入被预处理语句忽略,因此增加了预处理语句的安全性


4
  • 更容易阅读
  • 您可以轻松地使查询字符串成为常量

4

语句将用于执行静态SQL语句,并且不能接受输入参数。

PreparedStatement将用于多次动态执行SQL语句。它将接受输入参数。


4

预准备或参数化查询的另一个特征:参考来自本文。

该语句是数据库系统的功能之一,其中相同的SQL语句可以高效重复执行。准备的语句是模板的一种,供具有不同参数的应用程序使用。

语句模板已准备好并发送到数据库系统,数据库系统对此模板进行解析,编译和优化,然后存储而不执行它。

某些参数(例如,where子句未在模板创建以后的应用程序期间传递)中的一些参数,将这些参数发送到数据库系统,并且数据库系统使用SQL语句的模板并根据请求执行。

对于SQL注入,准备好的语句非常有用,因为应用程序可以使用不同的技术和协议准备参数。

当数据数量在增加并且索引频繁更改时,Prepared Statements可能会失败,因为在这种情况下需要新的查询计划。


3

Statement 接口执行不带参数的静态SQL语句

PreparedStatement 接口(扩展Statement)执行带/不带参数的预编译SQL语句

  1. 高效重复执行

  2. 它是预编译的,因此速度更快


2

不要感到困惑:只需记住

  1. 语句用于DDL之类的静态查询,即create,drop,alter,prepareStatement用于动态查询,即DML查询。
  2. 在Statement中,不对查询进行预编译,而在prepareStatement中则对查询进行预编译,因为prepareStatement是省时的。
  3. 在创建时prepareStatement接受参数,而Statement不接受参数。例如,如果要创建表并插入元素,则::使用Statement创建表(静态),并通过prepareStatement创建插入元素(动态)。

1
prepareStatement在创建时接受参数,而Statement不接受参数。

1

我遵循了这个问题的所有答案,以使用- Statement(但使用SQL注入)将工作正常的旧代码更改为使用PreparedStatement慢得多的代码的解决方案,因为对Statement.addBatch(String sql)&的语义理解不足PreparedStatement.addBatch()

所以我在这里列出我的情况,以便其他人不会犯同样的错误。

我的情况是

Statement statement = connection.createStatement();

for (Object object : objectList) {
    //Create a query which would be different for each object 
    // Add this query to statement for batch using - statement.addBatch(query);
}
statement.executeBatch();

因此,在上面的代码中,我有成千上万个不同的查询,所有查询都添加到了同一条语句中,并且此代码的​​运行速度更快,因为未缓存的语句是好的,而且此代码很少在应用程序中执行。

现在要修复SQL注入,我将此代码更改为,

List<PreparedStatement> pStatements = new ArrayList<>();    
for (Object object : objectList) {
    //Create a query which would be different for each object 
    PreparedStatement pStatement =connection.prepareStatement(query);
    // This query can't be added to batch because its a different query so I used list. 
    //Set parameter to pStatement using object 
    pStatements.add(pStatement);
}// Object loop
// In place of statement.executeBatch(); , I had to loop around the list & execute each update separately          
for (PreparedStatement ps : pStatements) {
    ps.executeUpdate();
}

因此,您看到了,我开始创建数千个PreparedStatement对象,然后最终无法利用批处理,因为我的方案要求- 有成千上万的UPDATE或INSERT查询,而所有这些查询恰好都不同。

修复SQL注入是必不可少的,而不会降低性能,并且我认为使用 PreparedStatement在这种情况下。

同样,当您使用内置批处理工具时,您不必担心仅关闭一个Statement,但是使用此List方法,您需要在重用之前关闭该语句,然后重用PreparedStatement

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.