web-dev-qa-db-ja.com

StatementとPreparedStatementの違い

Prepared StatementはStatementのもう少し強力なバージョンであり、常にStatementと少なくとも同じくらい速くて扱いやすいものであるべきです。
準備文はパラメータ化することができる

ほとんどのリレーショナルデータベースは、4つのステップでJDBC/SQLクエリを処理します。

  1. 着信SQLクエリを解析する
  2. SQLクエリをコンパイルする
  3. データ取得経路を計画/最適化する
  4. 最適化されたクエリを実行/データを取得して返す

ステートメントは、データベースに送信された各SQLクエリに対して、常に上記の4つの手順を実行します。準備済みステートメントは、上記の実行プロセスのステップ(1) - (3)を事前に実行します。したがって、Prepared Statementを作成するときには、事前の最適化がただちに実行されます。その結果、実行時にデータベースエンジンの負荷が軽減されます。

今、私の質問は、「Prepared Statementを使用することの他の利点は何ですか?」です。

202
SO_Bee

PreparedStatement の利点

  • SQL文のプリコンパイルおよびDB側のキャッシュにより、全体的に高速な実行と バッチ での同じSQL文の再利用が可能になります。

  • 引用符や他の特殊文字の組み込みエスケープによる SQLインジェクション攻撃 の自動防止。これには、値を設定するためにPreparedStatementsetXxx()メソッドのいずれかを使用する必要があることに注意してください。

    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();
    

    したがって、don'tは、文字列連結によってSQL文字列の値をインライン化します。

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
    
  • SQL文字列内の非標準Javaオブジェクトの設定を容易にします。 DateTimeTimestampBigDecimalInputStreamBlob )および ReaderClob )。これらのほとんどの型では、単純なStatementのようにtoString()を「単に」実行することはできません。以下のユーティリティメソッドに示すように、ループ内で 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();
    
182
BalusC
  1. 動的SQLの繰り返し実行(パラメータが変更される場合)のために高速になるので、事前にコンパイルされています(1回)。

  2. データベース文キャッシングはDB実行性能を向上させる

    データベースは、以前に実行された文の実行計画のキャッシュを格納します。これにより、データベースエンジンは以前に実行されたステートメントの計画を再利用できます。 PreparedStatementはパラメータを使用するため、実行されるたびに同じSQLとして表示されるため、データベースは以前のアクセスプランを再利用でき、処理が削減されます。ステートメントはパラメータをSQL文字列に「インライン」するため、DBに対して同じSQLとして表示されず、キャッシュの使用が妨げられます。

  3. バイナリ通信プロトコルにより、帯域幅が狭くなり、DBサーバーへの通話が速くなります。

    準備済みステートメントは通常、非SQLバイナリー・プロトコルを介して実行されます。これは、パケットに含まれるデータが少なくなるため、サーバーへの通信が速くなることを意味します。経験則として、ネットワーク操作はディスク操作よりも桁違いに速く、ディスク操作はインメモリーCPU操作よりも桁違いに高速です。したがって、ネットワークを介して送信されるデータ量を減らすと、全体的なパフォーマンスに大きな影響があります。

  4. 提供されたすべてのパラメータ値のテキストをエスケープすることで、SQLインジェクションから保護します。

  5. それらはクエリコードとパラメータ値の間のより強い分離(連結されたSQL文字列と比較して)を提供し、読みやすさを高め、コード管理者がクエリの入力と出力を素早く理解するのを助けます。

  6. Javaでは、getMetadata()とgetParameterMetadata()を呼び出して、それぞれ結果セットフィールドとパラメータフィールドに反映できます。

  7. Javaでは、setObject、setBoolean、setByte、setDate、setDouble、setDouble、setFloat、setInt、setLong、setShort、setTime、setTimestampを介して、Javaオブジェクトをパラメータ型としてインテリジェントに受け入れます(toStringではなくJDBCに理解可能なJDBC型フォーマットに変換します)。 () フォーマット)。

  8. Javaでは、setArrayメソッドを介してパラメータ型としてSQL ARRAYを受け入れます

  9. Javaでは、setClob/setNClob、setBlob、setBinaryStream、setCharacterStream/setAsciiStream/setNCharacterStreamの各メソッドを介して、CLOB、BLOB、OutputStream、およびReadersをパラメータ "feeds"としてそれぞれ受け入れます。

  10. Javaでは、setURL、setRowId、setSQLXML、およびsetNullメソッドを使用して、DBに固有の値をSQL DATALINK、SQL ROWID、SQL XML、およびNULLに設定できます。

  11. Javaでは、Statementからすべてのメソッドを継承します。これはaddBatchメソッドを継承し、さらにaddBatchメソッドを介して一連のバッチSQLコマンドに一致するように一連のパラメーター値を追加することを可能にします。

  12. Javaでは、特別なタイプのPreparedStatement(サブクラスCallableStatement)を使用してストアドプロシージャを実行できます - 高性能、カプセル化、手続き型プログラミングとSQL、DB管理/保守/ロジックの微調整、そして独自のDBロジックと機能の使用

43
Glen Best

PreparedStatementは、 SQLインジェクション攻撃 を防ぐのに非常に優れた防御策です(しかし絶対確実というわけではありません)。パラメータ値をバインドすることは、 "little Bobby Tables" が不要な訪問をするのを防ぐための良い方法です。

