Java PreparedStatement中的%符号

时间:2009-05-14 15:01:31

标签: java sql oracle prepared-statement

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是否会删除%符号?

6 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分: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()'

将失败

绑定变量使得使用它总是更安全。

答案 2 :(得分:3)

简短回答是:是的,假设您修改了引用,两者应该给出相同的结果。百分号不会被“删除”准备好的陈述,不会超过任何其他角色。

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

你永远不应该对死亡的痛苦永远编写代码,只是在一个未知值附近引用引号并将其填充到SQL语句中。要么使用预处理语句,要么编写一个正确转义任何嵌入式引号的函数。写这样的功能是微不足道的。我不明白为什么JDBC不包含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);
像那样快捷方便的东西。这违反了“赋予函数完整且有意义的名称”,但我认为值得在这里,我可以在一个语句中使用相同的函数十次。

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

答案 3 :(得分: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找不到sql哈希表中的语句和Oracle无法重用执行计划。)

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

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

答案 4 :(得分: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());
        }
    }

}

答案 5 :(得分:0)

好的,我会在Oracle上接受你的意见。毫不奇怪,这与数据库引擎有关。 Postgres的行为和我描述的一样。从JDBC使用MySQL时 - 至少在几年前我最后一次研究这个时 - 准备好的语句和单用语句之间几乎没有区别,因为MySQL JDBC驱动程序在CLIENT上保存了预准备语句另外,当您执行准备好的语句时,它会将值填入文本并将其发送到数据库引擎。因此就引擎而言,实际上没有准备好的声明。我不会惊讶地发现其他引擎的行为完全不同。