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

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


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

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

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

评论

根据我的说法,最有效的方法是您的查询可以动态参数化

#1 楼

PreparedStatement的优点:SQL语句的预编译和DB端缓存可提高整体执行速度,并能够批量重用同一SQL语句。

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

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对象,例如setXxx()DateTimeTimestampBigDecimalInputStream)和BlobReader)。在大多数类型中,您不能像在简单的Clob中那样“仅”执行toString()。您甚至可以将其重构为在循环内使用Statement,如下面的实用程序方法所示:

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

– XenoRo
2012年11月5日12:58

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

–布兰登
2014年10月15日15:59

对于Java,我不能肯定地说,但是通常来说,准备好的语句不会执行“内置的引号和其他特殊字符的转义”;相反,它执行可执行SQL和数据的分离,并在将SQL转换为查询计划后将参数作为单独的信息包发送到DBMS。

–IMSoP
16-09-13在15:02

@BalusC-感谢您的详细说明。

– CodeBee ..
18/12/24在11:52

#2 楼


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

数据库语句缓存提高了数据库的执行性能。

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


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

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

通过对提供的所有参数值进行转义,可以防止SQL注入。
它们提供更强的查询代码和参数值之间的分隔(与串联的SQL字符串相比),提高了可读性并帮助代码维护者快速了解查询的输入和输出。
在Java中,可以调用getMetadata()和getParameterMetadata()分别反映结果集字段和参数字段
在Java中,通过setObject,setBoolean,setByte,setDate,setDouble,setDouble,setFloat,setInt,setLong,setShort,setTime, setTimestamp-它转换为DB可以理解的JDBC类型格式(而不仅仅是toString()格式)。
在Java中,通过setArray方法接受SQL ARRAY作为参数类型。在Java中,分别通过setClob / setNClob,setBlob,setBinaryStream,setCharacterStream / setAsciiStream / setNCharacterStream方法将CLOB,BLOB,OutputStreams和Readers作为参数“提要”
在Java中,允许通过setURL,setRowId,setSQLXML和setNull方法为SQL DATALINK,SQL ROWID,SQL XML和NULL设置特定于DB的值。在Java中,从Statement继承所有方法。它继承了addBatch方法,并另外允许通过addBatch方法添加一组参数值以匹配该批处理的SQL命令。
在Java中,一种特殊类型的PreparedStatement(子类CallableStatement)允许存储过程执行以下操作:被执行-支持高性能,封装,过程编程和SQL,数据库管理/维护/逻辑调整,以及专有DB逻辑和功能的使用


评论


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

–拉斐尔
2014年4月5日在11:52

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

– Glen Best
2014年4月8日在11:36

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

–bhathiya-perera
19年11月12日在9:53

#3 楼

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

评论


那么,如何通过准备好的语句执行SQL注入?

–迈克尔·伯格沃德(Michael Borgwardt)
2010年7月17日在12:17

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

– CodeBee ..
2010年7月17日在19:09



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

– Peter Recore
2010年7月17日在22:50

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

–duffymo
2012年11月3日21:25

同样,许多数据库供应商也不支持在某些位置参数化列名(想想ORDER BY)和/或数字常量(想想LIMIT,OFFSET和其他分页解决方案),因此,即使在Prepared Statements和尽可能使用参数化。

– dnet
2012年11月5日13:37

#4 楼

与语句相比,PreparedStatement的一些优点是:


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

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

评论


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

–pedram bashiri
18年5月15日在18:21

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

–潘卡吉
18年6月5日在10:18

#5 楼

没什么可添加的,

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

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

#6 楼

语句是静态的,而预处理的语句是动态的。
语句适用于DDL,而预处理的语句适用于DML。
语句较慢,而预处理的语句较快。
差异更大(已存档)

#7 楼



并且:(OraclePreparedStatement)ps

#8 楼

正如mattjames引用的那样:JDBC中对Statement的使用应100%本地化以用于DDL(ALTER,CREATE,GRANT等),因为这些是仅声明
类型不能接受BIND VARIABLES。 PreparedStatements或
CallableStatements应该用于其他所有类型的语句
(DML,查询)。因为这些是接受bind
变量的语句类型。

这是事实,规则,法律-随时随地使用准备好的语句。
几乎在任何地方都使用STATEMENTS。


#9 楼

预处理语句忽略sql注入,因此提高了预处理语句的安全性

#10 楼

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

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

#11 楼


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


#12 楼

预处理查询或参数化查询的另一个特征:参考资料来自本文。

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

准备语句模板并将其发送到数据库系统,数据库系统对此模板进行解析,编译和优化并存储

一些参数,例如在模板创建以后的应用程序中未传递where子句的某些参数,将这些参数发送到数据库系统和数据库系统使用SQL语句的模板,并根据请求执行。 br />
由于SQL应用程序可以使用不同的技术和协议来准备参数,所以准备好的语句对于SQL注入非常有用。

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

#13 楼

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

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


高效重复执行
已预编译,因此速度更快


#14 楼

不要感到困惑:只需记住



Statement用于DDL之类的静态查询,即create,drop,alter和prepareStatement用于动态查询(即DML查询)。
在Statement中,在prepareStatement中查询不会被预编译,因为此prepareStatement是省时的,因此它是预编译的。
prepareStatement在创建时接受参数,而Statement不接受参数。
例如,如果要创建表并插入元素,然后:: ::
使用Statement创建表(静态),并使用prepareStatement创建插入元素(动态)。


评论


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

–user4768611
2015年6月1日,11:17

#15 楼

我遵循了这个问题的所有答案,因为对StatementPreparedStatement的语义了解不多,所以使用-Statement.addBatch(String sql)(但具有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方法,您需要在重用前关闭Statement,重用PreparedStatement