Mybatis初始化過程當中,解析parameterMap、resultMap、"select|insert|update|delete"元素,無疑是重頭戲。本節將詳細分析解析過程。java
元素parameterMap將會解析爲ParameterMap對象,該對象包含一個List<ParameterMapping>集合,是one-to-many關係。node
元素resultMap將會解析爲ResultMap對象,該對象包含一個List<ResultMapping>集合,是one-to-many關係。sql
元素"select|insert|update|delete"將會被解析爲MappedStatement對象,該對象包含了ParameterMap、ResultMap等對象。apache
1. 解析parameterMap元素網絡
(Made In Visual Paradigm)數據結構
MapperBuilderAssistant是一個通用構建Mapper輔助類。app
ParameterMapping.Builder用於構建ParameterMapping對象,而ParameterMap.Builder則用於構建ParameterMap對象。fetch
其中resolveTypeHandler()很重要,咱們自定義的TypeHandler要起做用,就靠該方法正確綁定TypeHandler了,後續會單獨開一篇關於TypeHandler的文章。ui
下面看看Mabtis解析parameterMap元素的源碼。this
private void parameterMapElement(List<XNode> list) throws Exception { 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<ParameterMapping>(); // 循環得到全部的ParameterMapping集合 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); @SuppressWarnings("unchecked") Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); parameterMappings.add(parameterMapping); } // 建立ParameterMap並加入List<ParameterMapping>,同時把ParameterMap註冊到Configuration內。 builderAssistant.addParameterMap(id, parameterClass, parameterMappings); } }
再看看builderAssistant.buildParameterMapping()方法源碼。
public ParameterMapping buildParameterMapping( Class<?> parameterType, String property, Class<?> javaType, JdbcType jdbcType, String resultMap, ParameterMode parameterMode, Class<? extends TypeHandler<?>> typeHandler, Integer numericScale) { resultMap = applyCurrentNamespace(resultMap, true); Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType); TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); // 下面的一系列方法鏈,其實都是賦值語句 return new ParameterMapping.Builder(configuration, property, javaTypeClass) .jdbcType(jdbcType) .resultMapId(resultMap) .mode(parameterMode) .numericScale(numericScale) .typeHandler(typeHandlerInstance) .build(); // 內部將調用resolveTypeHandler()方法 }
在看看build()方法。
public ParameterMapping build() { // 給每個ParameterMapping綁定一個TypeHandler,且必須綁定 resolveTypeHandler(); validate(); return parameterMapping; }
一個ParameterMapping,其實就是一個參數屬性的封裝,從jdbcType到javaType的轉換,或者從javaType到jdbcType的轉換,全由TypeHandler處理。
到此,一個ParameterMapping就解析結束了。
最後,看看ParameterMap是如何建立並註冊的。
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) { // 處理namespace名稱空間 id = applyCurrentNamespace(id, false); ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build(); // 註冊至Configuration configuration.addParameterMap(parameterMap); return parameterMap; }
public void addParameterMap(ParameterMap pm) { // 放到map中 parameterMaps.put(pm.getId(), pm); }
至此,一個ParameterMap就解析完了。
2. 解析ResultMap元素
(Made In Visual Paradigm)
解析ResultMap元素和解析parameterMap元素是極其類似的,有區別的地方,主要是ResultMap有繼承(extends)的功能,以及ResultMap會將List<ResultMapping>再進行一次計算,拆分爲多個List<ResultMapping>對象,也就是大集合,分類拆分爲多個小集合。
public class ResultMap { // ... private List<ResultMapping> resultMappings; private List<ResultMapping> idResultMappings; private List<ResultMapping> constructorResultMappings; private List<ResultMapping> propertyResultMappings; // ... }
org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap()方法源碼。
public ResultMap addResultMap( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { id = applyCurrentNamespace(id, false); extend = applyCurrentNamespace(extend, true); if (extend != null) { if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } // 處理繼承ResultMap屬性 ResultMap resultMap = configuration.getResultMap(extend); List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings()); // 刪除重複元素 extendedResultMappings.removeAll(resultMappings); // Remove parent constructor if this resultMap declares a constructor. boolean declaresConstructor = false; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } if (declaresConstructor) { Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator(); while (extendedResultMappingsIter.hasNext()) { if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { extendedResultMappingsIter.remove(); } } } // 合併 resultMappings.addAll(extendedResultMappings); } ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); // build()內將大集合,分類拆分爲多個小集合。 // 註冊到Configuration內 configuration.addResultMap(resultMap); return resultMap; }
至此,一個ResultMap就解析完了。且每個ResultMapping,都綁定了一個TypeHandler,和ParameterMapping同樣。
3. 解析"select|insert|update|delete"元素
(Made In Visual Paradigm)
org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()源碼。
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); }
全程平面式的解析,最後生成MappedStatement對象,並註冊至Configuration內部。
解析過程當中,出現的一些陌生的配置參數或類,如KeyGenerator、SqlSource、ResultSetType、LanguageDriver、constructor、discriminator等等,後續會逐一進行詳細的分析。
總結:解析的過程,因爲要處理很是多的配置參數,代碼顯得很長,可是,抓住Xml元素至Mybatis內部的數據結構映射關係,閱讀起來就容易的多了。
版權提示:文章出自開源中國社區,若對文章感興趣,可關注個人開源中國社區博客(http://my.oschina.net/zudajun)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)