准备好的声明是一个稍微强大的声明版本,并且应该始终至少与声明一样快速和容易处理。
准备好的声明可以参数化
大多数关系数据库分四步处理JDBC / SQL查询:
对于发送到数据库的每个SQL查询,语句将始终执行上述四个步骤。准备语句在上面的执行过程中预先执行步骤(1) - (3)。因此,在创建Prepared Statement时,会立即执行一些预优化。其效果是减少执行时数据库引擎的负载。
现在我的问题是 - “使用预备声明还有其他优势吗?”
答案 0 :(得分:181)
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对象的设置,例如: Date
,Time
,Timestamp
,BigDecimal
,InputStream
(Blob
)和Reader
(Clob
)。在大多数类型中,您不能像在简单的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)
它们是预编译的(一次),因此重复执行动态SQL(参数更改)的速度更快
数据库语句缓存可提高数据库执行性能
数据库存储先前执行的语句的执行计划的缓存。这允许数据库引擎重用先前已执行的语句的计划。由于PreparedStatement使用参数,因此每次执行时它都显示为相同的SQL,数据库可以重用以前的访问计划,从而减少处理。语句将参数“内联”到SQL字符串中,因此不会显示为与DB相同的SQL,从而阻止缓存使用。
二进制通信协议意味着更少的带宽和更快速的COM服务器调用
预处理语句通常通过非SQL二进制协议执行。这意味着数据包中的数据较少,因此与服务器的通信速度更快。根据经验,网络操作比磁盘操作快一个数量级,磁盘操作比内存CPU占用快一个数量级。因此,通过网络发送的数据量的任何减少都会对整体性能产生很好的影响。
它们通过转义所提供的所有参数值的文本来防止SQL注入。
它们在查询代码和参数值之间提供了更强的分离(与连接的SQL字符串相比),提高了可读性并帮助代码维护者快速理解查询的输入和输出。
在java中,可以调用getMetadata()和getParameterMetadata()来分别反映结果集字段和参数字段
在Java中,智能地通过的setObject,setBoolean,setByte,的setDate,setDouble,setDouble,的setFloat,SETINT,setLong,setShort,时刻设定,的setTimestamp接受java对象作为参数类型 - 它转换成JDBC类型格式,可理解到DB(不只是toString()格式)。
在java中,通过setArray方法接受SQL ARRAYs作为参数类型
在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,数据库管理/维护/逻辑调整以及专有权的使用数据库逻辑&amp;特征
答案 2 :(得分:36)
PreparedStatement
在防止SQL injection attacks方面是一个非常好的防御(但不是万无一失)。绑定参数值是防止"little Bobby Tables"进行不必要访问的好方法。
答案 3 :(得分:28)
PreparedStatement对Statement的一些好处是:
在http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example
了解有关SQL注入问题的更多信息答案 4 :(得分:13)
没什么可补充的,
1 - 如果要在循环中执行查询(超过1次),由于您提到的优化,准备好的语句可以更快。
2 - 参数化查询是避免SQL注入的好方法,只能在PreparedStatement中使用。
答案 5 :(得分:10)
答案 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)
在JDBC中使用Statement应该100%本地化以便使用 对于DDL(ALTER,CREATE,GRANT等),因为这些是唯一的陈述 无法接受BIND VARIABLES的类型。准备好的声明或 CallableStatements应该用于其他类型的语句 (DML,查询)。因为这些是接受bind的语句类型 变量
这是一个事实,一个规则,一个法律 - 使用准备好的声明无处不在。 使用STATEMENTS几乎没有。
答案 12 :(得分:3)
Statement
接口执行不带参数的静态SQL语句
PreparedStatement
接口(扩展语句)执行带/不带参数的预编译SQL语句
高效重复执行
预编译所以它更快
答案 13 :(得分:1)
不要混淆:只记得
答案 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。