36
duffymo

Statementに対するPreparedStatementの利点は次のとおりです。

  1. PreparedStatementは自動的に特殊文字をエスケープするため、SQLインジェクション攻撃を防ぐのに役立ちます。
  2. PreparedStatementを使用すると、パラメータ入力を使用して動的クエリを実行できます。
  3. PreparedStatementは、クエリの入力パラメータを設定するためのさまざまなタイプの設定メソッドを提供します。
  4. PreparedStatementはStatementよりも高速です。 PreparedStatementを再利用するか、複数のクエリを実行するためにバッチ処理メソッドを使用すると、より見やすくなります。
  5. PreparedStatementはセッターメソッドでオブジェクト指向のコードを書くのに役立ちますが、Statementではクエリーを作成するために文字列連結を使わなければなりません。設定するパラメータが複数ある場合は、String連結を使用してQueryを記述すると、非常に見苦しくエラーが発生しやすくなります。

SQLインジェクションの問題について詳しくは、 http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example をご覧ください。

28
Pankaj

追加するものは何もない、

1 - あなたがループでクエリを実行したい場合(1回以上)、あなたが言及した最適化のため、準備されたステートメントはより速くなることができます。

2 - パラメータ化されたクエリはSQLインジェクションを避けるための良い方法です。これはPreparedStatementでのみ利用可能です。

13
mhshams

ステートメントは静的であり、準備済みステートメントは動的です。

ステートメントはDDLおよびDML用の準備済みステートメントに適しています。

準備された文は速いが、文は遅くなります。

もっと違い

10
sandeep vanama

ステートメント内でCLOBを実行できません。

そして:(OraclePreparedStatement)ps

7
orbfish

sQLインジェクションは準備済みステートメントによって無視されるため、準備済みステートメントのセキュリティーが向上します。

5
ashish geol

として引用されるように mattjames

JDBCでのStatementの使用は、DDL(ALTER、CREATE、GRANTなど)に使用されるように100%ローカライズする必要があります。これらはBIND VARIABLESを受け入れられない唯一のステートメントタイプであるためです。 PreparedStatementsまたはCallableStatementsは、その他すべての種類のステートメント(DML、Queries)に使用する必要があります。これらはバインド変数を受け入れるステートメントタイプです。

これは事実であり、規則であり、法律である - どこでも準備文を使用する。ステートメントをほとんどどこにも使用しないでください。

4
Root
  • 読みやすい
  • クエリ文字列を簡単に定数にすることができます
4
nanda

ステートメントは静的SQLステートメントの実行に使用され、入力パラメーターを受け入れることはできません。

PreparedStatementは、SQLステートメントを何度も動的に実行するために使用されます。入力パラメータを受け入れます。

4
MARA MP

準備済みクエリまたはパラメータ化クエリのもう1つの特徴は、 この記事の参考文献です

このSQL文は、同じSQL文を効率よく繰り返し実行するというデータベースシステムの機能の1つです。プリペアドステートメントはテンプレートの一種で、さまざまなパラメータを持つアプリケーションで使用されます

ステートメントテンプレートが作成され、データベースシステムに送信され、データベースシステムはこのテンプレートおよび解析を実行せずに構文解析、コンパイルおよび最適化を実行する。

一部のパラメータは、テンプレート作成後のアプリケーションでwhere句が渡されない場合、これらのパラメータをデータベースシステムに送信し、データベースシステムはSQL文のテンプレートを使用して要求に従って実行します。

アプリケーションはさまざまな手法やプロトコルを使用してパラメータを準備できるため、準備済みステートメントはSQLインジェクションに対して非常に便利です。

その時点でデータ数が増え、索引が頻繁に変更されていると、このような状況では新しい照会計画が必要になるため、準備済みステートメントは失敗する可能性があります。

4
Anvesh

Statementインターフェースは、パラメーターなしで静的SQLステートメントを実行します

PreparedStatementインターフェース(Statementを拡張しています)は、パラメーターを指定して、または指定せずに、プリコンパイル済みSQLステートメントを実行します

  1. 繰り返し実行に効率的

  2. プリコンパイルされているので速いです

3
Bernard

混乱しないでください。

  1. ステートメントはDDLのような静的クエリに使用されます。すなわちcreate、drop、alter、prepareStatementは動的クエリ、すなわちDMLクエリに使用されます。
  2. Statementでは、prepareStatementクエリのプリコンパイル時にクエリはプリコンパイルされません。これは、prepareStatementの時間効率が高いためです。
  3. prepareStatementは作成時に引数を取りますが、Statementは引数を取りません。たとえば、テーブルを作成して要素を挿入する場合は、:: Statementを使用してテーブルを作成し(静的)、prepareStatementを使用して要素を挿入(動的)します。
1
Roopam

この質問のすべての回答に従い、-Statement(ただし、SQLインジェクションを使用)を使用するレガシーコードを、Statement.addBatch(String sql)周辺のセマンティクスの理解が不十分なため、PreparedStatementを使用するソリューションに大幅に遅いコードで変更しました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を使用して実行できるとは思いません。

また、組み込みのバッチ処理機能を使用する場合、1つのステートメントのみを閉じることを心配する必要がありますが、このリストアプローチでは、再利用する前にステートメントを閉じる必要があります Reusing a PreparedStatement

0
Sabir Khan