ステートメントとPreparedStatementの違い


222

準備済みステートメントは、ステートメントのやや強力なバージョンであり、常にステートメントと同じくらい速くて簡単に処理できる必要があります。
準備済みステートメントはパラメーター化されている可能性があります

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

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

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

今私の質問はそれです-「Prepared Statementを使用する他の利点はありますか?」


12
私によると、最も効率的なのは、クエリを動的にパラメーター化できることです
Hussain Akhtar Wahid 'Ghouri'

回答:


198

の利点PreparedStatement

  • SQLステートメントのプリコンパイルとDB側のキャッシュにより、全体的に実行が高速化され、同じSQLステートメントをバッチで再利用できるようになります。

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

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

    したがって、文字列連結によってSQL文字列の値をインライン化しないでください

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
  • 、SQL文字列に非標準のJavaオブジェクトの設定など容易になりDateTimeTimestampBigDecimalInputStreamBlob)とReaderClob)。これらのタイプのほとんどでtoString()は、単純なで行うようにaを「行う」ことはできませんStatementPreparedStatement#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();

4
参照と例を組み合わせた説明と説明のテキストは、優れた答えになります。+1
XenoRo

1
@RDこれは、準備されたステートメントがデータベースへの2回のラウンドトリップを必要とするため当てはまる可能性があります。最初の準備は、2番目の実行です。しかし、私はそれをテストします。プランはのデータベースサーバーに引き続きキャッシュされると思いStatementますが、テストする価値があるかもしれません。
Brandon

2
私はJavaで確実に言うことはできませんが、一般に準備されたステートメントは「引用符やその他の特殊文字の組み込みエスケープ」を実行しませ。代わりに、実行可能なSQLとデータの分離を実行し、SQLがクエリプランに変換された後、パラメーターを個別の情報パケットとしてDBMSに送信します。
IMSoP 2016

@BalusC-詳しい説明ありがとうございます。
CodeBee .. 2018

49
  1. これらは事前にコンパイルされており(1回)、動的SQL(パラメーターが変更される場所)を繰り返し実行するのに高速

  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オブジェクトをパラメーター型としてインテリジェントに受け入れます-DBに理解可能なJDBC型フォーマットに変換します(toStringだけではありません) () フォーマット)。

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

  9. Javaでは、CLOB、BLOB、OutputStreams、およびReaderを、それぞれsetClob / setNClob、setBlob、setBinaryStream、setCharacterStream / setAsciiStream / setNCharacterStreamメソッドを介してパラメーター「フィード」として受け入れます。

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

  11. Javaでは、Statementからすべてのメソッドを継承します。これは、addBatchメソッドを継承し、さらに、パラメータ値のセットを追加して、addBatchメソッドを介してバッチ処理されたSQLコマンドのセットと一致させることができます。

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


それらの両方がインターフェイスのみである場合、それらすべての不思議はどのようにして可能ですか?
ラファエル

1
「驚異」はそのリターン(ベンダー固有の)インターフェースの実装標準的なファクトリメソッドを介して可能になる:Connection.createStatementConnection.prepareStatement。この設計では、特定の実装クラスを知る必要がなく、そのような実装クラスとの不必要な密結合を回避するために、インターフェイスに対して作業する必要があります。すべては、Java jdbcドキュメントとJavaドキュメントの例で説明されています。:)
グレンベスト

あなたの「経験則として、」一部は意味が、それは🤔周りに他の方法ではありませんん作る
bhathiya・ペレラ

38

PreparedStatementSQLインジェクション攻撃を防ぐための非常に優れた防御策です(しかし絶対確実ではありません)。パラメータ値のバインドは、「小さなボビーテーブル」が不要なアクセスを行うのを防ぐための良い方法です。


6
では、準備されたステートメントを通じてSQLインジェクションをどのように実行しますか
Michael Borgwardt 2010

2
Michael、準備されたステートメントに引数として渡される変数は、JDBCドライバーによって自動的にエスケープされます。
CodeBee .. 2010

3
SQLインジェクション攻撃が準備されたステートメントに対してどのように機能するかの例を挙げられますか?データベースコードのバグを想定していますか?
Peter Recore 2010

2
はい、しかし、それは「かなりダム」をはるかに超えています。それは愚かさを吹く心です。1オンスの知識を持つ人はそれをしません。
duffymo

2
また、多くのデータベース・ベンダーは、列名(と思うパラメータ化をサポートしていませんORDER BY)、および/または特定の場所で、数値定数(だと思うLIMITOFFSETプリペアドステートメントおよびパラメータがどこで使用された場合でも、その他のページネーション・ソリューションを)ので、これらはSQLインジェクションによる攻撃することができます可能。
dnet

31

ステートメントに対するPreparedStatementの利点のいくつかは次のとおりです。

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

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


私はあなたの記事を読みました、本当に良いものです。私の質問は今、なぜ誰もがステートメントを使用するのですか?静的クエリでも?!
pedram bashiri

私は常にPreparedStatementを使用していますが、Statementの方がメリットがある特定のシナリオは知りません。
Pankaj

13

追加するものは何もありません、

1-ループ内でクエリを実行する場合(複数回)、前述の最適化により、準備されたステートメントはより高速になる可能性があります。

2-パラメータ化されたクエリは、SQLインジェクションを回避する良い方法です。パラメーター化されたクエリは、PreparedStatementでのみ使用できます。


10

ステートメントは静的であり、準備されたステートメントは動的です。

ステートメントは、DDLおよびDML用に準備されたステートメントに適しています。

準備されたステートメントが高速である一方で、ステートメントは低速です。

その他の違い(アーカイブ)



7

mattjamesから引用

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

これは事実であり、ルールであり、法律です。準備されたステートメントをどこでも使用してください。STATEMENTSを使用する場所はほとんどありません。


5

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


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

4

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

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


4

準備済みまたはパラメーター化されたクエリの別の特徴:この記事から引用した参照。

このステートメントは、同じSQLステートメントが高い効率で繰り返し実行されるデータベースシステムの機能の1つです。準備されたステートメントはテンプレートの一種であり、さまざまなパラメーターを持つアプリケーションによって使用されます。

ステートメントテンプレートが準備され、データベースシステムに送信されます。データベースシステムは、このテンプレートの解析、コンパイル、最適化を実行し、実行せずに保存します。

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

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

データの数が増えていて、インデックスが頻繁に変更されている場合、この状況では新しいクエリプランが必要になるため、準備済みステートメントは失敗する可能性があります。


3

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

PreparedStatement インターフェース(ステートメントの拡張)は、パラメーターあり/なしのプリコンパイル済みSQLステートメントを実行します。

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

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


2

混乱しないでください:単に覚えておいてください

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

1
prepareStatementは作成時に引数を取り、Statementは引数を取りません。

1

&に関するセマンティクスの理解が不十分なため、- Statement(ただしSQLインジェクションを使用)を使用して機能するレガシーコードをPreparedStatement、より遅いコードで使用するソリューションに変更するために、この質問のすべての回答に従いました。Statement.addBatch(String sql)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つだけ閉じることを心配する必要がありますが、このリストアプローチでは、再利用する前にステートメントを閉じる必要があります。PreparedStatementの再利用

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.