在上一篇 XMLConfigBuilder 类当中,在解析 mappers 的节点还没有分析,这篇主要分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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.");
}
}
}
}
}

对应的 XML 如下:

1
2
3
4
5
6
<mappers>
<package name=""/>
<mapper resource="org/mybatis/example/BlogMapper.xml" />
<mapper class=""/>
<mapper url=""/>
</mappers>

一般在使用的时候会使用 resource 这种方式,直接引用到另外的一个 xml 当中,从代码当中可以看到会使用 XMLMapperBuilder 这个类。下面看一下这个类的结构:

1
2
3
4
5
6
7
public class XMLMapperBuilder extends BaseBuilder {

private XPathParser parser;//解析 xml
private MapperBuilderAssistant builderAssistant;// builder 的助手类
private Map<String, XNode> sqlFragments;
private String resource;
}

XMLMapperBuilder 同样继承了 BaseBuilder。在 XMLMapperBuilder 当中最主要的是 parse 方法:

1
2
3
4
5
6
7
8
9
10
11
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}

首先判断加载过的就不在继续加载,然后 configurationElement 进行解析 mapper xml 的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
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 BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

对应的 XML 如下:

1
2
3
4
5
6
7
8
9
10
<resultMap id="AccountDetailMap" type="AccountDetail" >
<constructor>
<idArg column="" javaType="" jdbcType="" resultMap="" select="" typeHandler=""/>
</constructor>
<id property="id" column="id" />
<result property="type" column="type"
typeHandler="com.haode.yunying.repository.handler.RewardTypeHandler" />
<result property="credit" column="credit" />
<result property="createTime" column="createTime" />
</resultMap>

首先获取到当前 mapper 的 namespace,然后设置到 builderAssistant 当中去。然后开始逐步解析不同的节点,主要分析 resultMap 、 sql 和 sql 语句节点(select、update、delete、insert)。

解析 resultMap

先来看看解析 resultMap 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}

主要看 resultMapElement 这个方法:

  1. 解析 resultMap 上的属性,如 id、等
  2. 解析它的子节点包括 constructor、id、result 等
  3. 把这些子节点都添加到 resultMappings 当中
  4. 通过 ResultMapResolver 来解析出 ResultMap

主要涉及到的类有 ResultMapping、ResultMapResolver、ResultMap、MapperBuilderAssistant,依次进行分析:

先来看 ResultMapping 的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ResultMapping {

private Configuration configuration;
private String property;//对应id和result节点的property属性
private String column;//对应colum属性
private Class<?> javaType;//对应javaType属性
private JdbcType jdbcType;//对应jdbcType属性
private TypeHandler<?> typeHandler;//对应typeHandler属性
private String nestedResultMapId;//association和collection的resultMap属性
private String nestedQueryId;//association和collection的select属性
private Set<String> notNullColumns;//association和collection的notNullColumns属性
private String columnPrefix;//association和collection的columnPrefix属性
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;//association和collection的resultSet属性
private String foreignColumn;//association和collection的foreignColumn属性
private boolean lazy;//association和collection的fetchType属性

}

下面详细分析一下 resultMapElement 这个方法:

1
2
3
4
5
6
7
8
9
10
11
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}

上面的 if else 来判断属于什么节点,首先来看 constructor 节点的解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property = context.getStringAttribute("property");
//... 省略基本属性解析
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));

Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
}

public enum ResultFlag {
ID, CONSTRUCTOR
}

从上面代码可以看出来解析的是 constructor 所以 flags 里面添加了CONSTRUCTOR,如果是 constructor 节点下的 idArg 也会在 flags 里面添加 ID。

解析 nestedResultMap 的时候调用了 processNestedResultMappings 方法。

使用 builderAssistant 来创建 ResultMapping,依次分析这两个方法。

1
2
3
4
5
6
7
8
9
10
11
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}

代码清晰明了,先判断节点是不是 association、collection、case 当中的一个,如果是则调用 resultMapElement 生成 ResultMap,并且把 ResultMap 的 id 返回。

其中 resultMapElement 就是我们开始一直在分析的方法。

然后在看 builderAssistant 创建 ResultMapping 的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public ResultMapping buildResultMapping(
Class<?> resultType,
String property,
String column,
Class<?> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class<? extends TypeHandler<?>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
List<ResultMapping> composites = parseCompositeColumnName(column);
if (composites.size() > 0) {
column = null;
}
ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
builder.jdbcType(jdbcType);
builder.nestedQueryId(applyCurrentNamespace(nestedSelect, true));
builder.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true));
builder.resultSet(resultSet);
builder.typeHandler(typeHandlerInstance);
builder.flags(flags == null ? new ArrayList<ResultFlag>() : flags);
builder.composites(composites);
builder.notNullColumns(parseMultipleColumnNames(notNullColumn));
builder.columnPrefix(columnPrefix);
builder.foreignColumn(foreignColumn);
builder.lazy(lazy);
return builder.build();
}

主要就是参数的解析与设置,ResultMapping 内部有个 Builder 类,来创建 ResultMapping 的实例。

下篇继续分析 resultMap 的解析过程。

—EOF—