MyBatis-别名详细分析


前言

  使用MyBatis开发过程中sql语句都需要参数或返回值,而且大部分都是自定义对象。参数或返回值可以使用parameterType和resultType设置,正常情况下是需要使用包名加类名来确定对象的,也可以使用别名来简化。接下来详细讲述别名。


一、如何使用?

  别名的配置分为两种,一种是通过包名配置,获取包下类信息根据类名简写作为别名。另一种直接根据类信息配置别名。接下来说明下mybatis中别名是如何使用的。(会使用的可以直接看第二节源码分析)
项目结构如下:
项目结构
  mybatis-config.xml中的代码(后面简写成config.xml或主配置),以package和typeAlias设置了别名信息。

<configuration>
    <!-- 别名设置 -->
    <typeAliases>
        <!-- 根据类信息配置别名 -->
        <!-- 别名定义为uur -->
        <typeAlias alias="uur" type="com.learn.mybatis.entity.param.UmsUserParam" />
        <!-- 根据包名配置别名 -->
        <package name="com.learn.mybatis.entity" />
    </typeAliases>
</configuration>

  也可以使用@Alias对类进行别名设置,前提是需要先设置package或typeAlias,如果未设置package或typeAlias,@Alias注解将不会生效(后面对注解进行分析)。
使用@Alias注解如下:

@Data
// 别名定位为uup
@Alias("uup")
public class UmsUserParam{

    /**
     * 系统生成的客户编号
     */
    private String no;

    /**
     * 姓名
     */
    private String name;

    /**
     * 性别
     */
    private String sex;

    /**
     * 年龄
     */
    private Integer age;

}

  对自定义别名进行使用。

<!-- 使用别名uup或uur  -->
<select id="select" parameterType="uup" resultMap="BaseResultMap" >
        select
        <include refid="column_info"/>
        from ums_user
        <where>
            <if test="no!=null">
                and `no` = like concat(concat('%',#{no}),'%')
            </if>
            <if test="name!=null">
                and `name` like concat(concat('%',#{name}),'%')
            </if>
            <if test="age!=null">
                and `age` = {age}
            </if>
            <if test="sex!=null">
                and `sex` = {sex}
            </if>
        </where>
    </select>

  config.xml与注解分别配置了不同的别名。
加载自定义别名如下:解析typeAlias
解析typeAlias
加载自定义别名如下:解析package
解析package
解析mapper.xml时使用别名如下:
使用别名
  可以看到别名信息中存在uup和uur两个而且对应的类是相同的。uur是通过typeAlias配置添加到别名信息中的,uup则是通过package扫描下面的类然后通过Alias注解注册的别名。

二、源码分析

  TypeAliasRegistry类使用map保存别名与类的关系,初始化时添加了常用的别名信息。TypeAliasRegistry类中包含通过参数获取类信息和注册别名的方法。
下面是TypeAliasRegistry类信息:

public class TypeAliasRegistry {
  // map对象用来维护别名与类的关系
  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
  // 构造方法 注册默认的别名信息
  public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);
    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);
    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);
    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);
    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);
    registerAlias("ResultSet", ResultSet.class);
  }
  
  // 根据参数获取类信息(存在别名则直接从别名map中获取,否则根据参数转换成对应的类)
  @SuppressWarnings("unchecked")
  // throws class cast exception as well if types cannot be assigned
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // 将key统一转换成小写(保存别名时是小写)
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      // 别名map中是否包含该key
      if (TYPE_ALIASES.containsKey(key)) {
        // 包含则直接从map中获取类信息
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
      	// 不包含则直接转换成类信息
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }
  
  // 根据包名注册别名
  public void registerAliases(String packageName){
    registerAliases(packageName, Object.class);
  }
  
  // 根据包名和父类注册别名
  public void registerAliases(String packageName, Class<?> superType){
    // 解析工具
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    // 扫描包下的类并且是superType的子类
    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()) {
      	// 将类注册到别名map中
        registerAlias(type);
      }
    }
  }
  
  // 将类信息注册到别名中(package解析和typeAlias只设置类信息时会调用该方法,所以Alias需要在package或typeAlias设置后使用)
  public void registerAlias(Class<?> type) {
  	// 获取类名(不包含包名,简单类名)
    String alias = type.getSimpleName();
    // 获取类上注解
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      // 存在注解则获取注解配置的别名
      alias = aliasAnnotation.value();
    } 
    // 将别名和类信息注册到别名map中
    registerAlias(alias, type);
  }
  // 将别名和类信息添加到别名map中(package和typeAlias最终都要调用该方法)
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    // 别名统一转换成小写(获取时传入的key同样操作)
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 别名map中存在相同的key 且 对应的value不为null而且与存在的类信息不同时抛出异常
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // 将别名与类信息添加到别名map中
    TYPE_ALIASES.put(key, value);
  }
  // 添加别名的重载方法 通过stirng转换成对应的类信息在进行添加
  public void registerAlias(String alias, String value) {
    try {
      registerAlias(alias, Resources.classForName(value));
    } catch (ClassNotFoundException e) {
      throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
    }
  }
  
  // 获取别名map 只读
  public Map<String, Class<?>> getTypeAliases() {
    return Collections.unmodifiableMap(TYPE_ALIASES);
  }
}

  TypeAliasRegistry是如何创建的?Mybatis的主配置类Configuration中包含TypeAliasRegistry属性。mybatis启动时创建Configuration对象,Configuration构造方法里创建TypeAliasRegistry对象。
