JdbcType
mybatis关注两种类型:数据库使用的类型及Java类型,Java类型通过别名处理,数据库使用的类型用JdbcType表示,相关接口与类定义在包org.apache.ibatis.type中
java.sql.Types通过静态int常量定义了SQL类型,也即JDBC类型
JdbcType是mybatis定义的一个枚举类,定义了一个码与java.sql.Types之间的关系
public enum JdbcType {
// 如ARRAY对应了Types中的ARRAY
ARRAY(Types.ARRAY),
// 如INTEGER对应了Types中的INTEGER
INTEGER(Types.INTEGER),
// 还有很多,不一一列出
public final int TYPE_CODE; //类型码,即Types中的静态常量
// 所有的JdbcType存在这个Map中,键是码,值为类型对象,初始时都存在这个集合中
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
// 静态初始化块
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
// 枚举类的构造方法
JdbcType(int code) {
this.TYPE_CODE = code;
}
// 静态方法,根据码获得对应的JdbcType对象
public static JdbcType forCode(int code) {
// 从集合中通过码获得JdbcType对象
return codeLookup.get(code);
}
}
类型别名及注册
mybatis提供了通过类型别名查找相应Java类型的机制,所有的类型都通过别名在注册器中查找,获得对应的Class对象,如有需要再创建这个类的对象。mybatis启动时,会注册系统默认的别名及自定义别名,在解析映射文件的属性parameterType="Student"及resultType="Student"时,将值作为类型别名,在类型注册器中查找别名对应的类型,如果没有找到,则作为类型的全类名获得Class对象,如果名字不是全类型名,则抛出异常。
TypeAliasRegistry:是类型别名注册器。使用一个Map存放别名(键-字符串)及对应的类型(值-Class对象),在TypeAliasRegistry的无参构造方法中注册了多个常用类型的别名。并提供了几个注册别名的重载方法registerAlias,及根据别名获得对应类型的方法resolveAlias
// MyBatis类型别名注册器
public class TypeAliasRegistry {
// 使用一个HashMap存放类型别名及对应的类型
// key: 别名(字符串),value:别名对应的类型,用Class对象表示
private final Map<String, Class<?>> typeAliases = new HashMap<>();
// 构造方法中,注册了预定义的别名
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
// 还有很多,不一一列出,可直接看源码
}
// 多个重载的注册别名的方法,这是其中之一,其他方法都通过这个方法注册别名
public void registerAlias(String alias, Class<?> value) {
// 别名不能为null
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// 别名都转为小写换
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);
}
// 通过Class.forName找到value的Class对象,调用上面的方法
public void registerAlias(String alias, String value){...}
// 若类前有注解Alias,有则取到其中的值,作为类型别名,否则使用类型的简单名作为别名
public void registerAlias(Class<?> type) {
// 获得类型的简单名
String alias = type.getSimpleName();
// 类型前有无注解@Alias
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
// 有用其值作为别名
alias = aliasAnnotation.value();
}
// 注册
registerAlias(alias, type);
}
// 注册一个包下的所类型别名,这些类型的父类型是superType
public void registerAliases(String packageName, Class<?> superType) {
//ResolverUtil一个实用工具类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 获得所有满足条件的Class对象,放在Set集合中
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
// 注册每个类
for (Class<?> type : typeSet) {
// 不注册内部类,接口
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
// 根据别名(转换为小写)获得对应的类型,先从Map中获得对应的类型,若找到则就用这个类型,如果没有找到,
// 则用Class.forName根据字符串(类型的全名)获得这个类型,若有异常(无法获得这个类型)则抛出异常
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
//将别名转换为小写
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
if (typeAliases.containsKey(key)) {
// 若找到则返回找到的类型
value = (Class<T>) typeAliases.get(key);
} else {
// 没有找到,则将别名看作类型全名,获得类型Class对象,若无法获得则抛异常ClassNotFoundException
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
}
别名注册时机
mybatis启动时,会注册所有的别名,包括系统默认的及自定义的。
1:Configuration对象持有一个TypeAliasRegistry,并在成员变量处定义并创建了这个对象,TypeAliasRegistry的构造方法注册了mybatis的默认的类型别名。
public class Configuration {
// 创建Configuration对象时即创建了TypeAliasRegistry对象
// TypeAliasRegistry()构造方法注册了系统默认的别名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
}
2:Configuration的无参构造方法使用TypeAliasRegistry对象注册了一些其他的系统用到的类型别名,详见Configuration无参构造方法
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
// 其它 ......
}
配置文件中自定义别名
自定义别名在mybatis配置文件中,可自定义别名,如下代码所示:
<typeAliases>
<!-- 一个别名,一个类型 -->
<typeAlias type="com.beck.mybatis.entity.Emp" alias="emp"></typeAlias>
<!-- 定义包名, 可注册这个包下的所有类型,如果使用@Alias定义了别名,则用这个别名,否则使用类型的简单名作为别名 -->
<package name="com.beck.mybatis.entity"></package>
</typeAliases>
别名子元素的解析
解析配置文件时,XMLConfigBuilder的typeAliasesElement处理配置文件中的typeAliases结点
对package子元素,则用TypeAliasRegistry的方法根据包名注册包中所有类的别名
对typeAlias子元素,则使用TypeAliasRegistry的方法注册给定的别名,alias属性可以为空
// 处理typeAliases子元素
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 获得typeAliases子元素下的子元素
for (XNode child : parent.getChildren()) {
// 处理package子元素
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// 注册这个包下的所有类
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 处理typeAlias子元素
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 没有定义属性alias,
typeAliasRegistry.registerAlias(clazz);
} else {
// 定义了属性alias,则使用这个别名注册
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
别名使用时机
映射文件的属性parameterType="Student"及resultType="Student",都会使用到类型别名,解析映射文件时,在BaseBuilder中有一个TypeAliasRegistry的引用,它的方法resolveClass需要根据别名获得一个类型对象,则使用TypeAliasRegistry的resolveAlias方法获得这个类型对象。