概述
mapper.xml是我們使用mybatis接觸最多的,需要編寫sql、ResultMap、ParameterMap等。且看如何解析xml,並註冊到Configuration中。
入口
接着上篇mybatis-confg解析的末尾,關於mapper.xml的解析。
先上實例mapper內容:
入口類是 XMLMapperBuilder,無論是單個還是批量都是循環遍歷 XMLMapperBuilder.parse()。
public class XMLMapperBuilder extends BaseBuilder {
// xml解析工具類
private final XPathParser parser;
// Mapper構造小助手(工具類)
private final MapperBuilderAssistant builderAssistant;
// sql片段容器,key=sqlId,value=xmlNode
private final Map<String, XNode> sqlFragments;
// mapper resource()
private final String resource;
}
依舊依靠XPathParser解析。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析<mapper />
configurationElement(parser.evalNode("/mapper"));
// 標記該resource已經加載過,防止重複加載
configuration.addLoadedResource(resource);
// 綁定mapper接口 和 xml
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
mapper.xml的根節點是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"));
// 解析<parameterMap /> 已廢棄
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析<resultMap />
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析<sql />
sqlElement(context.evalNodes("/mapper/sql"));
// 解析statement
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);
}
}
這裏依次解析mapper文件中允許出現的標籤,
cache-ref
cache引用,共享cache容器的時候使用。
private void cacheRefElement(XNode context) {
if (context != null) {
// 添加緩存引用到Configuration中
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 根據ref查找該cache是否存在
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
cache
真的用的很少,簡單掃一眼。
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
parameterMap
mybatis已經在賤賤廢棄parameterMap了,我們在日常使用的時候也基本不會使用到,這是老版本提供的功能。略過。
private void parameterMapElement(List<XNode> list) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
resultMap
結果集映射,日常使用的較多,也比較複雜,解析過程中主要關注日常使用的簡單標籤。先貼示例的ResultMap內容:
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<association property="author" javaType="Author"/>
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
這裏還涉及到一些不常見的標籤例如,association、collection、discriminator,可以在官網看下說明,平時也不太推薦使用,我覺得目前隨手可見分庫分表的場景,mapper還是保持簡單,提供最基礎的sql就可以,不要使用什麼高級用法,維護、定位問題都麻煩。
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
// 遍歷 解析<resultMap />
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 解析type,優先級上到下,四個type的作用是一樣的
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 通過alias獲取type
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
// 遍歷子節點,解析成ResultMapping
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<>();
// id標籤解析
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 構造resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// resultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
這裏解析resultMap的步驟:
-
解析ResultMapping
-
構造器節點解析,構造成ResultMapping。
-
鑑別器解析,構造成ResultMapping。
-
通用解析,構造成ResultMapping。
-
-
構造ResultMap對象
構造器ResultMapping解析-processConstructorElement
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
// 遍歷constructor下的子節點
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
// 構造ResultMapping並添加進結果中
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
這裏對構造器ResultMapping都設置了ResultFlag.CONSTRUCTOR,對id標籤額外添加了ResultFlag.ID。
discriminator(幾乎不用,略過)
通用解析
List<ResultFlag> flags = new ArrayList<>();
// id標籤解析
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
// 構造resultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
這裏除了constructor、discriminator這兩種標籤,處理都是相同的。
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
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.emptyList(), resultType));
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);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
解析結果無非就是值對應,並設置到屬性中,比較枯燥。
當有了id、typeClass、ResultMapping之後,ResultMap也就隨即產生。
Sql標籤解析
在一些重複的sql片段上,我們會使用sql標籤進行抽取,以便複用。
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 直接設置了id 和 xcond的映射
sqlFragments.put(id, context);
}
}
}
Statement解析
這裏遍歷解析mapper下的所有Statement,委託給 XMLStatementBuilder進行解析。
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);
}
}
}
這裏Statement的解析也會複雜一些,放到下一篇中單獨講解。
總結
mapper的解析代碼還是蠻簡單的,根據文檔中定義的標籤,按順序解析,解析好的結果會存儲到Configuration中。
不過話說 MapperBuilderAssistant 真是一個神奇的類,它是一個工具類,用於在解析Mapper.xml中提供各位創建相關關鍵類的方法。
例如
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
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) {
這種方法入參這麼長?不是方法設計的最佳實踐,如果在我們開發過程中出現這樣的方法,那還不被人砍死。
那mybatis這麼做的原因是什麼呢?
首先 比如構造ResultMapping確實需要很多屬性設置,這裏應該是想MapperBuilderAssistant只做純粹的構造過程。
否則可以將入參改爲XNode,在內部進行獲取解析,那麼入參就短爲1個。如果有其他不是通過XNode來創建的場景,那麼就重載方法解決嘍。
這裏考慮到代碼的複用的問題,我覺得在應用開發過程中,可以犧牲一點 依賴獨立 換更多的代碼可讀性。
Thats All