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方法获得这个类型对象。