处理mapper节点
构造函数中已经有很多很多默认类型匹配。这就是为什么在写sql的时候返回类型会自动映射到相应的java类型上面,这里已经处理好了。继续看最复杂的mapper在上面处理configuration节点的最后一句mapperElement(root.evalNode("mappers"));
。这个是配置文件里面最复杂的,所以再处理上面Mybatis多写了两个类专门处理mapper数据XMLMapperBuilder
和XMLStatementBuilder
- XMLMapperBuilder 处理mapper文件里面的resultMap,parameterMap,sql,cache等数据的解析
- XMLStatementBuilder 处理
先来看看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
*
*
*
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 最终将resultMap转换成ResultMap对象。并将ResultMap对象放到configuration对象里面
*
*
*
*
*/
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 additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获得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 resultMappings = new ArrayList();
// 第一次additionalResultMappings为空
resultMappings.addAll(additionalResultMappings);
//节点下的节点。
List 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 flags = new ArrayList();
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 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: and were parsed and removed)
//这个是sql语句处理的地方,最终将sql语句包装成了BoundSql对象。
/**
* 对象中包含下面属性
* private String sql; sql语句
* private List parameterMappings; 参数map
* private Object parameterObject;
* private Map 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();
}
//上面将
上面处理sql语句的方法比较长我们慢慢分析,首先是通过Node获取节点中的属性。一般我们写sql语句属性就三个id;resultMap;parameterType。因为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 parameterMappings;
private Object parameterObject;
private Map additionalParameters;
private MetaObject metaParameters;
分别为sql语句这个sql语句已经将#{}数据转化成了?然后是参数集合List
//SqlSourceBuilder 类的方法
public SqlSource parse(String originalSql, Class> parameterType, Map 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 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处理就完毕了。