这边文章接Mybatis之SqlSessionFactoryBean源码初步解析(一)。
直接进入主题
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);
}
}
首先说一下mybatis 里面的分页是假分页, 就是给数据全部拿到手,再去分页。脱裤子放屁,不看
先看context里面是什么内容:
因为用的逆向工程自动生成的 所以内容很多,就不全部截图下来了。
一 、解析mapper.xml文件
解析之前先给全部的resultMap标签找到
为了方便回头看,具体内容如下:
[<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
<id jdbcType="BIGINT" column="id" property="id"/>
<result jdbcType="VARCHAR" column="sname" property="sname"/>
<result jdbcType="INTEGER" column="sno" property="sno"/>
<result jdbcType="VARCHAR" column="password" property="password"/>
<result jdbcType="INTEGER" column="power" property="power"/>
</resultMap>
]
解析规则
首先要特别说一句,比如A的子元素是B ,B的子元素是C 那么C就不是A 的子元素。
在for循环挨个解析resultMap元素
最终的解析方法在这里:
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;
}
}
resultMapNode.getStringAttribute('A','B');这个方法很简单, 我看了一下,意思就是xnode 的attribute 属性里面如果key A 对应的value 是null 那么就将B 的值赋给A ;attribute的类型是Property继承自HashTable;
那么下面代码的意思是:
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
如果啊resultMapNode里面没有type, 就去找ofType,没有的话就去找resultType,再没有就去找javaType;
resultMapNode里面是什么呢?
<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
<id jdbcType="BIGINT" column="id" property="id"/>
<result jdbcType="VARCHAR" column="sname" property="sname"/>
<result jdbcType="INTEGER" column="sno" property="sno"/>
<result jdbcType="VARCHAR" column="password" property="password"/>
<result jdbcType="INTEGER" column="power" property="power"/>
</resultMap>
比上面的少一对[] ,就是list 里面的一个元素;
解析别名
Class<?> typeClass = resolveClass(type);这个方法我看了就是去解析别名的,存在一个final Map中。key值为class 的全路径名。
resultMappings 我们先不管, 先看resultChildren
[<id jdbcType="BIGINT" column="id" property="id"/>
, <result jdbcType="VARCHAR" column="sname" property="sname"/>
, <result jdbcType="INTEGER" column="sno" property="sno"/>
, <result jdbcType="VARCHAR" column="password" property="password"/>
, <result jdbcType="INTEGER" column="power" property="power"/>
]
看下面的判断如果他的子标签是constructor或者是discriminator。会调用processDiscriminatorElement(resultChild, typeClass, resultMappings);这个方法其实就是在剥一层而已。然后如果是constructor则和result标签一样掉buildResultMappingFromContext方法将解析好的东西放入resultMappings中。
那么重点就在buildResultMappingFromContext中了。下面上代码:
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property = context.getStringAttribute("property");
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
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, resultSet, foreignColumn, lazy);
}
context 内容为:<id jdbcType="BIGINT" column="id" property="id"/>即为resultChildren中的第一个元素
上面的没什么好看的就是解析标签内个元素标签。还有别名
二、封装解析出来的标签
最后的最后调用buildResultMapping方法,代码如下
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;
}
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<ResultFlag>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
这是干了什么? 其实就是用了一个构建模式。这边真的要好好看看Builder是ResultMapping里面的静态内部类,Builder里面有个resultMapping变量,怎么设置值呢? 我抽了一个代码看看,这个代码nestedQueryId 很明显是个很重要的东西,我忘了查看:
public Builder nestedQueryId(String nestedQueryId) {
resultMapping.nestedQueryId = nestedQueryId;
return this;
}
return的是this ,this 就是ResultMapping这个类本身的一个实例。最后调用.build的方法就是组装好了,然后返回Build中的resultMapping 变量。最后我们再将这个resultMapping放入ResultMappings里面;再看下面的代码:
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
}
意思是将resultMappings 传到ResultMapResolver中,同样里面也是构建模式。最后返回一个ResultMap类型;
可能这样会把大家绕晕了,我以下面的东西解释吧:
1.<resultMap type="com.fuchanghai.mybatis.pojo.Employee" id="BaseResultMap">
2.<id jdbcType="BIGINT" column="id" property="id"/>
3.<result jdbcType="VARCHAR" column="sname" property="sname"/>
4.<result jdbcType="INTEGER" column="sno" property="sno"/>
5.<result jdbcType="VARCHAR" column="password" property="password"/>
6.<result jdbcType="INTEGER" column="power" property="power"/>
7.</resultMap>
除了第一行和第七行,其他每一行都是一个resultMapping. 第二行到第6 行的总和组成了resultMappings。
最后第一行到第七行整个组成了一个resultMap.
下图是通过本章第一块代码的
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
调过来的,作用是解析所有select|insert|update|delete 放入一个list 中
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
这边我们可以看到不管databaseid是不是空都会掉到下面
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);
}
}
}
这里其实就是遍历list 一个一个解析,每一个查询标签都会对应一个statementParser,进入paserStatementNode方法
比如id 等等属性都会被先解析,最重要的一句代码是这个 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);我们先将这个高潮缓一下。先看看另外一个方法干了什么。
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");
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)
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();
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
builderAssistant.addMappedStatement()方法的参数是解析好的属性, 他干了什么呢?
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
这一看就是构建模式将各种属性放入进去,最终将调用configuration的方法将之放入大管家的一个Map中。这边有个resource属性,如果我们记错的话,这个resource是类名。
我们回头看这个createsqlSource 方法,
最重要的在下面展示的代中
三、解析增删查改的xml标签
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
<select id="selectByExample" resultMap="BaseResultMap" parameterType="com.fuchanghai.mybatis.pojo.DailyExample" >
1)select
2)<if test="distinct" >
distinct
</if>3)
4)<include refid="Base_Column_List" />
5) from daily
6) <if test="_parameter != null" >
<include refid="Example_Where_Clause" />
</if>7)
8)<if test="orderByClause != null" >
order by ${orderByClause}
</if>9)
</select>
图上的NodeList children = node.getNode().getChildNodes();children 的长度为9,这些标签中的内嵌的元素不在children中。children中只包含直接子标签这一点很重要。有人想问,3)7)9)是什么子元素?额。。。。是换行。。
3对应的图下的2了解一下
我们现在主要解析这句有代表性的:
<if test="_parameter != null">
<where>
<foreach item="criteria" separator="or" collection="oredCriteria">
<if test="criteria.valid">
<trim prefix="(" suffix=")" prefixOverrides="and">
<foreach item="criterion" collection="criteria.criteria">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
<foreach open="(" item="listItem" separator="," collection="criterion.value" close=")">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</if>
首先是if 标签,if 标签不是静态标签那么他就会走下面的代码
else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
nodeName是 if
nodeHandlers(nodeName);所调用的方法如下
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
即根据标签名称从Map中取得对应的处理标签的类
handler.handleNode(child, contents);我们这边是动态标签所以会掉到这里,这个contents 对应的是select 查询语句级别的 我们暂且给看看成全局的.
进入IfHandler的解析方法
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
因为if标签是动态标签所以我们会继续交给parseDynamicTags(nodeToHandle)来解析。很明显这是个递归的调用。只有当标签是StaticText类型的才会停止递归,进行下一个元素的解析。(这里以我的口述能力实在不知道该怎么去解释)
最终会将解析好的List <sqlNode> 封装成MixedSqlNode, z再将mixedsqlnode 封装成ifSqlNode 将之放入全局的contexts中。最终将全局的contexts分装成MixedSqlNode ,最最最后再分装成SqlSource。实际上我说出了递归,大家应该就知道的差不多了