文档章节

Mybatis3.3.x技术内幕(九):Mybatis初始化流程(中)

祖大俊
 祖大俊
发布于 2016/05/02 17:41
字数 1607
阅读 1648
收藏 12

Mybatis初始化流程,其实就是组装重量级All-In-One对象Configuration的过程,主要分为系统环境参数初始化和Mapper映射初始化。

上一节中,粗略讲述了Mybatis初始化的基本步骤,本节,将详细分析具体的初始化过程中的细节问题,细节决定成败。


1. Properties variables的作用

通常,我们会单独配置jdbc.properties文件,保存于variables变量中,而Xml文件内可以使用${driver}占位符,读取时可动态替换占位符的值。

String value = PropertyParser.parse(attribute.getNodeValue(), variables);

Mybatis中的PropertyParser类,就是用来动态替换占位符参数的。


2. 扫描package

<typeAliases>
	<typeAlias alias="Student" type="com.mybatis3.domain.Student" />
	<typeAlias alias="Teacher" type="com.mybatis3.domain.Teacher" />
	<package name="com.mybatis3.domain" />
</typeAliases>

前两个typeAlias,很容易理解,那么<package>元素如何处理呢?

  public void registerAliases(String packageName, Class<?> superType){
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    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);
      }
    }
  }

扫描package下所有的Class,并注册。此时,String alias = type.getSimpleName()。在处理typeHandlers和mappers时,处理package元素的原理也是一样。


3. namespace如何映射Mapper接口

org.apache.ibatis.builder.xml.XMLMapperBuilder.bindMapperForNamespace()。

  // namespace="com.mybatis3.mappers.StudentMapper"
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

直接使用Class.forName(),成功找到就注册,找不到就什么也不做。

Mybatis中的namespace有两个功能。

1. 和其名字含义一样,作为名称空间使用。namespace + id,就能找到对应的Sql。

2. 作为Mapper接口的全限名使用,通过namespace,就能找到对应的Mapper接口(也有称Dao接口的)。Mybatis推荐的最佳实践,但并不强制使用。


Mapper接口注册至Configuration的MapperRegistry mapperRegistry内。

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

Mapper接口将通过MapperProxyFactory创建动态代理对象。

可参看动态代理之投鞭断流(自动映射器Mapper的底层实现原理)博文。


4. 一个MappedStatement被缓存了两个引用的原理及原因

configuration.addMappedStatement(statement);

调用上面一句话,往Map里放置一个MappedStatement对象,结果Map中变成两个元素。

com.mybatis3.mappers.StudentMapper.findAllStudents=org.apache.ibatis.mapping.MappedStatement@add0edd
findAllStudents=org.apache.ibatis.mapping.MappedStatement@add0edd

我们的问题是,为什么会变成两个元素?同一个对象,为什么要存有两个键的引用?

其实,在Mybatis中,这些Map,都是StrictMap类型,Mybatis在StrictMap内做了手脚。

protected static class StrictMap<V> extends HashMap<String, V> {

    public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key);
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        // 不存在shortKey键值,放进去
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
        // 存在shortKey键值,填充占位对象Ambiguity
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }
}

Mybatis重写了put方法,将id和namespace+id的键,都put了进去,指向同一个MappedStatement对象。如果shortKey键值存在,就填充为占位符对象Ambiguity,属于覆盖操作。

这样做的好处是,方便我们编程。

Student std  = sqlSession.selectOne("findStudentById", 1);
Student std  = sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", 1);

上面两句代码,是等价的,Mybatis不强制我们一定要加namespace名称空间,所以,这是存放两个键的良苦用心。

问题:不同namespace空间下的id,能否相同呢?(网上的说法是,不同名称空间下的id可以相同)

明白上述put原理后,就不难得出结论,namespace名称空间不同,而id相同时,使用namespace+id获取Sql,完全可以正确执行。如果只用id获取,那么,将导致错误。

org.apache.ibatis.session.Configuration.StrictMap.get()方法源码。

    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

get时,如果得到的是一个占位对象Ambiguity,就抛出异常,要求使用full name进行调用。full name就是namespace+id。Ambiguity意为模糊不清。

解决办法:

1. 保证shortKey(即id)不重复。(好像有点难度,不推荐)

2. 使用绑定Mapper接口调用方法,因为它总是转换为full name调用。(Mybatis最佳实践,推荐)

3. 直接使用字符串full name调用。(退而求其次的方式,不推荐)


5. 初始化过程中的mapped和incomplete对象

翻译为搞定的和还没搞定的。这恐怕是Mybatis框架中比较奇葩的设计了,给人很多迷惑,我们来看看它具体是什么意思。

