Statement和PreparedStatement之间的区别

时间:2010-07-17 11:16:00

标签: java jdbc

准备好的声明是一个稍微强大的声明版本,并且应该始终至少与声明一样快速和容易处理。
准备好的声明可以参数化

大多数关系数据库分四步处理JDBC / SQL查询:

  1. 解析传入的SQL查询
  2. 编译SQL查询
  3. 规划/优化数据采集路径
  4. 执行优化查询/获取并返回数据
  5. 对于发送到数据库的每个SQL查询,语句将始终执行上述四个步骤。准备语句在上面的执行过程中预先执行步骤(1) - (3)。因此,在创建Prepared Statement时,会立即执行一些预优化。其效果是减少执行时数据库引擎的负载。

    现在我的问题是 - “使用预备声明还有其他优势吗?”

15 个答案:

答案 0 :(得分:181)

PreparedStatement

的优点
  • SQL语句的预编译和数据库端缓存可以提高整体执行速度,并能够在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对象的设置,例如: DateTimeTimestampBigDecimalInputStreamBlob)和ReaderClob)。在大多数类型中,您不能像在简单的toString()中那样“只”执行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();
    

答案 1 :(得分:43)

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

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

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

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

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

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

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

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

  7. 在Java中,智能地通过的setObject,setBoolean,setByte,的setDate,setDouble,setDouble,的setFloat,SETINT,setLong,setShort,时刻设定,的setTimestamp接受java对象作为参数类型 - 它转换成JDBC类型格式,可理解到DB(不只是toString()格式)。

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

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

  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,数据库管理/维护/逻辑调整以及专有权的使用数据库逻辑&amp;特征

答案 2 :(得分:36)

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

答案 3 :(得分:28)

PreparedStatement对Statement的一些好处是:

  1. PreparedStatement帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。
  2. PreparedStatement允许我们使用参数输入执行动态查询。
  3. PreparedStatement提供了不同类型的setter方法来设置查询的输入参数。
  4. PreparedStatement比Statement更快。当我们重用PreparedStatement或使用它的批处理方法执行多个查询时,它变得更加明显。
  5. PreparedStatement帮助我们用setter方法编写面向对象的代码,而使用Statement我们必须使用String Concatenation来创建查询。如果要设置多个参数,则使用字符串连接编写查询看起来非常难看并且容易出错。
  6. http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example

    了解有关SQL注入问题的更多信息

答案 4 :(得分:13)

没什么可补充的,

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

2 - 参数化查询是避免SQL注入的好方法,只能在PreparedStatement中使用。

答案 5 :(得分:10)

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

声明适用于DML的DDL和准备好的声明。

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

more differences

答案 6 :(得分:7)

无法在Statement中执行CLOB。

并且:(OraclePreparedStatement)ps

答案 7 :(得分:5)

准备好的语句会忽略

sql注入,因此准备语句中的安全性会增加

答案 8 :(得分:4)

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

答案 9 :(得分:4)

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

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

答案 10 :(得分:4)

准备或参数化查询的另一个特征:Reference taken from this article.

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

语句模板已准备好并发送到数据库系统,数据库系统对此模板进行解析,编译和优化,并在不执行的情况下进行存储。

某些参数如:where子句在模板创建后期间未传递应用程序,将这些参数发送到数据库系统和数据库系统使用SQL语句模板并按请求执行。

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

当数据数量增加且索引在那时频繁变化时,准备好的语句可能会失败,因为在这种情况下需要新的查询计划。

答案 11 :(得分:4)

mattjames

引用
  

在JDBC中使用Statement应该100%本地化以便使用   对于DDL(ALTER,CREATE,GRANT等),因为这些是唯一的陈述   无法接受BIND VARIABLES的类型。准备好的声明或   CallableStatements应该用于其他类型的语句   (DML,查询)。因为这些是接受bind的语句类型   变量

     

这是一个事实,一个规则,一个法律 - 使用准备好的声明无处不在。   使用STATEMENTS几乎没有。

答案 12 :(得分:3)

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

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

  1. 高效重复执行

  2. 预编译所以它更快

答案 13 :(得分:1)

不要混淆:只记得

  1. 语句用于静态查询,如DDL,即create,drop,alter和prepareStatement用于动态查询,即DML查询。
  2. 在Statement中,在预编译的prepareStatement查询中没有预编译查询,因为这样,prepareStatement是时间效率。
  3. prepareStatement在创建时接受参数,而Statement不接受参数。 例如,如果要创建表和插入元素,那么:: 使用Statement和Insert元素(动态)通过使用prepareStatement创建表(静态)。

答案 14 :(得分:0)

我遵循了这个问题的所有答案,以使用-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方法,您需要在重用Reusing a PreparedStatement

之前关闭该Statement。