Mybatis初始化流程,其实就是组装重量级All-In-One对象Configuration的过程,主要分为系统环境参数初始化和Mapper映射初始化。java
上一节中,粗略讲述了Mybatis初始化的基本步骤,本节,将详细分析具体的初始化过程当中的细节问题,细节决定成败。sql
1. Properties variables的做用apache
一般,咱们会单独配置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>元素如何处理呢?
session
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元素的原理也是同样。
mybatis
3. namespace如何映射Mapper接口app
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)。(通过网络爬虫或转载的文章,常常丢失流程图、时序图,格式错乱等,仍是看原版的比较好)