1
PreparedStatement ps = con.createStatement("select * from table1 where last_name like ?");
ps.setString(1, "'%"+lastName+"'");

这会和...一样工作吗?

Statement s = con.createStatement("select * from table1 where last_name like %"+ lastName);

还是 PreparedStatement 去掉了 % 符号?

4

6 回答 6

3

% 是一个通配符(至少在 Oracle 中),所以理论上两者应该是一样的(假设你添加了缺少的单引号)

但是,第一个被认为是更好的做法,因为它可以使数据库优化器不重新解析语句。第一个还应该保护您免受 SQL 注入,而第二个可能不会。

于 2009-05-14T15:06:48.440 回答
3

第二个不起作用,因为您忘记了字符串周围的引号!旁边你需要逃避并小心 sql 注入。

假设 SQL

lastName = "and a quote' or a bracket()";
Statement s = con.createStatement("select * from table1 where last_name like '%"+ lastName + "'");

生成的 SQL 是:

select * from table1 where last_name like '%and a quote' or a bracket()'

这将失败

绑定变量使其始终更安全。

于 2009-05-14T15:11:41.097 回答
3

简短的回答是:是的,假设您修复了引用,两者应该给出相同的结果。百分号不会从准备好的语句中“删除”,就像任何其他字符一样。

更长的答案:准备好的语句与一次性语句的问题可能很复杂。如果您只打算执行一次,则准备好的语句将花费更长的时间,因为数据库引擎必须为准备好的语句完成所有设置,然后插入值,然后让它在缓存中浮动,直到引擎决定冲洗它。此外,优化器通常不能有效地处理准备好的语句。准备好的语句的全部意义在于优化器解析查询并设计一次查询计划。假设您说“从 customer_type=? 和 customer_zip=? 的客户中选择 customer_name”。您对类型和 zip 都有索引。使用一次性声明(当然,填充的是真实值而不是问号),许多数据库引擎中的查询优化器可以查看关于两个字段的值分布的统计信息,并选择将提供较小记录集的索引,然后按顺序读取所有这些并消除未通过第二次测试的记录。使用准备好的语句,它必须在知道将提供什么值之前选择索引,因此它可能会选择效率较低的索引。

你永远不应该在死亡的痛苦中编写代码,只是在未知值周围加上引号并将其填充到 SQL 语句中。要么使用准备好的语句,要么编写一个正确转义任何嵌入引号的函数。这样的函数写起来很简单。我不明白为什么 JDBC 不包含一个,所以您必须自己编写它并将其包含在每个应用程序中。(鉴于某些 SQL 方言具有应转义的单引号以外的字符,这一点尤其正确。)

以下是 Java 中此类函数的示例:

public static String q(String s)
{
  if (s==null)
    return "null";
  if (s.indexOf('\'')<0)
    return "'"+s+"'";
  int sl=s.length();
  char[] c2=new char[sl*2+2];
  c2[0]='\''; 
  int p2=1;
  for (int p=0;p<sl;++p)
  {
    char c=s.charAt(p);
    if (c=='\'')
      c2[p2++]=c;
    c2[p2++]=c;
  }
  c2[p2++]='\'';
  return new String(c2,0,p2);
}

(注意:我刚刚从我从代码中提取的版本中编辑了该功能,以消除一些与此处无关的特殊情况——如果我在执行此操作时引入了一些小错误,请见谅。)

我通常给它一个非常短的名字,比如“q”,所以我可以写:

String sql="select customer_name from customer where customer_type="+q(custType)
  +" and customer_zip="+q(custZip);

或类似的快速简单的东西。这违反了“为函数提供完整且有意义的名称”,但我认为在这里值得,我可以在一个语句中使用相同的函数十次。

然后我重载它以获取日期和数字以及其他特殊类型并适当地处理它们。

于 2009-05-14T17:02:51.250 回答
1

使用带有绑定变量的准备好的语句要快得多,因为这意味着 Oracle 不必一次又一次地解析(编译)sql 语句。Oracle 将所有已执行的语句与执行计划一起存储在一个共享哈希表中以供重用。然而,Oracle 只会重用带有绑定变量的准备好的语句的执行计划。当你这样做时:

"select * from table1 where last_name like %"+ lastName

Oracle重用执行计划。

(Oracle 对每个 sql 语句进行哈希处理,当您使用 select ... where last_name like %"+ lastName 每个 sql 语句都有不同的哈希值,因为变量 lastname 几乎总是有不同的值,所以 Oracle 在哈希表和 Oracle 不能重用执行计划。)

在多并发情况下,影响更大,因为 Oracle 锁定了这个共享哈希表。这些锁不会持续很长时间,但在多并发情况下,锁确实开始受到伤害。当您使用带有绑定变量的准备好的语句时,几乎不需要锁定。顺便说一下,Oracle 将这些自旋锁称为闩锁。

只有当您有一个数据仓库并且您的查询需要几分钟(报告)而不是几秒钟时,您才能使用非准备好的语句。

于 2009-05-14T17:36:31.247 回答
0

我们经常毫无问题地使用第一种方法。例如:

String sql = "SELECT * FROM LETTER_BIN WHERE LTR_XML Like ' (?) ' AND LTR_BIN_BARCODE_ID = (?)";
try
{
    // Cast a prepared statement into an OralcePreparedStatement
    opstmt = (OraclePreparedStatement) conn.prepareStatement(sql);
    // Set the clob using a string
    opstmt.setString(1,fX.toString());
    // for this barcode
    opstmt.setLong(2,lbbi);
    // Execute the OraclePreparedStatement
    opstmt.execute();
} catch(java.sql.SQLException e)
{
    System.err.println(e.toString());
} finally
{
    if(opstmt != null)
    {
        try
        {
            opstmt.close();
        } catch(java.sql.SQLException ignore)
        {
            System.err.println("PREPARED STMT ERROR: "+ignore.toString());
        }
    }

}
于 2009-05-14T15:13:11.107 回答
0

好的,我会在 Oracle 上相信你的话。毫不奇怪,这取决于数据库引擎。Postgres 的行为与我描述的一样。当从 JDBC 使用 MySQL 时——至少在几年前我最后一次研究这个时——准备好的语句和单次使用的语句之间几乎没有区别,因为 MySQL JDBC 驱动程序将准备好的语句保存在客户端上另一方面,当您执行准备好的语句时,它会将值作为文本填充并将其发送到数据库引擎。因此,就引擎而言,确实没有准备好的语句之类的东西。得知其他引擎具有完全不同的行为,我一点也不感到惊讶。

于 2009-05-14T21:02:33.623 回答