Statement 和 PreparedStatement 的区别

Posted

技术标签:

【中文标题】Statement 和 PreparedStatement 的区别【英文标题】:Difference between Statement and PreparedStatement 【发布时间】:2010-07-17 11:16:00 【问题描述】:

Prepared Statement 是 Statement 的一个稍微强大的版本,并且应该始终至少与 Statement 一样快速和易于处理。 Prepared Statement可以被参数化

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

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

对于发送到数据库的每个 SQL 查询,语句将始终执行上述四个步骤。 Prepared Statement预先执行上述执行过程中的步骤(1)-(3)。因此,在创建 Prepared Statement 时,会立即执行一些预优化。其效果是减少执行时数据库引擎的负载。

现在我的问题是:

“使用 Prepared Statement 还有其他好处吗?”

【问题讨论】:

我认为最有效的方法是您的查询可以动态参数化 【参考方案1】:

PreparedStatement 的优点:

SQL 语句的预编译和 DB 端缓存可提高整体执行速度,并能够在 batches 中重用相同的 SQL 语句。

通过引号和其他特殊字符的内置转义自动防止SQL injection attacks。请注意,这要求您使用任何 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 对象的设置,例如DateTimeTimestampBigDecimalInputStream (Blob) 和 Reader (Clob)。在大多数这些类型中,您不能像在简单的Statement 中那样“只”执行toString()。您甚至可以将其全部重构为在循环中使用 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();

【讨论】:

描述性和解释性的文本,加上参考和示例,是一个很好的答案。 +1 @R.D.这可能是真的,因为准备好的语句需要两次往返数据库:第一次准备,第二次执行。但是,我会测试它。我认为该计划仍将缓存在数据库服务器中以获取Statement,但它可能值得一试。 我不能肯定地说Java,但一般来说,准备好的语句预先形成“引号和其他特殊字符的内置转义”;相反,它执行可执行 SQL 和数据的分离,在 SQL 转换为查询计划后将参数作为单独的信息包发送到 DBMS。 @BalusC - 谢谢你的详细解释。【参考方案2】:

    它们是预编译的(一次),因此对于重复执行动态 SQL(参数更改)更快

    数据库语句缓存提升数据库执行性能

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

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

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

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

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

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

    在 java 中,通过 setObject、setBoolean、setByte、setDate、setDouble、setDouble、setFloat、setInt、setLong、setShort、setTime、setTimestamp 智能地接受 java 对象作为参数类型 - 它转换为可理解的 JDBC 类型格式到 DB(不仅仅是 toString() 格式)。

    在 java 中,通过 setArray 方法接受 SQL 数组作为参数类型

    在 java 中,分别通过 setClob/setNClob、setBlob、setBinaryStream、setCharacterStream/setAsciiStream/setNCharacterStream 方法接受 CLOB、BLOB、OutputStreams 和 Readers 作为参数“feeds”

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

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

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

【讨论】:

当它们都只是接口时,所有这些奇迹怎么可能?!?! “奇迹”是通过标准工厂方法实现的,这些方法返回接口的(供应商特定的)实现:Connection.createStatementConnection.prepareStatement。这种设计迫使您针对接口工作,因此您无需了解具体的实现类,并避免与此类实现类进行不必要的紧耦合。 Java jdbc 文档和 Java 文档中的所有示例都进行了解释。 :) 您的“根据经验”部分毫无意义,不是相反吗? ?【参考方案3】:

PreparedStatement 在防止SQL injection attacks 方面是一个非常好的防御(但并非万无一失)。绑定参数值是防止"little Bobby Tables" 进行不受欢迎的访问的好方法。

【讨论】:

那么如何通过准备好的语句执行 SQL 注入? Michael,作为参数传递给准备好的语句的变量将被 JDBC 驱动程序自动转义。 你能举一个例子说明 SQL 注入攻击是如何作用于准备好的语句的吗?您是否假设数据库代码中存在错误? 是的,但它远远超出了“相当愚蠢”的范围。真是令人发指的愚蠢。没有一点知识的人会这样做。 此外,许多数据库供应商不支持在某些地方参数化列名(想想ORDER BY)和/或数字常量(想想LIMITOFFSET 和其他分页解决方案),所以这些可能会受到 SQL 注入的攻击,即使尽可能使用准备好的语句和参数化。【参考方案4】:

