mybatis源码分析-资源加载-下篇
处理mapper节点
构造函数中已经有很多很多默认类型匹配。这就是为什么在写sql的时候返回类型会自动映射到相应的java类型上面,这里已经处理好了。继续看最复杂的mapper在上面处理configuration节点的最后一句mapperElement(root.evalNode("mappers"));
。这个是配置文件里面最复杂的,所以再处理上面Mybatis多写了两个类专门处理mapper数据XMLMapperBuilder
和XMLStatementBuilder
- XMLMapperBuilder 处理mapper文件里面的resultMap,parameterMap,sql,cache等数据的解析
- XMLStatementBuilder 处理<select|insert|update|delete>这四类sql语句的处理
先来看看XMLMapperBuilder解析mapper表层文件
private void configurationElement(XNode context) {
try {
//拿到namespace。也就是类名
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//将当前namespace设置为当前builderAssistant的命名空间
builderAssistant.setCurrentNamespace(namespace);
//处理cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//处理cache
cacheElement(context.evalNode("cache"));
/**
* 注册参数map
* <parameterMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author">
* <parameter property="id" />
* </parameterMap>
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 最终将resultMap转换成ResultMap对象。并将ResultMap对象放到configuration对象里面
* <resultMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author">
* <id column="id" property="id" />
* <result property="username" column="username" />
* </resultMap>
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
//我们可以使用通用的sql注入到别的sql里面,这些sql放到sqlFragments这个map里面。这步比较简单
sqlElement(context.evalNodes("/mapper/sql"));
//处理sql sql语句比较复杂,所以使用单独的类XMLStatementBuilder类来出来
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
如果你已经按照前面分析配置文件的过程一路走了一遍,那么处理parameterMap和resultMap就很简单了,就是那个节点内容区出里面的各种属性,最终注册到对应的类上面。注册parameterMap使用类ParameterMap和ParameterMapping
细节就不一一描述了。再看一下resultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//<resultMap id="blogWithPosts" type="Blog"> 获得id
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获得type
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 一般没有设置,为空
String extend = resultMapNode.getStringAttribute("extends");
// 一般没有设置,为空
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//这里首先去typeAlias中看有没有别名,没有才回通过反射生成class对象
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
// 第一次additionalResultMappings为空
resultMappings.addAll(additionalResultMappings);
//<results>节点下的<result>节点。 <result property="username" column="username" />
List<XNode> resultChildren = resultMapNode.getChildren();
//遍历results下面所有的result节点
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);
}
//最普通的column和property就在这里处理
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;
}
}
处理resultMap节点将每一个result子节点封装成ResultMapping对象。然后将resultMapping对象和赋值给MapperBuilderAssistant。下面重点分析select,insert等标签处理的
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
因为sql处理比较复杂,所以单独创建XMLStatementBuilder来处理sql。上面方法中使用for循环list表示多个sql语句会使用多个XMLStatementBuilder来创建每个sql语句。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
//从TypeAliasRegistry里面获得对象,如果不是别名,则通过classUtil创建一个class对象
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//这个是sql语句处理的地方,最终将sql语句包装成了BoundSql对象。
/**
* 对象中包含下面属性
* private String sql; sql语句
* private List<ParameterMapping> parameterMappings; 参数map
* private Object parameterObject;
* private Map<String, Object> additionalParameters;
* private MetaObject metaParameters;
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
//上面将<select>里面的所有的数据都取出来,不管有没有,统统调用下面的构造方式,生成一个MappedStatement
//最终会很规整的构建一个MapperStatement对象,并将这个对象保存到configuration对象里面
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
上面处理sql语句的方法比较长我们慢慢分析,首先是通过Node获取节点中的属性。一般我们写sql语句属性就三个id;resultMap;parameterType。因为sql语句可以引用<include>标签来引用公共的sql,所以单独有处理这个引入sql的地方
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
还有使用insert语句处理的时候可以查询主键,所以也有专门处理这个的地方processSelectKeyNodes(id, parameterTypeClass, langDriver);
重点是这个方法SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
获得sql对象这个对象包含的主要属性如下:
private String sql;
private List<ParameterMapping> parameterMappings;
private Object parameterObject;
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;
分别为sql语句这个sql语句已经将#{}数据转化成了?然后是参数集合List<ParameterMapping>主要是这个两个属性。这个创建调用稍微复杂点调用顺序如下:XMLLanguageDriver.createSqlSource --> XMLScriptBuilder.parseScriptNode --> RawSqlSource构造函数 --> SqlSourceBuilder.parse -->
//SqlSourceBuilder 类的方法
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
看上面的处理应该知道是替换#{}用?替换,并且将参数转化成ParameterMapping对象。然后通个builderAssistant.addMappedStatement
包装成MapperStatement对象,并且保存到configuration里面configuration.addMappedStatement(statement);
回到XMLScriptBuilder.parseScriptNode方法。如果sql是动态sql也就是包含<#if>这种数据则会走动态sql
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode); //动态sql走这个处理器
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
最后将namespace对应的class对应到configuration对象中这样整个mapper处理就完毕了。