1. JDBCの基本動作の復習
疑似コードを使用したプロセスの概要は次のとおりです。
-
- データベースのバージョンに対応したドライバー パッケージをダウンロードし、ドライバー クラスを自分でロードします。
(Class.forName("com.mysql.cj.jdbc.Driver"))
-
- 接続接続を作成します。
conn = DriverManager.getConnection("jdbc:mysql://数据库IP:port/数据库名称?useUnicode=true&characterEncoding=utf8", "用户名", "用户密码");
-
- SQL ステートメントを準備します。
String sql = "select * from lib_book where book_id = ?";
-
- プリペアドステートメントオブジェクトを作成する
PreparedStatement ps = conn.prepareStatement(sql)
-
- 入力パラメータの処理
// 需要根据参数索引位置和参数类型替换对应占位符索引位置上的?为实际的参数值 例如 ps.setInt(1, 12)
ps.setInt(1, 10);
ps.setString(2, "老人与海");
-
- SQL文の実行
クエリ操作:
ResultSet resultSet = ps.executeQuery();
変更操作:
ps.execute();
-
- 返された結果セットを処理する
while(resultSet.next()){
// 取出一行数据来进行处理,映射成java实体类 LibBook book = new LibBook();
book.setBookId(resultSet.getLong(1));
book.setBookIndexNo(resultSet.getString(2));
book.setBookName(resultSet.getString(3));
book.setBookAuthor(resultSet.getString(4));
book.setBookPublisher(resultSet.getString(6));
book.setBookCateId(resultSet.getInt(7));
book.setBookStock(resultSet.getInt(8)); }
-
- 開いているすべてのハンドル オブジェクト (ResultSet、PreparedStatement、Connection) を順番に閉じます。
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
2. Myabtis フレームワークは JDBC 操作を簡素化します
Mybatis フレームワークの使用プロセスを要約します。
1) pom.xml文件中引入Mybatis、日志框架、单元测试框架及数据库驱动依赖
2) 编写mybatis-config.xml全局配置文件、日志配置文件
3) 编写Mapper层接口
4) 编写Mapper层接口对应的xml映射文件, OK搞定。
単体テストの使用プロセス:
1) 创建一个SqlSessionFactoryBuilder对象:SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
2) 创建一个SqlSessionFactory对象:SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
3) 创建一个SqlSession对象:SqlSession sqlSession = factory.openSession();
4) 获取Mapper层指定接口的动态代理对象:LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
5) 调用接口方法获取返回结果即可: List<LibBook> list = mapper.selectAllBook();
操作を比較すると、Mybatis フレームワークを使用した後は、JDBC の特定の操作プロセスに注意を払う必要がなくなったことがわかりました。入力パラメータと返される結果のマッピング変換
手動で処理する必要はありません。リソースの解放を手動で処理する必要はなくなりました。単純なアプリケーション層インターフェイスを呼び出すだけで、すべての操作を完了できます。これはクレイジーです。
しかし、資格のある開発者にとっては、問題が再び発生したときに元のフレームワークを変更および拡張して必要な機能を完成させることができるように、フレームワークの API の使用に習熟し、その原理を明確に理解していなければなりません。 、今日は主に、Mybatis フレームワークが JDBC で入力パラメーターを処理し、結果セットを返す型変換関数をどのようにカプセル化するかについて説明します。
3. Myabtisフレームワークの型変換モジュール
型変換モジュールは、Mybatis フレームワークの基本サポート層モジュールに属しており、主に 数据库中数据
と 主に次の 2 つのシナリオで使用されます。 Java对象中的属性
双向映射
1)在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型;
2)从ResultSet结果集中获取数据时,则需要从JDBC类型转换为Java类型;
3.1 javaType と JDBCType 間の型変換を完了する方法
3.2 Mybatisの設計と実装
3.2.1 TypeHandlerインターフェース
MyBatis フレームワークで提供されるすべての型コンバータ実装クラスは、型コンバータの最も基本的な機能 (入力パラメータと出力パラメータの処理) を定義する TypeHandler インターフェイスを継承します。
/*
* Copyright 2009-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author Clinton Begin
* 类型处理器
*/
public interface TypeHandler<T> {
/**
* 完成SQL语句中实际参数替换占位符的方法
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
* 通过数据库列名称从ResultSet中获取结果数据
*
* @param rs
* the rs
* @param columnName
* Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 通过数据库列索引从ResultSet中获取结果数据
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* 通过数据库列索引从存储过程的执行书写出结果中获取结果数据
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
3.2.2 BaseTypeHandler実装クラス
この基本実装クラスは、共通型変換の基本実装のみを提供し、元の機能を拡張します。
ただし、異なるデータ型の変換は、数値型、文字列型、日付型など、型に応じて処理する必要があり、処理方法が決定的に異なります。以下の一般的に使用されるタイプのコンバータ実装を提供します
タイプハンドラー | Javaの型 | JDBCタイプ |
---|---|---|
ブール型ハンドラー | java.lang.Boolean、ブール値 | データベース互換性のあるブール値 |
ByteTypeHandler | java.lang.Byte、バイト | データベースと互換性のある NUMERIC または BYTE |
ShortTypeHandler | java.lang.短い、短い | データベースと互換性のある NUMERIC または SMALLINT |
IntegerTypeHandler | java.lang.Integer、int | データベースと互換性のある NUMERIC または INTEGER |
LongTypeハンドラ | java.lang.長い、長い | データベースと互換性のある NUMERIC または BIGINT |
FloatTypeHandler | java.lang.Float、float | データベースと互換性のある NUMERIC または FLOAT |
DoubleTypeHandler | java.lang.Double、ダブル | データベース互換性のある NUMERIC または DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | データベースと互換性のある NUMERIC または DECIMAL |
StringTypeHandler | java.lang.String | CHAR、VARCHAR |
ClobReaderTypeHandler | java.io.Reader | - |
ClobTypeHandler | java.lang.String | CLOB、ロングバーチャー |
NStringTypeHandler | java.lang.String | NVARCHAR、NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | - |
ByteArrayTypeHandler | バイト[] | データベース互換性のあるバイト ストリーム タイプ |
BlobTypeHandler | バイト[] | BLOB、ロングバーバイナリ |
DateTypeHandler | java.util.Date | タイムスタンプ |
DateOnlyTypeHandler | java.util.Date | 日付 |
TimeOnlyTypeHandler | java.util.Date | 時間 |
SqlTimestampTypeHandler | java.sql.タイムスタンプ | タイムスタンプ |
SqlDateTypeHandler | java.sql.Date | 日付 |
SqlTimeTypeHandler | java.sql.Time | 時間 |
オブジェクトタイプハンドラー | 他の | または不特定のタイプ |
EnumTypeHandler | 列挙型 | (インデックス順序値ではなく) 列挙名を格納するために使用される VARCHAR または互換性のある文字列タイプ |
EnumOrdinalTypeHandler | 列挙型 | 列挙型の (名前ではなく) 序数値を格納するために使用される互換性のある NUMERIC または DOUBLE 型。 |
SqlxmlTypeHandler | java.lang.String | SQLXML |
インスタントタイプハンドラー | java.time.Instant | タイムスタンプ |
LocalDateTimeTypeHandler | java.time.LocalDateTime | タイムスタンプ |
LocalDateTypeHandler | java.time.LocalDate | 日付 |
LocalTimeTypeHandler | java.time.LocalTime | 時間 |
OffsetDateTimeTypeHandler | java.time.OffsetDateTime | タイムスタンプ |
OffsetTimeTypeHandler | java.time.OffsetTime | 時間 |
ZonedDateTimeTypeHandler | java.time.ZonedDateTime | タイムスタンプ |
YearTypeHandler | java.time.年 | 整数 |
MonthTypeHandler | java.time.Month | 整数 |
YearMonthTypeHandler | java.time.年月 | VARCHAR または LONGVARCHAR |
JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | 日付 |
これは、特定の基本データ型BooleanTypeHandler
クラスを通じて説明されます。
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author Clinton Begin
*/
public class BooleanTypeHandler extends BaseTypeHandler<Boolean> {
/**
* 预处理语句中的非null参数转换(null参数在哪里处理的呢, 在父类BaseTypeHandler里面处理了)
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
throws SQLException {
ps.setBoolean(i, parameter);
}
/**
* ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列名方式"转换成Java类中对应属性的javaType
*/
@Override
public Boolean getNullableResult(ResultSet rs, String columnName)
throws SQLException {
boolean result = rs.getBoolean(columnName);
return !result && rs.wasNull() ? null : result;
}
/**
* ResultSet结果集里面jdbcType返回为null时通过调用该方法"传递列索引方式"转换成Java类中对应属性的javaType
*/
@Override
public Boolean getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
boolean result = rs.getBoolean(columnIndex);
return !result && rs.wasNull() ? null : result;
}
/**
* 存储过程执行结果转换成javaType类型
*/
@Override
public Boolean getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
boolean result = cs.getBoolean(columnIndex);
return !result && cs.wasNull() ? null : result;
}
}
3.2.3 TypeHandlerRegistry レジスタ
Mybatis フレームワークには、使用できる型コンバータの実装が多数用意されています。これらの型コンバータ オブジェクトは、自分で作成する必要はありません。Mybatis がプロジェクトに統合されたときに完成している必要があります。TypeHandlerRegistry レジスタのコンストラクターで、Java の一般的なデータ型と jdbc の対応するデータ型を変換するときに必要な TypeHandler クラス オブジェクトの作成と登録が完了します。 型コンバーターによって実現されます。 は、
はデフォルトの型コンバーターによってインスタンス化されますが、Myabtis フレームワークはこれらのオブジェクトをどのように保存するのでしょうか?これらのオブジェクトのインスタンス化操作はいつ完了しましたか? TypeHandlerRegistry
3.2.4 TypeAliasRegistry エイリアスレジスタ
エイリアスは、MyBatis フレームワークを適用するときによく使用され、コードを大幅に簡素化できます。実際、MyBatis では、エイリアスは TypeAliasRegistry
クラスを通じて管理されます。 TypeAliasRegistry
クラスのコンストラクターには、システム内の一般的な型のエイリアスが挿入されます。
エイリアス レジスタのコア メソッドは次のとおりです。
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748 将别名先转成小写字母
String key = alias.toLowerCase(Locale.ENGLISH);
// 判断别名是否存在
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
// 将别名添加到aliasMap集合中去
typeAliases.put(key, value);
}
Mybatis カスタム エイリアスを登録するには 2 つの方法があります。
- 最初の設定ファイルの方法 (mybatis-config.xml)
<typeAliases>
<package name="com.baidu"/>
</typeAliases>
- 2 番目のアノテーション メソッド @Alias
@Alias("People")
public class SysPeople {
private String name;
}
カスタム エイリアスの登録は、 registerAliases(String packageName, Class<?> superType) メソッドを通じても実行されます。
/**
* 通过package指定别名路径和通过@Alisa注解来注册别名的方法
* 例如我们在全局配置文件中配置的 <typeAliases> <package name="com.baidu"/></typeAliases> 就是通过这个方法来解析处理的
* 还有我们使用@Alias()注解为类注册的别名都是通过这个方法来完成注册的
*/
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}
3.2.5 タイプチェンジャーのカスタマイズ方法
既存のタイプ ハンドラーをオーバーライドしたり、独自のタイプ ハンドラーを作成して、サポートされていないタイプや標準以外のタイプを処理したりできます。具体的な方法は次のとおりです。
org.apache.ibatis.type.TypeHandler インターフェイスを実装するか、
非常に便利なクラス org.apache.ibatis.type を継承します。 BaseTypeHandler 、および (オプションで) JDBC 型にマップできます。
特定の例の公式サンプル コードが提供されています。ご興味があれば、ご自身でドキュメントをご覧ください。ここにポータルを投稿してください: カスタム タイプ コンバータのドキュメントとサンプル コードのアドレス: https://mybatis.org/mybatis-3/zh/configuration.html#typeHandlers
3.3 Mybatis での Typehandler の適用
Mybatis フレームワークや SQL ステートメントの実行プロセスに関係なく、これほど長い期間接触していると、誰もがそれにますます慣れてくるはずです。誰もが SQL ステートメント入力の変換を条件付きで反映できるようにする必要があります。 SQL ステートメントの実行結果のパラメータと処理プロセス ノード。そういえば、SQL ステートメントの実行時に処理する必要があります。Myabtis フレームワークでの SQL ステートメントの実行は、 Executor
に依存して完了します。
SQLのパラメータ処理やResultSetの処理もこのインターフェースの実装クラス内で完結する必要がありますが、ここではあまり気を使わずに剣を振るいます。
3.3.1 入力パラメータの処理
に主に関与するコアクラスは、SimpleExecutor
、PreparedStatementHandler
、DefaultParameterHandler
です。入力情報を見てみましょう。各クラスのパラメータ処理の中核となるメソッド。
SimpleExecutor
SQL ステートメントの前処理の中心となるメソッドは次のとおりです。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 创建数据库连接会话,从这里可以看出数据库连接的创建时延迟创建的, 在真正使用的时候才会创建,这里我们可以做扩展,
// 数据库的读写分离可以通过这个特性通过扩展实现
Connection connection = getConnection(statementLog);
// 创建预处理SQL语句对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 预处理SQL语句对象中的参数占位符替换处理
handler.parameterize(stmt);
return stmt;
}
三件事
1、创建数据库连接会话
2、创建预处理SQL语句对象,
3、如果SQL语句中有查询条件,使用了参数占位符, 就将实际的参数替换成真实的输入参数
上のコードを見てください。これは JDBC のコードに対応していますよね?
Mybatis フレームワークがオブジェクト指向レベルをカプセル化しているだけです。実際には、核心は上記の a>三件事
です。
PreparedStatementHandler
handler.parameterize() メソッドの呼び出しプロセスの分析はここでは省略します。直接説明しましょう。StatementHandler のパラメータ置換関数を完成させるために模板方法模式
を使用します。MappedStatement があるためです。 StatementType
デフォルトのタイプは PREPARED
なので、ここで呼び出されるのは
PreparedStatementHandler
の parameterize()
メソッドです。では输入参数
実際の処理方法を見ていきましょう。
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
上記のメソッドは、ParameterHandler
インターフェース
3.3.2 ParameterHandler オブジェクトはいつ作成されますか?
作成は SimpleExecutor オブジェクトの doQuery() または doUpdate() メソッドで呼び出されました。StatementHanlder
、ParameterHandler
、ResultSetHandler
オブジェクトへの入り口、方法は次のとおりです
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// StatementHanlder、ParameterHandler、ResultSetHandler创建的入口
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 预处理SQL语句对象创建
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行SQL语句并处理结果后返回
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
見てみましょうconfiguration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
この方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建的statementhandler对象是一个RoutingStatementHandler()对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
次のように RoutingStatementHandler の構築メソッドを確認します。
次のように RoutingStatementHandler の構築メソッドを確認します。
もう一度確認してください。< a i=3>、次のように:BaseStatementHandler的构造方法
结论:
在创建实际Statement对象对应的StatementHandler时, 完成了ParameterHandler和ResultHandler对象的创建。
3.3.3 ParameterHandler/ResultSethandler オブジェクトとは何ですか?
上記の説明を続けて、3.3.2 の次の 2 行のコードの実行プロセスを見てみましょうBaseStatementHandler的构造方法
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
Configuration クラスの対応するメソッドは次のとおりです。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 这里我们使用的是**mapper.xml文件声明的SQL语句,所以这里的的LanguageDriver肯定是XMLLanguageDriver
// XMLLanguageDriver.createParameterHandler()方法会创建一个DefaultParameterHandler对象
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 这里我们可以集成参数处理的插件来扩展入参处理的功能
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
// Mybatis框架默认创建的就是DefaultResultSetHandler对象
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 这里我们可以集成结果集处理的插件来扩展结果集处理的功能
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
结论:
如果没有自定义插件, 那么Mybatis框架会为我们创建框架提供的默认的DefaultParameterHandler和DefaultResultHandler对象来实现
SQL语句入参和查询结果集ResultSet转换的功能
3.3.4 DefaultParameterHandlerにおけるパラメータ置換のメソッド実装
DefaultParameterHandler
/**
* 替换SQL语句中的占位符为实际传入的参数值的方法
*/
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 从BoundSql对象中获取到参数映射对象集合
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
// 依次取出parameterMapping对象
ParameterMapping parameterMapping = parameterMappings.get(i);
// 保证当前处理的parameterMapping对象都是输入参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 获取当前处理的parameterMapping对象的属性名称
String propertyName = parameterMapping.getProperty();
// 如果BoundSql对象的附加参数对象中包含该属性名称, 直接从BoundSql对象的附加参数对象中获取到该属性KEY对应的值
if (boundSql.hasAdditionalParameter(propertyName)) {
// issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
// 如果ParameterObject为空,说明没有传值,值直接就为null
} else if (parameterObject == null) {
value = null;
// 如果类型注册器中有该参数对象对应的类型处理器,则该参数取值就是parameterObject
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
// 以上都不满足,就创建一个元数据对象,然后从元数据对象汇总通过属性获取到对应的取值
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取当前parameterMapping对象的类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取当前parameterMapping对象的JDBC数据类型
JdbcType jdbcType = parameterMapping.getJdbcType();
// 如果参数输入值为null并且数据库数据类型为null,就将jdbcType类型设置为OTHER类型
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 为什么这里是使用i + 1?
// insert into t_user(name, age, gender, email) value(?, ?, ?, ?)
// 因为解析出来的带占位的sql语法中的?参数的计数是从1开始的, 不是从0开始的
// 调用typeHandler的替换参数的方法替换到SQL语句中目标位置上占位符上为输入的参数值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
3.3.5 出力結果の処理
に主に関係するコア クラスは、SimpleExecutor
、PreparedStatementHandler
、DefaultResultSetHandler
です。クエリを見てみましょう。各クラス ResultSet 処理の中核となるメソッド。
SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用Statementhandler的query()方法执行查询,获取查询结果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
PreparedStatementHandler
resultSetHandler.handleResultSets(ps) メソッドを呼び出して、クエリ結果セット ResultSet を処理します。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 调用DefaultResultHandler的handleResultSets方法处理SQL语句的执行结果
return resultSetHandler.handleResultSets(ps);
}
3.3.6 DefaultResultSetHandlerでのResultSet結果セット変換のメソッド実装
handleResultSets() メソッド
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理每一行数据从ResultSet转换成java实体类的方法
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
データの各行を処理し、ResultSet から Java エンティティ クラスに変換するメソッド
//
// GET VALUE FROM ROW FOR NESTED RESULT MAP
//
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
final String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (rowValue != null) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
ancestorObjects.remove(resultMapId);
} else {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, true)) {
// 自动完成jdbcType到javaType的映射
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 如果数据库字段和实体类属性无法自动映射, 需要通过该方法完成转换
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
putAncestor(rowValue, resultMapId);
// 如果存在关联查询时, 需要完成来自其他数据表的ResultSet的转换处理
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
自動マッピング中に、対応する TypeHandler に従って、対応する型の値を返すことができます。
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
Property に従って対応する TypeHandler を呼び出し、対応する型の値を返します。
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}