【问题标题】:Concatenation in prepared statement准备好的语句中的连接
【发布时间】:2014-07-07 20:30:17
【问题描述】:

我已经为 mySQL 编写了通用 DAO 层(它可以使用 Reflection 和 ResultSetMetaData 保存\获取任何将实体扩展到\from 表的类对象)。我的实现在 sql 查询中几乎没有串联。是浪费了准备好的语句的所有优势,还是我只是失去了连接字符串的一点性能,仅此而已?

例如删除实体的一段代码:

PreparedStatement prepStatement = con.prepareStatement("DELETE FROM "
                                      + tableName + " WHERE id = ?");
prepStatement.setLong(1, id);

【问题讨论】:

  • 是否浪费了准备好的语句的所有优点?不,这不对。阅读When we use PreparedStatement instead of Statement?和阅读when to use StringBuilder in java
  • 我知道 StringBuilder 以及它相对于字符串连接的优势。如果只添加表名,我认为 StringBuilder 是多余的。
  • 您的问题的答案在第一个链接中。第二个只是建议。
  • Prepared 语句准备和一次执行通常比简单语句慢。但这绝对是要走的路。您应该考虑在启动时为每个实体类上的每个操作准备一个语句,并根据需要经常重用它们。在运行时间很长的应用程序中,我建议不时重新准备语句。
  • 您的数据库将为每个表名重用查询。因此,您不会从仅在数据库级别重用 1 个查询中受益,而是与您使用的表一样多。但是,DELETE FROM TABLE1 WHERE ID = 1DELETE FROM TABLE1 WHERE ID = 2 将作为具有不同参数的相同查询处理,您仍然可以从中受益。但是,DELETE FROM TABLE1 WHERE ID = 1DELETE FROM TABLE2 WHERE ID = 1 将被视为两个不同的查询。

标签: java mysql sql jdbc


【解决方案1】:

PreparedStatements 的主要好处是当您的代码行为方式与此伪代码类似时:

PreparedStatement ps = con.prepareStatement("blabalblabla");
for (int i = 0; i < a gazillion times; i++) {
    // Set parameters into ps
    ...
    // execute already prepared statement
    ps.execute();
}

也就是说,您准备一次并执行多次,每次使用不同的参数集。这允许驱动程序/数据库仅执行一次可能成本高昂的操作(例如解析),然后重用该工作。除此之外,使用PreparedStatement 可能会被解释为对驱动程序的提示,即它应该缓存该语句资源或其他东西,因为它将在以后使用,但我认为它不会像“一次准备执行多次”的方法。

您使用连接来添加表名不会禁用 JDBC 驱动程序所做的优化(如果有的话)。但无论如何,如果您的代码执行的“一次准备执行一次”多于“一次准备执行多次”,那么 PreparedStatement 可能只会带来很小的性能优势。

请注意,以上所有内容都高度依赖于数据库/驱动程序。例如,如果您按照我所说的“一次准备执行多次”的方式使用PreparedStatements,Oracle 的性能会很多更好。最后的建议是,出于性能和安全性的原因,不要忘记您应该避免连接参数值,除非您别无选择。

【讨论】:

  • 所以,我的建议是正确的。使用新表名第一次调用方法后的 JDBC 驱动程序将缓存新的预准备语句,并且该实体的所有下一次调用将使用旧的预准备语句。
  • 不,您需要在文档/源代码中检查您的 JDBC 驱动程序和数据库组合对PreparedStatements 的实际作用。我所描述的是一个相当智能的 JDBC 驱动程序/数据库组合可能会做什么
【解决方案2】:

建议使用准备好的语句来提高数据库性能。 理论上,数据库驱动程序缓存准备好的语句(您可能需要启用对连接对象的缓存)。 我认为连接并不那么重要。 请记住,tableName 在驱动程序缓存中可能区分大小写。

我会检查您的数据库驱动程序功能,您应该能够调试驱动程序,并监控数据库以查看您的语句是如何处理/执行的。

【讨论】:

    【解决方案3】:

    您示例中的变量 tableName 可能会引入 SQL 注入漏洞,但它可能是防止这种情况的替代方法。例如,

    Map<String,String> myTables; // key and value are the same.
    tableName = myTables.get(tableName); // safe known value or null.
    

    通常,最好始终使用准备好的语句来避免麻烦。然而,有时“即时”构建查询(最常见的是where 查询)可以节省很多行,否则会接近重复代码,因此很难说“永远不要这样做”。

    【讨论】:

    • 是的,我关心带有“final”关键字的tableName :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-30
    • 2011-01-12
    • 2013-10-17
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    相关资源
    最近更新 更多