序文
多くの人がMyBatisを使用しているかもしれませんが、MyBatisのSQL実行プロセスは誰にとっても明確ではないかもしれません。ここにいるので、この記事を読んだ後、次のことがわかります。
-
1.マッパーインターフェイスとマッピングファイルをバインドする方法
-
2.MyBatisでのSQLステートメントの実行プロセス
-
3.MyBatisでパラメーター設定プロセッサーtypeHandlerをカスタマイズします
-
4.MyBatisで結果セットプロセッサtypeHandlerをカスタマイズします
利点:MyBatisの研究ノートとソースコード分析
PS:この記事はMyBatis3.5.5バージョンのソースコードに基づいています
概要
MyBatisでのプログラムによるデータクエリの使用、主に次のコード行。
SqlSession session = sqlSessionFactory.openSession();UserMapper
userMapper = session.getMapper(UserMapper.class);List<LwUser> user
List = userMapper.listUserByUserName("孤狼1号");
最初の行はSqlSessionオブジェクトを取得するためのものです。前の記事で分析しました。2行目はUserMapperインターフェイスを取得するためのものです。3行目のコードはクエリステートメントフロー全体を実装します。次に、2番目と3番目を注意深く分析します。ステップ。
Mapperインターフェースの取得(getMapper)
2番目のステップは、SqlSessionオブジェクトを介してMapperインターフェイスを取得することです。このプロセスは比較的簡単です。以下は、session.getMapperメソッドを呼び出した後の実行シーケンス図です。
- 1. getMapperを呼び出した後、プロジェクトの開始時にMapperインターフェイスがロードされ、解析されてConfigurationオブジェクトに格納されるため、MapperオブジェクトはConfigurationオブジェクトから取得されます。
- 2.ConfigurationオブジェクトのMapperRegistryオブジェクトプロパティを介してgetMapperメソッドを引き続き呼び出します。
- 3.タイプタイプに応じて、MapperRegistryオブジェクトのknownMappersから現在のタイプに対応するプロキシファクトリクラスを取得し、プロキシファクトリクラスを介してマッパーに対応するプロキシクラスを生成します。
- 4.最後に、インターフェイスに対応するプロキシクラスMapperProxyオブジェクトを取得します
また、MapperProxyは、InvocationHandlerが実装され、JDK動的プロキシが使用されていることを確認できます。
この時点で、マッパーを取得するプロセスは終了しました。次に、質問があります。HashMapプロパティのデータがMapperRegistryオブジェクトのknownMappersに格納されているのはいつですか。
マッパーインターフェイスとマッピングファイルはいつ関連しますか?
マッパーインターフェイスとそのマッピングファイルは、mybatis-config構成ファイルがロードされるときに保存されます。タイミング図は次のとおりです。
- 1.最初に、SqlSessionFactoryBuilderメソッドでbuild()メソッドを手動で呼び出します。
- 2.次に、XMLConfigBuilderオブジェクトが作成され、その解析メソッドが呼び出されます。
- 3.次に、独自のparseConfigurationを呼び出して、構成ファイルを解析します。構成ファイルは、それぞれグローバル構成ファイルの最上位ノードを解析します。他のノードは調べません。マッパーノードの最終的な分析を直接調べます。
- 4.引き続き独自のmapperElementを呼び出してマッパーファイルを解析します(このメソッドはスクリーンショットを完成させるために比較的長いため、フォントが1つ減ります)、マッパーノードを解析する方法は4つあることがわかります。構成、対応4つのマッパー構成方法があり、赤いボックス内の2つの方法は直接構成されたxmlマッピングファイルであり、青いボックス内の2つの方法は、マッパーインターフェイスを解析して直接構成する方法です。説明することもできます。ここから、どの構成が構成されているかに関係なく、最終的に、MyBatisはxmlマッピングファイルをマッパーインターフェイスに関連付けます。
- 5.最初に2番目と3番目を見てみましょう(xmlマッピングファイルの解析メソッドを直接構成します)。XMLMapperBuilderオブジェクトが作成され、その解析メソッドが呼び出されます。
もちろん、これは引き続き解析されます。後でクエリが実行されると、クエリがトラバースされて分析が再度完了します。ただし、相互に参照すると分析が失敗し、エラーが報告されることに注意してください。開発プロセスでは、循環依存の生成を回避する必要があります。
- 6.マッピングファイルを解析した後、独自のメソッドbindMapperForNamespaceを呼び出して、マッパーインターフェイスとマッピングファイルのバインドを開始します。
- 7、構成オブジェクトのaddMapperを呼び出します
- 8. ConfigurationオブジェクトのプロパティMapperRegistryでaddMapperメソッドを呼び出します。このメソッドは、MapperインターフェイスをknownMappersに正式に追加するため、上記のgetMapperを直接取得できます。
この時点で、マッパーインターフェイスとxmlマッピングファイルのバインドが完了しました。
- 9.上の赤いボックスのコードに注意して、parseメソッドを再度呼び出します。このparseメソッドは、主に次のステートメントなどの注釈を解析するためのものです。
@Select("select * from lw_user") List<LwUser> listAllUser();
したがって、このメソッドは@Selectおよびその他の注釈を解析します。マッパーノードには4つの構成メソッドがあり、そのうち2つはMapperインターフェースで構成され、Mapperインターフェースを構成すると、マッピングファイルを解析せずに、最初にaddMapperインターフェースが直接呼び出されるため、注釈解析メソッドparseを入力すると、XMLマッピングファイルの解析を再試行する必要があります。
分析が完了すると、マッパーインターフェイスのメソッドが解析され、各メソッドの完全修飾クラス名が、構成のmappedStatementsプロパティのキーとして格納されます。
ここに格納する場合、同じ値が2回格納され、1つの完全修飾名がキーとして使用され、もう1つはメソッド名(SQLステートメントのID)のみをキーとして使用することに注意してください。
したがって、最終的なmappedStatementsは次のようになります。
実際、インターフェイスを介してプログラムする場合、最終的にgetStatementに到達すると、完全修飾名に基づいて取得されるため、名前が重複していても影響はありません。この理由は、実際には以前のバージョンの使用法、つまり、インターフェースを介してではなく、メソッド名を介して直接クエリを実行します。
session.selectList("com.lonelyWolf.mybatis.mapper.UserMapper.listAllUser");
ここで、shortNameが繰り返されていない場合は、省略形で直接照会できます。
session.selectList("listAllUser");
ただし、shortNameが繰り返されると、省略形でクエリを実行すると、次の例外がスローされます。
ここでの例外は、実際にはStrickMapのgetメソッドによってスローされます。
SQL実行プロセス分析
前述のように、取得したマッパーインターフェースは実際にはプロキシオブジェクトとしてパッケージ化されているため、プロキシオブジェクトの実行方法としてクエリステートメントを実行する必要があります。次に、マッパーインターフェースのプロキシオブジェクトMapperProxyを使用してクエリプロセスを分析します。
SQL実行プロセス全体は、次の2つの主要なステップに分けることができます。
-
1つは、SQLを探す
-
2、SQLステートメントを実行します
SQLを探しています
まず、sqlステートメントを見つけるシーケンス図を見てみましょう。
- 1.プロキシモードを理解している人は、プロキシオブジェクトのメソッドを呼び出した後にプロキシオブジェクトのinvokeメソッドが実際に実行されることを知っておく必要があります。
- 2.ここではObjectクラスのメソッドを呼び出さなかったため、他の場所に移動する必要があります。それ以外の場合は、MapperProxyの内部クラスMapperMethodInvokerのメソッドcachedInvokerを引き続き呼び出します。デフォルトのメソッドはJdk1.8のインターフェイスに追加できるため、デフォルトのメソッドであるかどうかを判断する判断があります。デフォルトのメソッドは次のとおりです。抽象メソッドではないため、特別な処理が必要です(最初はキャッシュから取得されます。ここではキャッシュ関連の知識については説明しません。後でキャッシュを分析するために、別の記事を作成します))。
- 3.次に、MapperMethodオブジェクトを作成します。これは、対応するメソッド情報をMapperインターフェイスにカプセル化し、対応するSQLステートメント情報をカプセル化します。
これにより、実行されるすべてのSQLステートメントが解析されてカプセル化され、パラメーターとメソッドの戻り値がMapperMethodオブジェクトに要求され、後でSQLステートメントを実行する準備が整います。
SQLステートメントを実行します
まず、SQLステートメントを実行するシーケンス図を見てみましょう。
- 1.上記のプロセスを続行して、executeメソッドを入力します。
- 2.これにより、ステートメントの種類と戻り値の種類に応じて実行方法が決まります。ここで返すのはコレクションなので、executeForManyメソッドを入力します。
- 3.これは、最初に以前に保存されたパラメーターを変換し、次にそのような円を一周し、開始点SqlSessionオブジェクトに戻り、selectListメソッドを呼び出し続けます。
- 3.次に、プロセスはExecuteに委任されてqueryメソッドを実行し、最後にqueryFromDatabaseメソッドが呼び出されます。
- 4.ここに到着したら、いよいよトピックを入力します。一般的に、これから始まるメソッドは実際に実行されており、この命名メソッドはSpringの多くの場所でも使用されています。
前のSQLステートメントはまだプレースホルダーであり、パラメーターは設定されていないため、ここでは、prepareStatementメソッドを呼び出して上記の行にStatementオブジェクトを作成すると、パラメーターが設定され、プレースホルダーが置き換えられることに注意してください。パラメータの設定方法、最初はスキップしましょう。プロセスが終了したら、パラメータマッピングと結果セットマッピングを別々に分析します。
- 5. PreparedStatementHandlerオブジェクトのqueryメソッドの入力を続けます。このステップは、jdbc操作オブジェクトPreparedStatementのexecuteメソッドを呼び出すことであり、最後のステップは、結果セットを変換して返すことです。
この時点で、SQLステートメント実行プロセス全体の分析は終了します。パラメーター変換はコアではないため、一部のパラメーターは途中で保存および変換されます。データフロープロセス全体を知っている限り、独自の実装。保存し、最終的に再分析して読み取ることができる限り、その方法。
パラメータマッピング
次に、クエリを実行する前に、上記のパラメータがどのように設定されているかを見てみましょう。まず、prepareStatementメソッドを入力します。
最後に、StatementHandlerのparameterizeが呼び出されてパラメーターが設定されたことがわかりました。次に、スペースを節約するために、ステップバイステップではなく、パラメーターの設定方法を直接入力します。
上記のBaseTypeHandlerは抽象クラスであり、setNonNullParameterは実装されておらず、実装するサブクラスに渡され、各サブクラスはデータベースのタイプに対応しています。次の図は、デフォルトのサブクラスStringTypeHandlerであり、他のロジック、つまりパラメーターの設定は含まれていません。
StringがjdbcのsetStringメソッドを呼び出し、それがintの場合は、setIntメソッドも呼び出すことがわかります。
これらのサブクラスを見て、前に説明したMyBatisパラメーター構成を読んだ場合、これらのサブクラスがデフォルトでシステムによって提供されるいくつかのtypeHandlerであることは明らかです。そして、これらのデフォルトのtypeHandlerは、デフォルトで登録され、Javaオブジェクトにバインドされます。
MyBatisがデフォルトで一般的に使用されるデータ型のマッピングを提供するため、SQLを書き込むときにパラメータのマッピング関係を省略できます。次のメソッドを直接使用できます。システムは、のタイプに応じてマッピングに適切なtypeHanderを自動的に選択できます。私たちのパラメータ:
select user_id,user_name from lw_user where user_name=#{userName}
上記のステートメントは、実際には次のステートメントと同等です。
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR}
または、typeHandlerを直接指定することもできます。
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=org.apache.ibatis.type.IntegerTypeHandler}
ここでは、typeHandlerを構成しているため、構成済みのtypeHandlerを優先し、デフォルトのマッピングを読み取りません。タイプが一致しない場合、エラーが直接報告されます。
多くの人は、typeHandlerを自分でカスタマイズすれば、それを独自のカスタムクラスとして構成できることを知っておく必要があります。
それでは、typeHandlerをカスタマイズする方法を見てみましょう
カスタムtypeHandler
カスタムtypeHandlerは、BaseTypeHandlerインターフェイスを実装する必要があります。BaseTypeHandlerには、結果セットのマッピングを含む4つのメソッドがあります。スペースを節約するために、コードは記述されていません。
package com.lonelyWolf.mybatis.typeHandler;import org.apache.ibatis.type.BaseTypeHandler;import org.apache.ibatis.type.JdbcType;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;public class MyTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement preparedStatement, int index, String param, JdbcType jdbcType) throws SQLException { System.out.println("自定义typeHandler生效了"); preparedStatement.setString(index,param); }
次に、上記のクエリステートメントを書き直します。
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.typeHandler.MyTypeHandler}
次に実行すると、カスタムtypeHandlerが有効になっていることがわかります。
結果セットのマッピング
次に、上記のSQLプロセスを実行する最後の方法に戻って、結果セットのマッピングを見てみましょう。
resultSetHandler.handleResultSets(ps)
結果セットのマッピングのロジックは比較的複雑です。多くの状況を考慮する必要があるため、ここではすべての詳細に立ち入るわけではなく、正式な分析結果セットのコードを直接入力します。次の5つのコードスニペットです。これは単純です。しかし、完全な分析プロセス:
[外部リンク画像の転送に失敗しました。ソースサイトにヒル防止リンクメカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします(img-07TajOBy-1617021981095)(https://upload-images.jianshu.io/ upload_images / 26085619-98641fb2e7962100?imageMogr2 / auto-orient / strip%7CimageView2 / 2 / w / 1240)]
上記のコードスニペットから、実際には、結果セットの解析は依然として非常に複雑であることがわかります。前の記事で紹介した複雑なクエリと同様に、クエリは他のクエリを継続的にネストでき、次のような複雑さがあります。読み込みの遅延。
プロセスの特性であるため、ロジックのブランチが多数ありますが、最終的にはプロセスのコアセットを上回り、最終的にtypeHandlerを呼び出して結果へのクエリを取得します。
はい、あなたはそれを正しく推測しました。typeHandlerは単なるパラメーター設定メソッドではなく、結果セットメソッドでもあるため(上記のパラメーターを設定する場合は省略)、これは上記のパラメーターをマップしたtypeHandlerです。
カスタムtypeHandler結果セット
したがって、上記のMyTypeHandlerの例を使用して、valueメソッドを書き直します(設定パラメーターメソッドは省略されています)。
package com.lonelyWolf.mybatis.typeHandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class MyTypeHandler extends BaseTypeHandler<String> {
/**
* 设置参数 *
/
@Override public void setNonNullParameter
(PreparedStatement preparedStatement, int index, String param, JdbcType jdbcType)
throws SQLException {
System.out.println("设置参数->自定义typeHandler生效了");
preparedStatement.setString(index,param); }
/** * 根据列名获取结果 */
@Override public String getNullableResult(ResultSet resultSet, String columnName)
throws SQLException { System.out.println("根据columnName获取结果->自定义typeHandler生效了");
return resultSet.getString(columnName); }
/** * 根据列的下标来获取结果
*/
@Override public String getNullableResult(ResultSet resultSet, int columnIndex)
throws SQLException { System.out.println("根据columnIndex获取结果->自定义typeHandler生效了");
return resultSet.getString(columnIndex); }
/** * 处理存储过程的结果集 */
@Override public String getNullableResult(CallableStatement callableStatement,
int columnIndex) throws SQLException {
return callableStatement.getString(columnIndex); }}
マッパーマッピングファイルの構成を書き直します。
<resultMap id="MyUserResultMap" type="lwUser">
<result column="user_id" property="userId" jdbcType="VARCHAR"
typeHandler="com.lonelyWolf.mybatis.typeHandler.MyTypeHandler" />
<result column="user_name" property="userName" jdbcType="VARCHAR" />
</resultMap><select id="listUserByUserName" parameterType="String" resultMap="MyUserResultMap">
select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.
typeHandler.MyTypeHandler}
</select>
実行後の出力は次のとおりです。
属性に設定した属性は1つだけなので、出力は1回だけです。
作業フローチャート
上記でコードの流れを紹介しましたが、周りを回るのは少しめまいがするかもしれません。そこで、MyBatisのメインワークフローをより明確に示すために、メインオブジェクト間にフローチャートを描きましょう。
上記の作業フローチャートから、SqlSessionの下に4つの大きなオブジェクトがあり、これらの4つの大きなオブジェクトも非常に重要であることがわかります。後でインターセプターを学習するときに、これらの4つの大きなオブジェクトをインターセプトします。これらの4つの大きなオブジェクトについて具体的な詳細については、次の記事で分析します。
総括する
この記事では、主にMyBatisのSQL実行プロセスを分析します。プロセスを分析するプロセスでは、typeHandlerをカスタマイズして、カスタムパラメーターマッピングと結果セットマッピングを実現する方法も示しましたが、MyBatisで提供されるデフォルトのマッピングは、いくつかの属性が必要な場合、実際にはほとんどのニーズを満たすことができます。カスタムtypeHandleを使用して実現できます。この記事を理解していれば、少なくとも次の点を明確に理解している必要があると思います。
-
1.マッパーインターフェイスとマッピングファイルをバインドする方法
-
2.MyBatisでのSQLステートメントの実行プロセス
-
3.MyBatisでパラメーター設定プロセッサーtypeHandlerをカスタマイズします
-
4.MyBatisで結果セットプロセッサtypeHandlerをカスタマイズします
もちろん、詳細の多くは言及されておらず、ソースコードを見ると、理解するためにコードのすべての行を追求する必要はありません。たとえば、プロジェクトであっても、ビジネスシステムは少し複雑です。開発者、特定のモジュールが私たちのものでない場合、責任があるので、コードの各行の意味を理解するのは難しいと思います。したがって、MyBatisや他のフレームワークのソースコードについても同じです。まず、全体的な状況から始めて、全体的なプロセスと設計のアイデアを習得し、次に、実装の詳細に興味がある場合は、 -深い理解。