PreparedStatement 相对于 Statement 的一些好处是:

    PreparedStatement 帮助我们防止 SQL 注入攻击,因为它会自动转义特殊字符。 PreparedStatement 允许我们使用参数输入执行动态查询。 PreparedStatement 提供了不同类型的 setter 方法来设置查询的输入参数。 PreparedStatement 比 Statement 快。当我们重用 PreparedStatement 或使用它的批处理方法执行多个查询时,它会变得更加明显。 PreparedStatement 帮助我们使用 setter 方法编写面向对象的代码,而使用 Statement 我们必须使用字符串连接来创建查询。如果要设置多个参数,使用字符串连接编写 Query 看起来很丑陋且容易出错。

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

【讨论】:

我读了你的文章,非常好。我现在的问题是为什么有人会使用语句?!即使是静态查询?! 我一直使用 PreparedStatement,我不知道 Statement 可能有更多好处的具体场景。【参考方案5】:

没什么可补充的,

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

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

【讨论】:

【参考方案6】:

语句是静态的,准备好的语句是动态的。

Statement 适用于 DDL,Prepared Statement 适用于 DML。

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

more differences(已存档)

【讨论】:

【参考方案7】:

不能在语句中执行 CLOB。

还有:(OraclePreparedStatement)ps

【讨论】:

【参考方案8】:

引用mattjames

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

这是一个事实、一个规则、一个法律——在任何地方都使用准备好的语句。 几乎不会在任何地方使用 STATEMENTS。

【讨论】:

【参考方案9】:

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

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

【讨论】:

【参考方案10】:

准备好的语句忽略sql注入,因此准备好的语句增加了安全性

【讨论】:

【参考方案11】: 更容易阅读 您可以轻松地将查询字符串设为常量

【讨论】:

【参考方案12】:

Prepared 或 Parameterized Query 的另一个特点:Reference taken from this article.

该语句是数据库系统的特点之一,同一条SQL语句可以高效地重复执行。准备好的语句是模板的一种,由不同参数的应用程序使用。

准备好语句模板并发送给数据库系统,数据库系统对这个模板进行解析、编译和优化,不执行就存储起来。

有些参数,例如,where子句在模板创建以后应用时没有传递,将这些参数发送到数据库系统,数据库系统使用SQL语句的模板,按请求执行。

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

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

【讨论】:

【参考方案13】:

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

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

    高效重复执行

    它是预编译的,所以速度更快

【讨论】:

【参考方案14】:

不要混淆:记住

    Statement 用于 DDL 等静态查询,即 create、drop、alter,prepareStatement 用于动态查询,即 DML 查询。 在 Statement 中,查询未预编译,而在 prepareStatement 中预编译查询,因为这种 prepareStatement 非常省时。 prepareStatement 在创建时采用参数,而 Statement 不采用参数。 例如,如果您想创建表格并插入元素,那么 :: 使用 Statement 创建表(静态)并使用 prepareStatement 创建插入元素(动态)。

【讨论】:

prepareStatement 在创建时带参数,而 Statement 不带参数。?【参考方案15】:

我按照这个问题的所有答案将使用 - 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 是不可能的。

另外,当你使用内置批处理工具时,你不得不担心只关闭一个语句,但是使用这种列表方法,你需要在重用之前关闭语句,Reusing a PreparedStatement

【讨论】:

以上是关于Statement 和 PreparedStatement 的区别的主要内容,如果未能解决你的问题,请参考以下文章

JDBC预编译statement(preparedstatement)和statement的比较execute与executeUpdate的区别

Statement和ResultSet

JDBC学习笔记——Statement和ResultSet

Java_JDBC使用Statement和PreparedStatement

Java中PreparedStatement和Statement的用法区别

PreparedStatement和Statement