代码信息如下:

	// Configuration无参构造,其他构造都会调用无参构造。(与别名无关的代码都删掉了)
	public Configuration() {
        // 创建TypeAliasRegistry
        this.typeAliasRegistry = new TypeAliasRegistry();
        // 创建默认的别名信息
        this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
        this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
        this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
        this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
        this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
        this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
        this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
        this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
        this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
        this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    }

  用户自定义的别名是如何注册的呢?这里需要说明Configuration的解析了,以Xml形式为例,XMLConfigBuilder的parseConfiguration方法是对config.xml进行解析的,里面包含对别名的解析。
parseConfiguration方法如下:

// 对Config.xml文件进行解析(这里只看别名)
 private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      // 对别名进行解析
      typeAliasesElement(root.evalNode("typeAliases"));
      // mybatis插件解析
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 自定义的typeHandler进行解析
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 对配置的mapper进行解析
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
 // 对Config.xml文件中配置的别名进行解析
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      // 遍历typeAliases下子节点
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // package解析
          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) {
              // 别名为null则通过类进行别名注册(如果存在注解则使用注解配置的别名,不存在则使用类简单名称TypeAliasRegistry类中有说明)
              typeAliasRegistry.registerAlias(clazz);
            } else {
              // 别名不为null则直接注册
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

  别名的注册已经了解了,mapper.xml配置的sql中使用的parameterType和resultType是如何解析的呢?mapper.xml中<select|insert|update|delete>最终都会转换成MappedStatement。MappedStatement是由XMLStatementBuilder的parseStatementNode方法进行解析的。
parseStatementNode代码如下:

// 解析<select|insert|update|delete>语句
public void parseStatementNode() {
	// context为解析当前语句的信息(select标签内包含的信息)。
    // 获取parameterType的值
    String parameterType = context.getStringAttribute("parameterType");
    // 根据parameterType的值通过TypeAliasRegistry获取对应的类。如何是null则直接返回null,如果是别名则根据别名获取类信息,不是别名则根据获取的值转换成对应的类。
    Class<?> parameterTypeClass = resolveClass(parameterType);
     // 获取resultType的值
    String resultType = context.getStringAttribute("resultType");
    // 与parameterType相同
    Class<?> resultTypeClass = resolveClass(resultType);
}

总结

  mybatis别名是通过TypeAliasRegistry进行存储的,TypeAliasRegistry通过内部的Map存储别名与类的关系。创建Configuration时会创建TypeAliasRegistry对象,并初始化默认的别名信息,解析Configuration的过程中会对自定的别名进行解析。自定义别名的方式分为两种,一种是通过package对包下的类定义别名,一种是根据typeAlias定义单个类。可以通过Alias注解定义单个类别名,使用注解前需要使用package或typeAlias,否则注解不生效。同一个类可以定义多个别名,不同的类不能定义相同的别名。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值