Mapper文件解析源码导读
mapper文件解析入口
mybatis配置文件解析核心方法最后一项
private void parseConfiguration(XNode root) {
try {
...
//解析mapper文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
查看mapperElement(XNode parent)源码
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//节点是package
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) {
//从resource加载
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) {
//从url加载
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) {
//从mapperClass加载
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.");
}
}
}
}
}
mapper节点分两种方式解析(mapper、package)
<!--mapper分三种情况:-->
<mapper class="com.mybatis.builder.UserMapper"/>
<mapper resource="mapper/UserMapper.xml"/>
<mapper url="file:///var/mappers/UserMapper.xml"/>
<package name="com.mybatis.source.mapper" />
以上不同分支逻辑最后都会进入MapperRegistry的addMapper(...)方法
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
最初设计时,Mybatis是一个XML驱动的框架。配置信息基于xml,映射语句也定义在xml。Mybaits3开发了基于注解的配置方式,注解方式提供一种简单方式来实现简单映射语句,不引入大量开销,但最强大的mybatis不能用注解来构建
重点分析xml解析方式mapperParser.parse()
public void parse() {
//检测映射文件是否已被解析
if (!configuration.isResourceLoaded(resource)) {
//解析mapper节点
configurationElement(parser.evalNode("/mapper"));
//添加资源路径到 已解析集合 中
configuration.addLoadedResource(resource);
//通过命名空间绑定Mapper接口
bindMapperForNamespace();
}
//处理未完成解析的节点
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
映射文件解析入口逻辑包含三个核心操作,分别如下:
- 解析mapper节点
- 通过命名空间绑定Mapper接口
- 处理未完成解析的节点
在mapper映射文件中可以配置多种节点。eg:
<mapper namespace="cn.yto.mat.interfacex.dao.IDictDao">
<cache/>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.yto.mat.interfacex.entity.DictEntity">
<id column="ID" property="id" />
<result column="PID" property="pid" />
<result column="TYPE" property="type" />
<result column="CODE" property="code" />
<result column="NAME" property="name" />
<result column="VALUE" property="value" />
<result column="SORTNO" property="sortno" />
<result column="STATUS" property="status" />
<result column="IS_FIXED" property="isFixed" />
<result column="DATA_DESC" property="dataDesc" />
<result column="UPDATE_TIME" property="updateTime" />
<result column="UPDATE_USER" property="updateUser" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
ID, PID, TYPE, CODE, NAME, VALUE, SORTNO, STATUS, IS_FIXED, DATA_DESC, UPDATE_TIME, UPDATE_USER
</sql>
<select id="getDict" resultType="cn.yto.mat.interfacex.entity.DictEntity">
select d.type, d.code, d.name, d.value
from t_mat_dict d
where d.type = #{type}
and d.code = #{code}
</select>
</mapper>
解析mapper节点核心代码
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
//解析cache节点
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//SQL语句解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
ResultMap的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述他们的关系就行了。简述解析过程
- 获取
节点的各种属性 - 遍历
的子节点,并根据子节点名称执行相应的解析逻辑 - 构建resultMap对象
- 若构建的过程中发生异常,则将resultMapResolver添加到incompleteResultMaps集合中