分析MyBatisのソース - 型変換モジュール
序文
SQL文がバインドされているように、Java言語でのJDBCデータ型とデータ型は、完全に対応していないパラメータ、JDBCの型にJavaの型からのPreparedStatementに変換する必要があり、結果セットからデータを取得し、あなたはJDBCからの変換を入力する必要がありますJava型へ。図に示すように変換上記の2種類を使用して、プロセッサのMyBatisの種類、。
MyBatisの中でJdbeType
このデータ型は、列挙型は、列挙型定義JIDBC表しているTYPECODE
JDBCタイプを記録し、フィールドをjavasql.Types
適切な定数をコードにおいて、静的セットをcodelookup
(HashMap<nteger,JdbeTypec>
タイプ)定数JdbcTypeをコードする維持しました対応の間。
タイプハンドラ
TypeHandler
プロセッサインタフェースタイプは、変換のすべてのタイプが継承MyBatisのTypeHandler
中で、インターフェイスをTypeHandler
インターフェイスに次の4つのメソッドの定義は、4つの2つの方法に分割:setParameter()
方法タイプJdbeType用にデータを変換するための責任があるJava
タイプ、getResult()
メソッド、およびそのヘビーデューティーデータの責任はJdbcType型にJavaの型から変換しました。
public interface TypeHandler<T> {
//在通过PreparedStatement为SQL语句绑定参数时,会将数据由JdbcType类型转换成Java类型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//从 ResultSet 中获取数据时会调用此方法,会将数据由Java类型转换成JdbcType类型
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
ユーザ定義TypeHandlerの便宜上BaseTypeHandler MyBatisの抽象クラスを実装することを条件とする、実装TypeHandler
インタフェース及び図1に示す構造を継承継承TypeReference抽象クラス、。
BaseTypeHandler
実装TypeHandler.setParameter()
方法及びTypeHandler.getResult()
以下のような方法、特定の実装です。null以外のデータを扱うためのこれらの2つの方法は、サブクラスの実装に引き渡されることに留意されたいです。
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
//参数 类型 异常
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
//绑定参数为null的处理
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
//绑定非空参数 该方法为抽象方法 由子类实现
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
//抽象方法 由多个重载 由子类实现
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
BaseTypeHandler
より多くの実装クラスとして示されたが、大部分は直接呼び出しPreparedStatement
及びResultSet
又はCallableStatement
対応する方法、比較的単純な達成します。
ここにするためのIntegerTypeHandler
例の簡単な紹介は、他の実装クラスのリーダーは、関連するソースコードと呼ばれます。
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
//调用PreparedStatement.setInt()实现参数绑定
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
//调用PreparedStatement.getInt()获取指定列值
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
//调用ResultSet.getInt()获取指定列值
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
//调用CallableStatement.getInt()获取指定列值
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
一般的に、TypeHandler
ジャワの複数列に値が必要性が存在するオブジェクト場合に完全な変換型の単列と単一のパラメータ値のため、我々は、(マッピングファイル内のマッピングルールの適切な定義の使用を優先する必要があり<resultMap>
マッピングノードを完了するために)。
TypeHandlerRegistry
入門TypeHandler
インタフェースとその機能は、我々は見てMyBatis
の大規模な数の管理方法のTypeHandler
どの使用する際に知ってどのように、インタフェースをTypeHandler
変換を完了するためのインターフェイスを。これらはTypehandlerRegistry
完了MyBatis
初期化プロセスは、すべての既知提供しますTypeHandler
オブジェクトを作成し、達成するために登録されているTypeHandlerRegistry
中でTypeHandlerRegistry
責任をあなたにこれらのハングTypeHandler
のオブジェクトを。
TypeHandlerRegistry
次のようにフィールドの意味コアは、以下のとおりです。
//记录JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型
//该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
//记录了Java类型向指定JdbcType转换时,需要使用的TypeHandler对象。例如: Java 类型中的String可能
//转换成数据库的char、varchar等多种类型,所以存在一对多关系
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
//记录了全部TypeHandler类型以及该类型相应的TypeHandler对象
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
//空TypeHandler集合的标识
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
1.登録TypeHandler
オブジェクト
TypeHandlerRegistry.register()
オブジェクトの機能を実現するTypeHandler方法をサイン、登録プロセスは、4組に追加されますTypeHandler
オブジェクト。register()
この方法は、過負荷、複数の図の過負荷に示される呼の関係を有しています。
図、レジスタ()メソッドのほとんどは、最終的に呼び出しますから見ることができ、オーバーロード④登録機能を完了し、我々は最初の三つのパラメータを処理することができるタイプTypeHandlerのJava、JDBC型のオブジェクトとTypeHandlerを指定達成するための方法を紹介。
//register()方法的重载④的实现如下:
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) { //检验是否明确指定了TypeHandler能够处理的Java类型
//获取指定Java类型在TYPE_ HANDLER_ MAP集合中对应的TypeHandler集合
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
//创建新的TypeHandler集合,并添加到TYPE HANDLER MAP中
map = new HashMap<>();
typeHandlerMap.put(javaType, map);
}
//将TypeHandler对象注册到typeHandlerMap集合中
map.put(jdbcType, handler);
}
//向AllTypeHandler集合注册TypeHandler类型和对应的TypeHandler对象【以Class为key handler对象为value】
allTypeHandlersMap.put(handler.getClass(), handler);
}
この3つの①〜③でregistr()
メソッドのオーバーロードが読みしようとTypeHandler
して定義されたクラス@MappedTypes
の注釈と@MappedJdbcTypes
、ノートを@MappedTypes
指定するためのアノテーションをTypeHandler
達成するために処理できるJavaクラスタイプのセットを、@MappedJdbcTypes
指定されたため、アノテーションTypeHandler
の実装クラスは、で処理できるJDBC
タイプのセット。register()
次のように過負荷①〜③具体的な方法は次のとおりです。
//register()方法的重载①的实现如下:
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
//获取 @MappedTypes 注解
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
//经过强制类型转换 以及反射创建TypeHandler对象之后,交由重载③继续处理
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
//未@MappedTypes 注解,调用重载方法 ② 处理
register(getInstance(null, typeHandlerClass));
}
}
//register()方法的重载②的实现如下:
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
//获取@MappedTypes注解 并根据MappedTypes注解指定的java类型进行注册,逻辑与重载①类似
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
//交由重载③进行处理
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
//从3.1.0版本开始,可以根据TypeHandler类型自动查找对应的Java类型,这需要
//我们的TypeHandler实现类同时继承TypeReference这个抽象类
// @since 3.1.0 - try to auto-discover the mapped type
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
//交由重载③进行处理
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
//类型转换后,交由重载③继续处理
register((Class<T>) null, typeHandler);
}
}
//register()方法的重载③的实现如下:
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
//获取MappedJdbcTypes注解
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
//根据指定类型进行注册 读取@MappedJdbcTypes value值 获取handlerJdbcType
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
//未指定MappedJdbcTypes注解 交由重载④继续处理
register(javaType, null, typeHandler);
}
}
上記のすべてregister()
の方法はでオーバーロードされたTYPE_ HANDLER_ _MAP
セットとALL_TYPE_HANDLERS MAP
登録TypeHandlerオブジェクトセットは、⑤にオーバーロードされるJDBC_ TYPE_ HANDLER_ MAP
コレクション登録TypeHandler
被写体次のように、特定の実装です。
//register()方法重载⑤的实现如下:
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
// 注册JdbcType类型对一个的TypeHandler
jdbcTypeHandlerMap.put(jdbcType, handler);
}
TypeHandlerRegistry
単一の登録提供することに加えてTypeHandler
のregister()
過負荷になり、また、パケット全体のスキャンできるTypeHandler
インターフェースクラスを、そして完了TypeHandler
登録実装クラスを。⑥ヘビーデューティーの具体的な実現で見てみましょう:
//register()方法重载⑥的实现,主要用来自动扫描指定包下的TypeHandler实现并完成注册
public void register(String packageName) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//查找指定包下的TypeHandler接口实现类
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
for (Class<?> type : handlerSet) {
//Ignore inner classes and interfaces (including package-info.java) and abstract classes
//过滤调内部类、接口以及抽象类
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
//交由重载①继续后面注册操作
register(type);
}
}
}
最後に視聴TypeHandlerRegistry
上記によってコンストラクタ意志register()
方法は、多くの種類の基礎は、対応する登録されTypeHandler
、次のようにオブジェクトコードが簡単になります。
public TypeHandlerRegistry(Configuration configuration) {
//...这里重点看下String相关的TypeHandler对象的注册 其他类型的TypeHandler的注册类型(略)
// stringTypeHandler 能够将数据从String 类型转换成null ( JdbcType ),所以向
TYPE HANDLER MAP
//集合注册该对象,并向ALL_ TYPE_ HANDLERS_ MAP集合注册StringTypeHandler
register(String.class, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
//向JDBC_TYPE_HANDLER_MAP集合注册NVARCHAR对应的NStringTypeHandler
register(JdbcType.NVARCHAR, new NStringTypeHandler());
// ...注册其他JdbcType类型对应的TypeHandler的过程类似(略)
}
TypeHandlerオブジェクトを検索2
登録TypeHandlerオブジェクト関数を導入した後、TypeHandlerRegistryが設けTypeHandlerオブジェクト関数を発見するために来ます。TypeHandlerRegistry.getTypeHandler()メソッドは、4つの特徴の前記セットからの対応するTypeHandlerを得る目的を達成します。過負荷の複数のTypeHandlerRegistry.getTypeHandler()メソッドは、これらの過負荷の関係を図に示します。
キャストの一連の後、TypeHandlerRegistry.getTypeHandler()
複数のオーバーロードされたメソッドが呼び出されますTypeHandlerRegistry.getTypeHandle(Type,JdbcType)
オーバーロードメソッドを、指定に応じてなりJava
種類とJdbcType
適切なタイプの検索TypeHandler
オブジェクトを、具体的には以下を達成します。
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
//查找或初始化Java类型对一个的TypeHandler对象
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
//根据JdbcType查找TypeHandler
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
//如果JdbcType只注册了一个TypeHandler,使用此TypeHandler对象
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
ではTypeHandlerRegitry.getJdbcHandlerMap()
、プロセスは、検出したTYPE_HANDLER_MAP
コレクション指定Java
に対応するタイプTypeHandler
のセットが初期化されているかどうかを。初期化されていない場合は、Java型にしようと、対応する初期化された親クラスTypeHandler
の初期セットのセットは、親が存在しないとして、すでに初期化され、対応するTypeHandler
セットがに初期化されるNULL_TYPE_HANDLER_MAP
アイデンティティ。getJdbcHandlerMap()
次のように実装の詳細:
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
//查找指定Java类型对应的TypeHandler集合
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { //检测是否为空集合标识
return null;
}
//初始化指定的Java类型的TypeHandler集合
if (jdbcHandlerMap == null && type instanceof Class) {
Class<?> clazz = (Class<?>) type;
if (Enum.class.isAssignableFrom(clazz)) {
Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
if (jdbcHandlerMap == null) {
//枚举类型的处理
register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
return typeHandlerMap.get(enumClass);
}
} else {
//查找父类对应的TypeHandler集合,并作为初始化集合
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
// 以NULL_TYPE_HANDLER_MAP作为TypeHandler集合
typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
// getJdbcHandlerMapForSuperclass()实现
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class.equals(superclass)) {
return null; //父类为Object或null则查找结束
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
if (jdbcHandlerMap != null) {
return jdbcHandlerMap;
} else {
//继续递归查找父类对应的TypeHandler集合
return getJdbcHandlerMapForSuperclass(superclass);
}
}
TypeHandlerRegistry.getMappingTypeHandler()
この方法は、指定された型から直接TypeHandlerますALL_TYPE_HANDLERS_MAP
コレクション見つけるTypeHandler
オブジェクトを。TypeHandlerRegistry.getTypeHandler(JdbcType)
この方法は、特定に基づいて行われますJdbcType
タイプから、JDBC_TYPE_HANDLER_MAP
コレクションを見つけるためにTypeHandler
オブジェクトを。これらの2つの比較的簡単な方法は、コードが掲載されていません。
最後に、他にMyBatis
提供し、それ自体TypeHandler
の実装を、我々はまた、カスタム追加できるTypeHandler
追加の方法で、インタフェースをmybatisconfig.xml
コンフィギュレーションファイルを<typeHandlers>
、ノードで適切な追加<typeHandler>
ノード構成を、カスタム指定TypeHandler
そのインタフェースの実装。MyBatis
初期化ノードを解析し、TypeHandler
オブジェクトタイプが登録されているTypeHandlerRegistry
ためにMyBatis
その後の使用。後述するMyBatis
初期設定と呼ぶことにします。
TypeAliasRegistry
エイリアスを使用してSQL文を書くときには簡単に長いたとえば、テーブルやカラム名に、我々は一般的に簡単に理解し、そのエイリアスのデザインを維持するために、理解し、維持することができます。MyBatis
SQLステートメントの別名の拡張機能と概念の拡大、MyBatis
クラスとしてエイリアスを追加することができ、そのクラスは、別名で参照することができます。エイリアス機能クラスの登録及び管理の完了これにより、比較的簡単な構造で、フィールド(管理型別名)によってタイプとの対応関係、登録を完了する方法別名、特定の実装方法は次の通りであります:MyBatis
TypeAliasRegistry
TypeAliasRegistry
TYPE_ ALIASES
Map<String, Class<?>>
Java
TypeAliasRegistry.registerAlias()
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() + "'.");
}
//注册别名
typeAliases.put(key, value);
}
でTypeAliasRegistry
構築プロセス、デフォルトは、基本タイプおよびJava配列型、ベース型および配列型ラッパークラスであるDate
、BigDecimal
、BigInteger
、Map
、HashMap
、List
、ArrayList
、Collection
、Iterator
、ResultSet
及び別名を追加し、他のタイプのコードは比較的簡単であり、読者は参照されるTypeAliasRegistry
にソース学習。
TypeAliasRegistry
2つの方法が説明する必要があり、registerAliases(String,Class<?>)
大型パッケージは、指定されたすべてのクラスをスキャンし、指定されたクラスのサブクラスとして別名を追加します。registerAlias(Class<?>)
過負荷が読みしようと@Alias
ノートを。次のようにこれらの2つの方法を達成:
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
//查找指定包下的superType类型类
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注解
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
//检测次别名不存在后,会将其记录到TYPE_ALIASES集合中
registerAlias(alias, type);
}
この記事では、ライセンスに3.0 CN契約によってCCを使用して、Jankerが執筆しました。それは自由に、再現名前の著者で引用されたとの記事のソースを示すことができます。マイクロチャネルパブリック数に関して再版、最終的に二次元コードを公共の数を追加します。