Mybatis初始化过程中,解析parameterMap、resultMap、"select|insert|update|delete"元素,无疑是重头戏。本节将详细分析解析过程。
元素parameterMap将会解析为ParameterMap对象,该对象包含一个List<ParameterMapping>集合,是one-to-many关系。
元素resultMap将会解析为ResultMap对象,该对象包含一个List<ResultMapping>集合,是one-to-many关系。
元素"select|insert|update|delete"将会被解析为MappedStatement对象,该对象包含了ParameterMap、ResultMap等对象。
1. 解析parameterMap元素
(Made In Visual Paradigm)
MapperBuilderAssistant是一个通用构建Mapper辅助类。
ParameterMapping.Builder用于构建ParameterMapping对象,而ParameterMap.Builder则用于构建ParameterMap对象。
其中resolveTypeHandler()很重要,我们自定义的TypeHandler要起作用,就靠该方法正确绑定TypeHandler了,后续会单独开一篇关于TypeHandler的文章。
下面看看Mabtis解析parameterMap元素的源码。
private void parameterMapElement(Listlist) throws Exception { for (XNode parameterMapNode : list) { String id = parameterMapNode.getStringAttribute("id"); String type = parameterMapNode.getStringAttribute("type"); Class parameterClass = resolveClass(type); List parameterNodes = parameterMapNode.evalNodes("parameter"); List parameterMappings = new ArrayList (); // 循环获得所有的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 > typeHandlerClass = (Class >) resolveClass(typeHandler); ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); parameterMappings.add(parameterMapping); } // 创建ParameterMap并加入List ,同时把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 > 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, ListparameterMappings) { // 处理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 ListresultMappings; private List idResultMappings; private List constructorResultMappings; private List propertyResultMappings; // ...}
org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap()方法源码。
public ResultMap addResultMap( String id, Class type, String extend, Discriminator discriminator, ListresultMappings, 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 extendedResultMappings = new ArrayList (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 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:and 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)。(经过网络爬虫或转载的文章,经常丢失流程图、时序图,格式错乱等,还是看原版的比较好)