<resultMap type="Student" id="StudentResult" extends="Parent">
	<id property="studId" column="stud_id" />
	<result property="name" column="name" />
	<result property="email" column="email" />
	<result property="dob" column="dob" />
</resultMap>
	
<resultMap type="Student" id="Parent">
	<result property="phone" column="phone" />
</resultMap>

Mapper.xml中的很多元素,是可以指定父元素的,像上面extends="Parent"。然而,Mybatis解析元素时,是按顺序解析的,于是先解析的id="StudentResult"的元素,然而该元素继承自id="Parent"的元素,但是,Parent被配置在下面了,还没有解析到,内存中尚不存在,怎么办呢?Mybatis就把id="StudentResult"的元素标记为incomplete的,然后继续解析后续元素。等程序把id="Parent"的元素也解析完后,再回过头来解析id="StudentResult"的元素,就可以正确继承父元素的内容。

简言之就是,你的父元素可以配置在你的后边,不限制非得配置在前面。无论你配置在哪儿,Mybatis都能“智能”的获取到,并正确继承。

这便是在Configuration对象内,有的叫mapped,有的叫incomplete的原因。

protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();

org.apache.ibatis.builder.xml.XMLMapperBuilder.parse()方法内,触发了incomplete的再度解析。

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    // 执行incomplete的地方
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

Pending含义为待定的,悬而未决的意思。


总结:有了这些细节储备,阅读源码就变得更加得心应手了。


版权提示:文章出自开源中国社区,若对文章感兴趣,可关注我的开源中国社区博客(http://my.oschina.net/zudajun)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)

© 著作权归作者所有

共有 人打赏支持
祖大俊
粉丝 748
博文 32
码字总数 52477
作品 0
昌平
私信 提问
Mybatis3.3.x技术内幕(八):Mybatis初始化流程(上)

Mybatis初始化流程,其实就是组装重量级All-In-One对象Configuration的过程,主要分为系统环境参数初始化和Mapper映射初始化,其中Mapper映射初始化尤为重要。 inputStream = Resources.getR...

祖大俊
2016/05/02
1K
2
Mybatis3.3.x技术内幕(一):SqlSession和SqlSessionFactory列传

前言:我长大了,成年了,有需求,但我单身,所以我要讨个媳妇,要求是:漂亮、高挑、身材好、笑容甜美…… 和A相亲:漂亮,不够高挑。 和B相亲:高挑,身材不够好。 和C相亲:身材好,笑容不...

祖大俊
2016/04/25
3.3K
2
Mybatis3.3.x技术内幕(七):Mybatis初始化之六个工具

全民欢庆的五一劳动节,可谓是赏花赏月赏秋香的好季节,炎炎夏日,柳絮飞扬,短裙飞舞,低胸抢镜,是旅游撩妹裸奔等精彩活动的不二选择,不过,这显然与我无关。 终于要开启Mybatis的初始化过...

祖大俊
2016/05/01
1K
5
Mybatis3.3.x技术内幕(十二):Mybatis之TypeHandler

Mybatis中的TypeHandler有两个功能,一个是完成javaType至jdbcType的转换,另外一个是完成jdbcType至javaType的转换。 public interface TypeHandler<T> { void setParameter(PreparedStatem......

祖大俊
2016/05/06
991
0
Mybatis3.3.x技术内幕(十):Mybatis初始化流程(下)

Mybatis初始化过程中,解析parameterMap、resultMap、"select|insert|update|delete"元素,无疑是重头戏。本节将详细分析解析过程。 元素parameterMap将会解析为ParameterMap对象,该对象包含...

祖大俊
2016/05/04
851
0

没有更多内容

加载失败,请刷新页面

加载更多

RadosClient OSDC

RadosClient.h class librados::RadosClient : public Dispatcher//继承自Dispatcher(消息分发类){public: using Dispatcher::cct; md_config_t *conf;//配置文件private: ......

banwh
29分钟前
0
0
如果让你写一个消息队列,该如何进行架构设计?

面试题 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 面试官心理分析 其实聊到这个问题,一般面试官要考察两块: 你有没有对某一个消息队列做过较为深入的原理的了解,或者...

李红欧巴
今天
4
0
错题

无知的小狼
今天
2
0
PowerShell因为在此系统中禁止执行脚本的解决方法

参考:window系统包管理工具--chocolatey 报错提示: & : 无法加载文件 C:\Users\liuzidong\AppData\Local\Temp\chocolatey\chocInstall\tools\chocolateyInstall.ps1,因为在此系统上禁止运...

近在咫尺远在天涯
今天
3
0
TP5 跨域请求处理

https://blog.csdn.net/a593706205/article/details/81774987 https://blog.csdn.net/wyk9916/article/details/82315700...

15834278076
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部