首先在此声明,本人是通过学习湖畔微风《深入浅出mybatis》基础之上对代码进行总结。
《深入浅出mybatis》 http://blog.csdn.net/hupanfeng/article/details/9068003
项目中的sql语句、一级缓存等都会配置在Mapper文件中,在开发中要对其经常进行配置,所以对解析Mapper文件做一下详细的总结。
1、XMLConfigBuilder类中的mapperElement方法是解析Mapper文件的入口,在解析之前会检查子节点类型并获取子节点属性,加载配置文件获取字节流。然后创建XmlMapperBuilder对象,字节流作为参数传进去。
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url , configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
1.1 在创建XMLMapperBuilder对象,其中一个参数为configuration.getSqlFragments(),返回Configuration类中的成员数性
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
StrictMap是Configuration类的一个内部类用来存储每个mapper元素下的sql标签,它继承了HashMap并重写了put和get方法
@SuppressWarnings("unchecked") 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); if (super.get(shortKey) == null) { super.put(shortKey, value); } else { super.put(shortKey, (V) new Ambiguity(shortKey)); } } return super.put(key, value); }
通过代码可以看出,重写put方法重要是为了,当key值有重复时,抛出异常。
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方法,获取不到值时,抛出异常。
1.2 XMLMapper的构造方法,其中MapperBuilderAssistant用于缓存、sql参数、查询返回的结果集处理。
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; }
2、XMLmapper中的parse方法
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); }
2.1 configurationElement 方法解析mapper标签下的子节点。mybatis的一级缓存是默认开启的,可以通过追查buildStatemnetFromContext方法中调用XMLStatementBuilder类的parseStatementNode方法去验证而且想关闭一级缓存光配置useCache=false是没有用的,还必须配置flushCache=true才行。其它的方法有兴趣的朋友可以自己研究,这里就不做详细介绍了。
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e); } }
2.2 configuration.addLoadedResource(resource)添加到一个HashSet中表示此rescue的mapper文件已经被解析过了。
2.3 bindMapperForNameSpace方法,用于获取mapper的代理工厂对象,判断是否knowMappers中是否存在其代理工厂对象,knowMappers在上一节中有介绍。如果不存在,则创建其代理工程对象并保存在knowMappers中。
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); } } } }
总结:(1)通过上一节和这节的总结,可以发现mybatis解析xml的套路。先获取配置文件的根节点,解析其子节点的类名称为(根节点名+MapperBulider)这样写的程序很有层次感。
(2)内部类并重写hashmap的get、put方法,对于内部类的学习http://blog.csdn.net/ilibaba/article/details/3866537。
(3)想要详细了解对Mapper文件的解析请学习XMLMapperBuilder类中的configurationElement方法。