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

簡単な答えは次のとおりです。はい、引用を修正すると、2つは同じ結果になるはずです。パーセント記号は、他の文字と同様に、準備されたステートメントから「取り除かれ」ません。

より長い答え: 準備されたステートメントと使い捨てステートメントの問題は複雑になる可能性があります。1 回だけ実行する場合、データベース エンジンが準備済みステートメントのすべてのセットアップを実行し、値を挿入し、エンジンが決定するまでキャッシュに保持する必要があるため、準備済みステートメントに時間がかかります。それをフラッシュします。また、オプティマイザーは準備済みステートメントを効率的に処理できないことがよくあります。プリペアド ステートメントの要点は、オプティマイザーがクエリを解析し、クエリ プランを 1 回考案することです。「customer_type=? および customer_zip=? の顧客から customer_name を選択」のようなことを言うとします。タイプとジップの両方にインデックスがあります。使い捨てのステートメント (もちろん、疑問符ではなく実際の値が入力されています) を使用すると、多くのデータベース エンジンのクエリ オプティマイザは、2 つのフィールドの値の分布に関する統計を調べ、より小さなレコード セットを提供するインデックスを選択し、これらすべてを順番に読み取り、2 番目のテストに失敗したレコードを除外します。準備されたステートメントでは、提供される値を知る前にインデックスを選択する必要があるため、効率の低いインデックスを選択する可能性があります。

未知の値を引用符で囲み、それを 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);

またはそのような迅速かつ簡単なもの。これは「関数に完全で意味のある名前を付ける」という違反ですが、同じ関数を 1 つのステートメントで 10 回使用する可能性があるここでは価値があると思います。

次に、オーバーロードして、日付、数値、およびその他の特殊な型を取得し、適切に処理します。

于 2009-05-14T17:02:51.250 に答える
3

% は (少なくとも Oracle では) ワイルドカード文字であるため、理論的にはどちらも同じように機能するはずです (不足している単一引用符を追加すると仮定します)。

ただし、最初の方法は、データベース オプティマイザーがステートメントを再解析しないようにする可能性があるため、より適切な方法と見なされます。前者は SQL インジェクションから保護する必要がありますが、後者はそうでない場合があります。

于 2009-05-14T15:06:48.440 に答える
3

文字列を囲む引用符を忘れたため、2 番目は機能しません。次にエスケープし、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 に答える
1

バインド変数で準備済みステートメントを使用すると、Oracle が SQL ステートメントを何度も解析 (コンパイル) する必要がないため、はるかに高速になります。Oracle は、実行されたすべてのステートメントを実行計画とともに共有ハッシュ テーブルに格納し、再利用します。ただし、Oracle は、バインド変数を持つ準備済みステートメントの実行計画のみを再利用します。あなたがするとき:

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

Oracleは実行計画を再利用しません。

(Oracle はすべての sql ステートメントをハッシュし、 select ... where last_name like %"+ lastName を使用すると、変数 lastname はほとんど常に異なる値を持つため、すべての sql ステートメントは異なるハッシュ値を持つため、Oracle は SQL ステートメントをハッシュ テーブルと Oracle は実行計画を再利用できません。)

複数の同時実行の状況では、Oracle がこの共有ハッシュ テーブルをロックするため、影響はさらに大きくなります。これらのロックは長続きしませんが、複数の同時実行の状況では、ロックは本当に傷つき始めます。バインド変数で準備済みステートメントを使用する場合、ロックはほとんど必要ありません。ちなみにOracleはこれらのスピンロックラッチを呼び出します。

データウェアハウスがあり、クエリが数秒ではなく数分 (レポート) かかる場合にのみ、準備されていないステートメントを使用できます。

于 2009-05-14T17:36:31.247 に答える
0

わかりました、オラクルであなたの言葉を信じます。これは、当然のことながら、データベース エンジンに依存しています。Postgres は、説明したとおりに動作します。JDBC から MySQL を使用する場合 (少なくとも数年前に最後に調査した時点では)、準備済みステートメントと使い捨てステートメントの違いはほとんどありません。これは、MySQL JDBC ドライバーが準備済みステートメントを CLIENT に保存するためです。一方、準備されたステートメントを実行すると、値がテキストとして入力され、データベース エンジンに送られます。したがって、エンジンに関する限り、準備されたステートメントのようなものは実際にはありません。他のエンジンの動作がまったく異なることを知っても、まったく驚かないでしょう。

于 2009-05-14T21:02:33.623 に答える
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 に答える