|
おそらくほとんどの開発者は Hibernate や Mybatis などのフレームワークを使用しており、その途中でいくつかの落とし穴に遭遇したことがあるでしょう。
MyBatis/iBatisにおける#と$の違いは、#はSQLインジェクションを大幅に防ぐのに対し、$はそうではないという点です。そのため、経験豊富な開発者は初心者に#の使用を勧めることが多いです。簡単に言うと、#{}は準備が整っており安全ですが、$はそうではなく、変数の値を取得するだけなので安全ではなく、SQLインジェクションに対して脆弱です。注意が必要な特殊なケースもあり、現実の問題に対処するには具体的な解決策が必要になる場合もあります。 SQL文を実行する際に、変数を処理せずに直接値を代入したい場合があります。この場合、(${a})を使用します。このメソッドを使用する場合は、次のように値を代入します:@Param(value="a") String a たとえば、日付関連の問題: 日付のフォーマット設定で時間範囲に問題が発生する可能性があります。データベースの時刻がDATE型の場合、MyBatisのjdbcTypeはDATEを使用する必要があります。
代わりに
resultMap を使用する場合は、最初の行に ID を記述する必要があります。そうしないと、エラーが発生します。 たとえば、最近のプロジェクトで、MyBatis の大きな落とし穴に遭遇しました。MyBatis が例外をスローし続けたのです。
そこでコードのデバッグを開始しました。コードにはストアドプロシージャがあり、ROOTユーザーが実行したSQLの結果セットは正常でした。プログラムはテスト環境では正常に動作しましたが、本番環境では他のユーザーでは動作しませんでした。最終的に、データベースがユーザーに必要な権限を付与していなかったことが問題の原因であることが判明しました。 ケース1: 初心者として、私が遭遇した落とし穴を書き留めておきます(失敗から学ぶことは、同じことを繰り返すよりも良いことです)。2つのテーブルを使ってSQL文を作成しました。どちらのテーブルにも、「Time」と「Content」という同じ2つのフィールドがあります。両方のテーブルからこの2つのフィールドを取得し、1つのDTOに格納する必要がありました(下の画像を参照)。 SQL ステートメントは次のとおりです。 しかし、プログラムを実行した後、データベース テーブル内の同じ名前を持つ最後のいくつかのフィールドが取得時にすべて null であることがわかりました。 しかし、データベース側でコマンドを実行しても空のデータは取得されませんでした。 本当にイライラしました!すると、ある専門家が、SQL文で取得するフィールド名はDTOのパラメータ名と一致している必要があると指摘してくれました。このように変更したら、問題は解決しました。 データが取得されました…HibernateではHQLを使っていた時、データをDTOに入れて、正しい順序と型のパラメータで`SELECT new DTO_name()`を使って取得できたのを覚えています…これは違いと言えるのではないでしょうか?HQLの方が使いやすい気がします…両者の違い、メリット、デメリットを教えていただけませんか? エンティティクラスの属性名とテーブルのフィールド名が一致していない場合、MyBatisクエリは対応する結果を返すことができません。インターネットで徹底的に検索した結果、フィールド名をエンティティクラスの属性名にマッピングして1対1の対応を確立するという解決策を見つけたので、ご紹介します。この方法は、MyBatisの組み込みメカニズムを利用して、フィールド名と属性名のマッピングを解決します。 ケース2: データベーステーブルは複合主キーを使用しており、リバースエンジニアリングによって2つのエンティティクラスが生成されました。見た目は不自然でしたが、それでも使用可能でした。その後、主キーを一旦削除し、最初の生成後に再度追加しました。また、当初は `tinyint` は小さな整数を表すためのものだと思っていましたが、実際にはブール型の属性が生成されていました。その後、`tinyint` は yes/no ステートメントにのみ使用することにしました。リバース生成されたSQL文は手動で変更しないでください。変更すると、繰り返し再生成されてしまいます。しかし、これらの落とし穴はありますが、それでもMyBatisは非常に便利で、Hibernateよりもはるかに優れていると感じています。MyBatisに完全に切り替える前に、Hibernateを少し試しただけでした。 ケース3: sum() と count() の誤った使用によりエラーが発生します。 count()、count(1)、count(0) 関数は、すべてのフィールドが null である行も含め、行の絶対数を表します。count() と比較すると、count(*) は InnoDB では効率が悪くなります。 COUNT(column_name) クエリを使用すると、列名が null ではない行の数が表示されます。 `sum(column_name)` メソッドは指定された列の値を合計します。 MyBatis は 0 を空の文字列 '' として扱い、MySQL は空の文字列 '' を 0 として扱うことを考えると、整数を空の文字列 '' として扱うために、MyBatis の Mapper で整数型の条件をどのようにチェックすればよいでしょうか? データベースフィールドの型が整数の場合、パラメータ変数が空文字列またはNULL値であれば、Mybatisは自動的にパラメータ値0を割り当てます。したがって、整数パラメータの複数の状態をMapperに渡す前に判断したい場合は、値が空文字列またはNULL値であるかどうかを確認し、対応する状態値をパラメータに割り当てる必要があります。そうしないと、パラメータ値が空文字列、NULL、または0のいずれであっても、結果は同じになります。 通常、int 型に関わる操作を処理する場合、サービスはまず数値型を文字列型に変換してから、それを処理のために Mapper に渡します。 タイムスタンプの使用 新しいレコードを作成するときは、このフィールドを現在の時刻に設定しますが、後で変更するときに更新しないでください (createtime にこれを使用できます)。 タイムスタンプ デフォルト CURRENT_TIMESTAMP このデータ列は、新しいレコードを作成するとき、および既存のレコードを変更するときに更新されます (更新に使用できます)。 TIMESTAMP デフォルト CURRENT_TIMESTAMP 更新時 CURRENT_TIMESTAMP resultMap を使用する場合は、最初の行に ID を記述する必要があります。そうしないと、エラーが発生します。 ケース4: XML エスケープ文字を直接記述するとエラーが発生します。左側にリストされているエスケープ文字を使用する必要があります。 < < 小なり記号> > 大なり記号 & & そして ' ' 一重引用符 " " 二重引用符 ケース5: 数日前にプロジェクトでこれに遭遇したので、ここでお話しします。経験豊富な方は飛ばしていただいて構いません。selectOne を使って count をクエリする場合… `resultType` を `Integer` として指定し、ビジネスコード内でこのメソッドの戻り値を受け取るために `int` 変数を自然に使用した場合、渡した条件に該当する値がデータベースに見つからない場合、`selectOne` メソッドの戻り値は `null` になります。Java のオートボクシング機構を使用すると、容赦なく NPE(Non-Proof Exception)がスローされます。 理由: Javaはオートボクシングを実行する際に、IntegerクラスのintValueメソッドを呼び出します。現在のオブジェクトがnullの場合、NPE(非証明例外)がスローされます。 したがって、データを受け入れるときに null をチェックする必要があります。そうしないと、例外が発生する可能性があります。 ケース6: 複数のパラメータの使用 MyBatis のクエリまたは更新で複数のパラメータが必要な場合は、いくつかの方法があります。 オブジェクトマッピングはJavaオブジェクトを作成し、それをインターフェースのパラメータとして渡します。オブジェクトのプロパティには、`#{プロパティ名}`という形式を使用して直接アクセスできます。 `Map` メソッドは `Map` をパラメータとして受け取ります。キーはプロパティ名、値はパラメータ値に対応します。このメソッドでは、パラメータを渡す際に一時的な `Map` オブジェクトを作成する必要があります。 `@param` アノテーションは、インターフェース メソッド内のパラメータの前にパラメータ名を追加し、Mapper で名前によって直接アクセスできるようにします。 序数でアクセスします。最初のパラメーターは #{0} または #{param1}、2 番目のパラメーターは #{1}、#{param2} です。 MyBatis での時間フィールドの使用 – 戻り値; 現在、文字列を置き換えて時間フィールドを返しています。 date_format(update_time, '%Y-%c-%d %H:%i:%s') 更新時刻 MySQL の時間フォーマット方法を使用します。 あるいは、Timestamp 型のデータを返す場合は、返されるオブジェクトのプロパティ パラメータが Timestamp である必要があります。 MyBatisの時間フィールドの使用 - パラメータ 特定の時間範囲内のデータをクエリする必要がある場合は、次の動的 SQL クエリ メソッドを使用できます。 および lbr.update_time > #{startTime} および lbr.update_time < #{endTime, javaType=Date, jdbcType=TIMESTAMP} インターフェースメソッド名は次のとおりです。 …日付の開始時刻、日付の終了時刻… この方法はフォーマット変換よりも効率的だと思います。 MyBatisで時間フィールドを使う – ライティング 書き込みでは、次のように、いくつかの JDBCType 情報を記述する必要がある Timestamp データを直接書き込むことができます。 {インストール時間、jdbcType=TIMESTAMP} Mapper レイヤーは Map パラメータを受け取りますが、これは Service レイヤーによってオーバーライドされます。 Mapper は仕組み上、オーバーロードできず、パラメータは通常 Map に設定されます。しかし、これによりパラメータが曖昧になります。コードをより明確にしたい場合は、サービス層を通じてオーバーロードの目的を達成できます。外部に提供されるサービス層はオーバーロードされていますが、これらのオーバーロードされたサービスメソッドは実際には同じ Mapper を呼び出しますが、対応するパラメータは同じではありません。 なぜサービス層でもマップとして設定しないのかと疑問に思う人もいるかもしれません。個人的にはお勧めしません。以前のプロジェクトでは利便性からこのアプローチを多用しましたが、将来のメンテナンスで明らかに問題が生じます。これは、MVCフレームワーク全体がマップモデルに依存するようになるためです。このモデルはフレームワークを構築する上で非常に優れており便利ですが、問題があります。メソッドシグネチャを見ただけでは、マップ内のパラメータの数と型、そして各パラメータの意味がわからないのです。 サービス層またはDAO層のみに変更を加えると想像してみてください。プロセス全体を通して、マップ層から渡されるすべてのパラメータを理解する必要があります。コメントやドキュメントが優れていなければ、どのパラメータが渡されるのかを知るには、各層のコードを理解する必要があります。これはシンプルなMVCであればそれほど問題ではありませんが、層が複雑になるにつれて、コードは非常に複雑になります。新しいパラメータを追加するには、すべての層にコメントを追加する必要があります。コードの制御性を確保するには、コメントよりもメソッドシグネチャを使用する方が現実的です。コメントは古くなる可能性がありますが、メソッドシグネチャは古くなる可能性が低いからです。 メンテナンスの難易度を軽減するために、if ステートメントと choose ステートメントの使用を最小限に抑えます。 MyBatisでSQLを設定する際は、`if`や`choose`などのタグの使用を最小限に抑えてください。将来のメンテナンスを容易にするため、可能な限り条件チェック用のSQL文(`CASE WHEN`、`DECODE`など)を使用してください。そうしないと、SQL文が非常に肥大化し、見苦しくなる可能性があります。MyBatis SQLのデバッグには多数の条件文を削除する必要があり、これは非常に面倒です。さらに、`if`文を過度に使用すると、生成されるSQLに多くのスペースが含まれるようになり、ネットワーク転送時間が長くなるため、これも望ましくありません。 さらに、if 文と choose 文の数が多いと、生成される SQL に毎回矛盾が生じ、Oracle で大量のハード解析が行われることになりますが、これも望ましくありません。 次のような SQL を見てみましょう。 このようなif文は全く不要です。デフォルト値の問題はDECODEを使えば簡単に解決できます。 もちろん、CASE WHENとDECODEを導入するとOracle関数の解析が必要になり、SQL実行速度が低下すると考える方もいるかもしれません。ご興味のある方は、実際にテストして、大きな影響があるかどうかを確認してください。私の個人的な経験では、関数の解析によってSQLが遅くなるケースは一度もありませんでした。SQL実行効率に一般的に影響を与える操作は、JOIN、ORDER BY、DISTINCT、PARTITATION BYであり、これらは通常、テーブル構造の設計と密接に関連しています。これらの操作の効率への影響と比較すると、関数の解析がSQL実行速度に与える影響はごくわずかです。 もう1つのポイントは、デフォルト値の割り当て、例えば上記のSQLのように現在の日付をデフォルトとする処理は、サービス層またはコントローラー層で完全に処理できるということです。MyBatisでは、これらの条件文は最小限に抑えるべきです。これは、キャッシュを困難にするためです。`startdate` が空の場合、SQLで動的な `SYSDATE` を使用すると、`startdate` のキャッシュキーを特定できなくなります。したがって、理想的には、パラメーターはMyBatisに渡す前に処理する必要があります。これにより、MyBatis内の `if` 文と `choose` 文の数が減り、キャッシュが容易になります。もちろん、`if` と `choose` を避けることは絶対ではありません。SQLを最適化するために、`LIKE` 文のように `if` 文を使用する必要がある場合もあります。`LIKE` は一般的には推奨されませんが、記事タイトルを検索する場合など、使用する場合は可能な限り削除してクエリ効率を向上させましょう。最善のアプローチは、Luceneなどの検索エンジンを使用して全文インデックスを処理することです。要約すると、`if` および `choose` 条件分岐を完全に排除することはできませんが、動的分岐を MyBatis に完全に依存するのではなく、ネイティブ SQL メソッドを使用して動的な問題を解決することが推奨されます。動的分岐は過度に複雑で、保守が困難になるためです。 SQL コメントを XML コメントに置き換えます。 Mybatisでは、元のSQLにコメントを残すことは避けるべきです。コメントは問題を引き起こす可能性があるためです。コメントが必要な場合は、XMLで使用して生成されたSQLにコメントが表示されないようにすることで、問題が発生する可能性を低減できます。また、IDEでコメントとSQLを区別しやすくなります。では、コメントによって引き起こされる問題について説明しましょう。私が携わったあるプロジェクトでは、ページネーションコンポーネントはMybatisをベースにしていました。このコンポーネントは、SQLスクリプトを`SELECT COUNT(*) ROWNUM_ FROM (….)`ステートメントで囲み、レコードの総数を計算します。さらに、`SELECT * FROM(…) WHERE ROWNUM > 10 AND RONNUM < 10 * 2`ステートメントをネストしてページネーション情報を生成します。スクリプトの最終行にコメントが含まれている場合、追加された部分がコメントの一部となり、実行時にエラーが発生します。さらに、以下の状況のように、特定の条件が無視される場合もあります。 SELECT * FROM TESTWHERE COL1 > 1 -- これはコメントです AND COL2 = #{a} 入力パラメータに対応するパラメータが存在しても、後続の内容が完全にコメントアウトされているため、効果はありません。このようなエラーは、厳密なテストを行わない限り検出が困難です。一般的に、XMLコメントはSQLコメントを完全に置き換えることができるため、このような動作は禁止する必要があります。 可能な場合は `${}` の代わりに `#{}` を使用してください。 MyBatisでは、`${}`の使用は避けてください。開発には便利ですが、過度に使用するとOracleでハードパースが実行され、データベースのパフォーマンスが低下する可能性があります。実行時間が長くなるほど、パフォーマンスは悪化します。複数の文字列をINする操作を処理するには、次の解決策を参照してください:http://www.myexception.cn/sql/849573.html。この方法では、`${}`に関するほとんどの問題は基本的に解決できます。 `${}` のよくある誤用法として、`LIKE` があります。別の例を挙げましょう。例えば、ツリー構造のメニューでは、ノードは '01'、'0101' のように設計され、2 桁のノードを使用してレベルを区別することがあります。この場合、ノード '01' の下にあるすべてのノードをクエリする必要がある場合、最も単純な SQL は `SELECT * FROM TREE WHERE ID LIKE '01%'` になります。この SQL はインデックスを使用するため、特別な処理は必要なく、そのまま使用すれば問題ありません。ただし、記事のタイトルの場合は、特別な注意が必要です。`SELECT * FROM T_NEWS_TEXT WHERE TITLE LIKE '%OSC%'`。この場合、インデックスは使用されません。前述のように、全文検索が推奨されます。ただし、LIKE がどうしても必要な場合は、使用方法に注意する必要があります。`ID LIKE #{ID} || '%'` の代わりに `ID LIKE '` を使用してください。`||` を使用すると Oracle の処理時間が長くなると考える人もいます。Oracle を過小評価すべきではないと思います。時には非常に非効率的になることがあり、その非効率性と効率的な側面については後でまとめますが、少しテストしてみれば、この連鎖方法が SQL の解析と実行全体に及ぼす影響は無視できるほど小さいことがわかります。もちろん、動的に挿入された列名やテーブル名など、処理できない特殊なケースもあります。これらのケースはより複雑で、私はまだ便利な解決策を見つけていません。このような状況は比較的まれであるため、`||` を使用すると Oracle の処理時間が長くなります。Oracle を過小評価すべきではないと思います。時には非常に非効率的になることがあり、その非効率性と効率的な側面については後でまとめますが、少しテストしてみれば、この連鎖方法が SQL の解析と実行全体に及ぼす影響は無視できるほど小さいことがわかります。もちろん、動的に挿入された列名やテーブル名など、処理できない特殊なケースもあります。これらのケースはより複雑で、便利な解決策はまだ見つかっていません。このような状況は比較的まれなので、`{}` を使用しても大きな影響はありません。もちろん、コードの簡潔さを重視する場合は、Oracle の動的 SQL 実行メカニズムである `Execute immediate` を使用できます。これにより、`${}` の出現が完全に回避されます。ただし、これはより複雑なモデルを導入するため、メリットとデメリットを比較検討する必要があります。動的 SQL によって引き起こされる問題に対処する最も根本的なアプローチは、ストアドプロシージャのみを使用し、データベースでネイティブに問題を解決することです。これにより開発とデバッグが容易になりますが、開発者への要求の高まり、ストアドプロシージャの管理の複雑化などの問題も発生します。私のプロジェクトではこのアプローチを使用していないため、ここではこれ以上詳しく説明しません。 Mybatis の簡単な使い方。 Mybatis の機能は比較的弱く、必要な補助ライブラリや文字列操作機能など、多くの機能が不足しています。拡張性も非常に低く、一般的に戻り値の処理が一部しかできません。そのため、Mybatis は SQL 設定ファイルと基本的な ORM フレームワークとしてのみ使用するのが最善です。Mybatis に過度な動的 SQL を実装することは避けてください。後々のメンテナンスが非常に困難になる可能性があります。 ここにいくつかのヒントがあります: 1. 複数のフィールドをクエリする場合、それらを抽出して SQL ステートメントにインポートできます。 抽出する: id、type、shopCouId、Path、fromDate、toDate、insDate、insUserId、updDate、updUserId、delFlg は以下を導入します: 選択 adinfoより ここで、id = #{id,jdbcType=INTEGER} 2. SQL文で<、>、または""記号を使用する必要がある場合は、< > "または CDATA 文字列内のすべての内容はパーサーによって無視されます。`select type, shopCouId, Path from adinfo WHERE delFlg = '0' and fromDate < #{date} and toDate >= #{date}` 3. キャッシュ CRUD 操作を実行するときに、キャッシュ属性を使用してデータ キャッシュを制御できます。 4. 操作を実行する前に渡されるパラメータを判別できます。
5. SQL文で加算、減算、乗算、除算を直接実行できます。あいまいクエリを使用する場合は、使用方法に注意してください。
MyBatis が 0 を空の文字列 '' として扱い、MySQL が空の文字列 '' を 0 として扱う問題。 データベースフィールドの型が整数の場合、パラメータ変数が空文字列またはNULL値であれば、Mybatisは自動的にパラメータ値0を割り当てます。したがって、整数パラメータの複数の状態をMapperに渡す前に判断したい場合は、値が空文字列またはNULL値であるかどうかを確認し、対応する状態値をパラメータに割り当てる必要があります。そうしないと、パラメータ値が空文字列、NULL、または0のいずれであっても、結果は同じになります。 通常、int 型に関わる操作を処理する場合、サービスはまず数値型を文字列型に変換してから、それを処理のために Mapper に渡します。 ケース7: MyBatis をバッチ挿入に使用すると、データは自動的にリストをキーとするマップにカプセル化されます。保存されるデータは配列になります。XML でカスタムコレクションを使用する場合は、パラメータを渡す際に、独自の変数名でマップのキーを定義する必要があることに注意してください。resultMap を使用する場合は、最初の行に ID を指定する必要があります。そうしないとエラーが発生します。 メリットとデメリット この記事では、MyBatis のメリットとデメリットをまとめ、より包括的な理解を深めていただけるよう努めています。ただし、これらの点はあくまで私の個人的な経験に基づくものであり、一般的な意見を反映していない可能性があります。そのため、「簡単な考察」というタイトルを付けています。異なる見解をお持ちの方は、ぜひお気軽にご意見をお聞かせください。そうすることで、私の理解を深め、知識の不足を補うことができます。 アドバンテージ:
欠点:
要約: MyBatisの強みは弱みでもあります。使いやすさゆえに、MyBatisのデータの信頼性と整合性は、プログラマーのSQLスキルに大きく依存します。XMLでSQLを記述することは、変更、最適化、統一されたブラウジングには便利ですが、可読性が低く、デバッグが難しく、大きな制限があります。JDBCのような複雑な動的SQL連結をコードで実現することはできません。MyBatisの本質は、フィールドマッピングとオブジェクトリレーショナルマッピングを提供するJDBCであり、オブジェクトへのデータ割り当ての手順を省略しています。それ以外に、MyBatisが提供する機能は多くありません。Hibernateほどの強力さは期待できません。MyBatisの最大のメリットは、シンプルさ、コンパクトさ、使いやすさ、そしてSQLのブラウジングと変更の容易さです。 MyBatisは小規模なプロジェクトや、スキルの低いプログラマーがいるユーザーに適しています。中規模から大規模のプロジェクトにはお勧めしません。Hibernateが非効率的だと感じる場合(実際には不適切な使用方法が原因で、Hibernateは高負荷のプロジェクトには適していません)、オブジェクトマッピングもサポートするSpringが提供するシンプルなJDBCフレームワーク(Template)を使用することをお勧めします。 |