《深入浅出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方法,获取不到值时,抛出异常。
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方法。
原文:http://blog.csdn.net/xiangwangye66/article/details/41807385