第三章 映射文件解析
3.1 映射文件解析入口
映射文件的解析 过程是配置文件解析 过程的一部分 , MyBatis会在解析配置文件的过程中对映射文件进行解析。解析逻辑封装在mapperElement()
方法中,我们把这个方法作为本章的总入口方法。
这个方法主要对<mappers>
的每个子节点,按照不同的类型和属性,进行不同方式的解析,具体分派过程我放在源代码的注释中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 private void mapperElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String mapperPackage = child.getStringAttribute("name" ); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource" ); String url = child.getStringAttribute("url" ); String mapperClass = child.getStringAttribute("class" ); if (resource != null && url == null && mapperClass == null ) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null ) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null ) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only" + "specify a url, resource or class," + "but not more than one." ); } } } } }
从代码段3.0
中,我们可以看出,对于某个节点的解析逻辑,主要放在XMLMapperBuilder
的parse()
方法中,这个parse()
方法主要包含如下几步
mapper
节点的具体解析过程
将这个节点设置为已加载
通过命名空间绑定Mapper
接口
处理各个未完成解析的节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
3.2 解析映射文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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. The XML location is '" + resource + "'. Cause: " + e, e); } }
从上述代码段,我们可以得知,configurationElement()
主要对单个<mapper>
节点的各个类型的子节点进行解析,包括<cache>
、<cache-ref>
、<parameterMap>
、<resultMap>
、<sql>
、<select>
、<insert>
、<update>
、<delete>
等子节点。在本节的剩余部分,我们将挑选几个有特点的<mapper>
节点的子节点进行解析。
3.2.1 解析<cache>
节点
MyBatis提供了一、二级缓存,其中一级缓存是SqlSession
级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显式配置才能开启。
下面的代码段给出了一个配置二级缓存的例子
1 2 3 4 5 6 <cache eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="true" />
那么我们废话少说,从代码块3.2
的22行cacheElement(context.evalNode("cache"));
向下,具体分析cacheElement()
方法的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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); } }
cacheElement
方法的逻辑还是比较简单的,它从<cache>
节点上读取各种配置,然后使用这些配置。调用Mapper
建造助手来构建一个新的缓存。下面我们从这个代码段的27行builderAssistant.useNewCache(typeClass,evictionClass,flushInterval,size,readWrite,blocking,props);
向下,具体分析缓存的构造过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public Cache useNewCache (Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { Cache cache = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; return cache; }
在我们继续深入源代码之前,首先我想介绍一个Mybatis的Cache
接口极其实现类的设计。它使用一个装饰器模式。首先Cache
接口有一个普通实现,PerpetualCache
,它仅提供最基本的缓存功能,如果还需要其他功能,就需要将这个类作为delegate
,包装到Cache
接口的其他装饰器,例如,若我们想让Cache
具有日志功能,就使用LoggingCache
。下图展示了Cache
接口的大量实现。
在了解了Cache
接口的设计后,我们从3.5
代码段的第21行CacheBuilder.build()
向下,看看build()
具体做了什么工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public Cache build () { setDefaultImplementations(); Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache> decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache(cache); } return cache; }
第13行的setCacheProperties(cache);
主要将<cache>
节点的各个<property>
子节点设置进Cache
的具体实现中,这个没什么可分析的,跳过它。这里,我们继续分析23行的cache = setStandardDecorators(cache);
看看PerpetualCache
都会被设置哪些标准装饰器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 private Cache setStandardDecorators (Cache cache) { try { MetaObject metaCache = SystemMetaObject.forObject(cache); if (size != null && metaCache.hasSetter("size" )) { metaCache.setValue("size" , size); } if (clearInterval != null ) { cache = new ScheduledCache(cache); ((ScheduledCache) cache).setClearInterval(clearInterval); } if (readWrite) { cache = new SerializedCache(cache); } cache = new LoggingCache(cache); cache = new SynchronizedCache(cache); if (blocking) { cache = new BlockingCache(cache); } return cache; } catch (Exception e) { throw new CacheException("..." ); } }
结合上述代码,这里画一个图,来表示标准装饰器的装饰顺序
3.2.2 解析<resultMap>
节点
对于resultMap
,引用官方文档的一段话,来说明其强大作用。
resultMap元素是MyBatis中最重要最强大的元素。它可以让你从90%的 JDBC ResultSet数据提取代码中解放出来,并在一些情形下允许你做一些JBC不支持的事情。实际上,在对复杂语句进行联合映射的时候,它很可能可以代替数千行的同等功能的代码。 ResultMap的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述它们的关系就行了。
在分析源代码之前我们使用一个复杂的resultMap
的例子来展示它的强大功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <resulpMap type ="com.edu.neu.pojo.Employee" id ="employee" > <constructor > <idArg column ="id" property ="id" javaType ="int" jdbcType ="INT" /> <arg column ="real_name" property ="realName" /> <arg column ="email" property ="email" /> </constructor > <id column ="id" property ="id" /> <result column ="real_name" property ="realName" /> <result column ="sex" property ="sex" typeHandler ="com.edu.neu.Handler.SexTypeHandler" /> <result column ="email" property ="email" /> <association property ="workCard" column ="id" select ="com.edu.neu.mapper.WorkCardMapper.getWorkCardByEmpId" /> <collection property ="employeeTaskList" column ="id" select ="com.edu.neu.mapper.EmployeeTaskMapper.getEmployeeTaskByEmpId" /> </resulpMap >
这个例子几乎把<resultMap>
的子节点演示遍了。下面解释一下这些子节点的作用
constructor
元素:用来配置一个构造方法。MyBatis会根据这个配置找到合适的构造方法对这个类实例化
result
元素:配置的是POJO成员变量到SQL列的映射关系,column
代表SQL列名,property
代表属性名
id
元素:除了具有result
元素的功能,还表示了哪个列是主键,其实就是唯一标识列,不一定非要是主键。
association
元素:用来配置一个一对一的级联,例如上述代码段:当使用这个resultMap
时,还会顺便把WorkCard
也根据id
从数据库中取出
collection
元素:与association
元素类似,也是完成级联,但是它用于一对多级联,它将返回一个java.util.List
其他子节点并不常见,这里就不介绍了。
在正式分析解析逻辑之前,我们先看看存储结构,所有解析完成的ResultMap
都将存放在Configuration
的成员变量resultMaps
中,这个Map
的键为我们为<resultMap>
节点指定的id
属性,例如代码清单3.8
的employee
就将成为这个resultMap
的key
。而值为一个org.apache.ibatis.mapping.ResultMap
的实例,这个类存储了单个<resultMap>
的解析结果。
1 2 3 4 5 protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection" );
下面我们再看看上文中提到的org.apache.ibatis.mapping.ResultMap
中都存放了什么吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class ResultMap { private Configuration configuration; private String id; private Class<?> type; private List<ResultMapping> resultMappings; private List<ResultMapping> idResultMappings; private List<ResultMapping> constructorResultMappings; private List<ResultMapping> propertyResultMappings; private Set<String> mappedColumns; private Set<String> mappedProperties; private Discriminator discriminator; private boolean hasNestedResultMaps; private boolean hasNestedQueries; private Boolean autoMapping; }
其实,说白了,就是把单个<result>
、<id>
的解析结果,按照不同的类型,在不同的List
中存放了起来,仅此而已。下图是个很好的例子。
但是这还没完,上述代码块用到的ResultMapping
类,它看起来是存储单个POJO-SQL映射的类,我们接着分析它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class ResultMapping { private Configuration configuration; private String property; private String column; private Class<?> javaType; private JdbcType jdbcType; private TypeHandler<?> typeHandler; private String nestedResultMapId; private String nestedQueryId; private Set<String> notNullColumns; private String columnPrefix; private List<ResultFlag> flags; private List<ResultMapping> composites; private String resultSet; private String foreignColumn; private boolean lazy; }
那么resultMap
的存储结构就分析完了,我们继续看源码,从代码清单3.2的28行resultMapElements(context.evalNodes("/mapper/resultMap"));
向下,详细分析resultMap
的解析过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 private void resultMapElements (List<XNode> list) throws Exception { for (XNode resultMapNode : list) { try { resultMapElement(resultMapNode); } catch (IncompleteElementException e) { } } } 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()); String type = resultMapNode.getStringAttribute("type" , resultMapNode.getStringAttribute("ofType" , resultMapNode.getStringAttribute("resultType" , resultMapNode.getStringAttribute("javaType" )))); Class<?> typeClass = resolveClass(type); if (typeClass == null ) { typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null ; List<ResultMapping> resultMappings = new ArrayList<>(); 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<>(); if ("id" .equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings. add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } String id = resultMapNode.getStringAttribute("id" , resultMapNode.getValueBasedIdentifier()); String extend = resultMapNode.getStringAttribute("extends" ); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping" ); ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
上述resultMapElement
解析过程还是挺复杂的,这里总结一下,它完成的几项工作
获取<resultMap>
节点的各种属性
解析<resultMap>
的所有子节点,并把返回结果存起来
用第1步和第2步获取的信息构造一个ResultMap
对象
若第3步构造失败,则添加到未成功解析列表并抛出异常
第1步比较简单,大家一看就懂。第2步将在3.2.2.1中展开分析。第3步将在3.2.2.2中展开分析。
3.2.2.1 解析<resultMap>
节点中的<id>
节点和<result>
节点
本节以<id>
节点和<result>
节点为例,分析<resultMap>
节点的子节点是如何解析的。那我们从代码清单3.12
的71行resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
向下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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); }
这个方法实在乏善可陈,它只是从<result>
或者<id>
节点上获取了各种属性,然后将这些获取到的属性统统传递给builderAssistant.buildResultMapping()
,然后这个助手类完成真正的解析工作并返回ResultMapping
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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); 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<>() : flags) .composites(composites) .notNullColumns(parseMultipleColumnNames(notNullColumn)) .columnPrefix(columnPrefix) .foreignColumn(foreignColumn) .lazy(lazy) .build(); }
至于这个ResultMapping.Builder
就不继续深入了,它是一个简单的建造者负责建造ResultMapping
。其实就是通过各种方法调用,为ResultMapping
的实例设置属性而已。设置完成后,调用build()
直接返回这个实例。
下面总结一下,对于<id>
和<result>
这种代表单个POJO-SQL映射的标签,MyBatis会将标签携带的属性进行解析,并全部存放在一个ResultMapping
实例中返回。
3.2.2.2 构建ResultMap
对象的过程
我们从代码清单3.12
的93行return resultMapResolver.resolve();
,看看这个解析器是如何构建ResultMap
的
1 2 3 4 5 6 7 8 9 10 11 public ResultMap resolve () { return assistant.addResultMap(this .id, this .type, this .extend, this .discriminator, this .resultMappings, this .autoMapping); }
代码清单3.13
实际上调用了建造器助手的addResultMap
方法,我们继续向下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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 = configuration.getResultMap(extend); List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); extendedResultMappings.removeAll(resultMappings); boolean declaresConstructor = false ; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true ; break ; } } if (declaresConstructor) { extendedResultMappings.removeIf( resultMapping -> resultMapping. getFlags().contains(ResultFlag.CONSTRUCTOR) ); } resultMappings.addAll(extendedResultMappings); } ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }
这个方法实际上做了这几件事
处理resultMap
的继承(extend
属性)
通过ResultMap
的建造者构造ResultMap
实例
将这个ResultMap
实例保存到configuration
的resultMaps
中
下面我们继续看看这个建造者是怎么完成建造工作的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 public ResultMap build () { if (resultMap.id == null ) { throw new IllegalArgumentException("ResultMaps must have an id" ); } resultMap.mappedColumns = new HashSet<>(); resultMap.mappedProperties = new HashSet<>(); resultMap.idResultMappings = new ArrayList<>(); resultMap.constructorResultMappings = new ArrayList<>(); resultMap.propertyResultMappings = new ArrayList<>(); final List<String> constructorArgNames = new ArrayList<>(); for (ResultMapping resultMapping : resultMap.resultMappings) { resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null ; resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null ); final String column = resultMapping.getColumn(); if (column != null ) { resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH)); } else if (resultMapping.isCompositeResult()) { for (ResultMapping compositeResultMapping : resultMapping.getComposites()){ final String compositeColumn = compositeResultMapping.getColumn(); if (compositeColumn != null ) { resultMap.mappedColumns. add(compositeColumn.toUpperCase(Locale.ENGLISH)); } } } final String property = resultMapping.getProperty(); if (property != null ) { resultMap.mappedProperties.add(property); } if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { resultMap.constructorResultMappings.add(resultMapping); if (resultMapping.getProperty() != null ) { constructorArgNames.add(resultMapping.getProperty()); } } else { resultMap.propertyResultMappings.add(resultMapping); } if (resultMapping.getFlags().contains(ResultFlag.ID)) { resultMap.idResultMappings.add(resultMapping); } } if (resultMap.idResultMappings.isEmpty()) { resultMap.idResultMappings.addAll(resultMap.resultMappings); } if (!constructorArgNames.isEmpty()) { final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames); if (actualArgNames == null ) { throw new BuilderException("Error in result map '" + resultMap.id + "'. Failed to find a constructor in '" + resultMap.getType().getName() + "' by arg names " + constructorArgNames + ". There might be more info in debug log." ); } resultMap.constructorResultMappings.sort((o1, o2) -> { int paramIdx1 = actualArgNames.indexOf(o1.getProperty()); int paramIdx2 = actualArgNames.indexOf(o2.getProperty()); return paramIdx1 - paramIdx2; }); } resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings); resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings); resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings); resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings); resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns); return resultMap; }
上面的代码比较长,但实际上就如代码清单3.10
所示,它将传入的resultMappings
列表中的元素,按照不同的特点放入了不同的列表和集合中,仅此而已。
到此,我们就完成了ResultMap
对象的构建,并且将构建完的结果以id
做键、ResultMap
做值的形式存放到了configuration的resultMaps
映射中。本节比较值得学习的就是MyBatis对于建造者模式的使用。
3.2.3 解析<sql>
节点
<sql>
节点用来定义一些可重用的SQL语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过 <include>
节点引用<sql>
节点定义的内容。
在分析源码之前,先来演示一下<sql>
节点的使用方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <sql id ="table" > article </sql > <select id ="findOne" resultType ="Article" > SELECT id, title FROM <include refid ="table" /> WHERE id = #{id} </select > <update id ="update" parameterType ="Article" > UPDATE <include refid ="table" /> SET title = #{title} WHERE id = #{id} </update >
然后,我们从代码清单3.2
的32行sqlElement(context.evalNodes("/mapper/sql"));
继续向下,看看对于<sql>
节点的解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 private final Map<String, XNode> sqlFragments;private void sqlElement (List<XNode> list) { if (configuration.getDatabaseId() != null ) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null ); } 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)) { sqlFragments.put(id, context); } } }
<sql>
节点的解析非常简单,它只不过是完成了以下几件事
通过databaseId
筛选符合当前数据库的<sql>
节点
将符合要求的节点加入sqlFragment
映射,这个映射将在解析SQL语句节点时使用
并且,其实这个sqlFragement
也是存储在Configuration
中的,方便后面的使用。
3.2.4 解析SQL语句节点
下面是本章的重头戏,<select>
、<insert>
、<update>
、<delete>
等SQL语句节点的解析。这些节点的用处都是存储SQL语句,所以解析过程是相同的。
在分析之前,我们还是先看看在Configuration
中解析完的信息是怎么储存的。对于每个SQL语句节点,MyBatis都会解析成一个MappedStatement
的实例。然后在Configuration
中,是通过以id
为键,以MappedStatement
本身为值存储在了一个Map
中。
1 2 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection" );
接下来我们看看,上面提到的MappedStatement
都存储了那些信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets; }
让我们详细展开上一个代码清单的20行private SqlSource sqlSource;
看看SqlSource
是什么
1 2 3 4 5 6 7 8 public interface SqlSource { BoundSql getBoundSql (Object parameterObject) ; }
它是个接口,这个接口传入parameter
,然后返回一个BoundSql
实例。那我们接着看看BoundSql
的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; }
至此,和SQL语句有关的存储结构算是分析完了。
我们下面从代码清单3.2
的34行buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
继续向下,看看MappedStatement
是怎么构建的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private void buildStatementFromContext (List<XNode> list) { if (configuration.getDatabaseId() != null ) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null ); } 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); } } }
这个方法其实什么也没干,它制作遍历每个节点,然后把具体每个节点的解析交给XMLstatementBuilder
的parseStatementNode()
来处理,具体的解析逻辑都在这个方法里,那我们继续向下看看这个方法。
在看源码之前,先大体描述这个方法进行的几步操作
解析SQL语句中的<include>
节点,第34行
解析SQL语句中的<selectKey>
节点,第46行
解析SQL语句, 第67行
构建MappedStatement
,第93行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); if (!databaseIdMatchesCurrent(id, databaseId, this .requiredDatabaseId)) { return ; } 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 ); XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType" ); Class<?> parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang" ); LanguageDriver langDriver = getLanguageDriver(lang); processSelectKeyNodes(id, parameterTypeClass, langDriver); 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)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute( "statementType" , StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize" ); Integer timeout = context.getIntAttribute("timeout" ); String parameterMap = context.getStringAttribute("parameterMap" ); String resultType = context.getStringAttribute("resultType" ); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap" ); String resultSetType = context.getStringAttribute("resultSetType" ); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String keyProperty = context.getStringAttribute("keyProperty" ); String keyColumn = context.getStringAttribute("keyColumn" ); String resultSets = context.getStringAttribute("resultSets" ); builderAssistant.addMappedStatement( id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
下面的4小节,将分别展开这四个步骤
3.2.4.1 解析<include>
节点
注:下面的解析过程比较难,看不懂可以先看后面的例子。如果实在看不懂,这里讲一下这方法执行后的结果。它将XNODE
树上的<include>
节点替换成了包含对应sql
语句的普通文本节点。也就是说,经过这一步的处理,<include>
节点在XNODE
树中消失了。我们在mapper
文件的层次上举个不太恰切的例子。
在没进行解析时,XNODE
树是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <mapper namespace ="xyz.coolblog.dao.ArticleDao" > <sql id ="table" > ${table_name} </sql > <select id ="findOne" resultType ="xyz.coolblog.dao.Article" > SELECT id, title FROM <include refid ="table" > <property name ="table_name" value ="article" /> </include > WHERE id = #{id} </select > </mapper >
完成解析之后,它变成了一个再普通不过的sql
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <mapper namespace ="xyz.coolblog.dao.ArticleDao" > <sql id ="table" > ${table_name} </sql > <select id ="findOne" resultType ="xyz.coolblog.dao.Article" > SELECT id, title FROM article WHERE id = #{id} </select > </mapper >
只不过上面的解析,不是在mapper文件的层面上进行的,而是在XNODE
的层面进行的。大家体会理解意思即可。
然后我们从代码清单3.20
第37行includeParser.applyIncludes(context.getNode());
向下,看看<include>
节点的解析过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 private void applyIncludes (Node source, final Properties variablesContext, boolean included) { if (source.getNodeName().equals("include" )) { Node toInclude = findSqlFragment(getStringAttribute(source, "refid" ), variablesContext); Properties toIncludeContext = getVariablesContext(source, variablesContext); applyIncludes(toInclude, toIncludeContext, true ); if (toInclude.getOwnerDocument() != source.getOwnerDocument()) { toInclude = source.getOwnerDocument().importNode(toInclude, true ); } source.getParentNode().replaceChild(toInclude, source); while (toInclude.hasChildNodes()) { toInclude.getParentNode(). insertBefore(toInclude.getFirstChild(), toInclude); } toInclude.getParentNode().removeChild(toInclude); } else if (source.getNodeType() == Node.ELEMENT_NODE) { if (included && !variablesContext.isEmpty()) { NamedNodeMap attributes = source.getAttributes(); for (int i = 0 ; i < attributes.getLength(); i++) { Node attr = attributes.item(i); attr.setNodeValue(PropertyParser.parse( attr.getNodeValue(), variablesContext)); } } NodeList children = source.getChildNodes(); for (int i = 0 ; i < children.getLength(); i++) { applyIncludes(children.item(i), variablesContext, included); } } else if ( included && source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) { source.setNodeValue( PropertyParser.parse( source.getNodeValue(), variablesContext)); } }
然后我们以代码清单3.21
从8-12行的<select>
节点的解析为例,详细看看<include>
是怎么被替换的。
但是这里还要插入一个先序知识:在XNODE
树中,所有的<xxx>
标签会被解析为ELEMENT_NODE
,而所有<xxx>
和</xxx>
间的文本将被解析为TEXT_NODE
,除此之外还有ATTRIBUTE_NODE
、COMMENT_NODE
等很多节点类型,有兴趣可以查看org.w3c.dom.Node
接口
那么这个<select>
节点的类型为ELEMENT_NODE
,它有三个子节点,如下表
编号
子节点
类型
描述
1
SELECT id,title FROM
TEXT_NODE
文本节点
2
<include refid="table"/>
ELEMENT_NODE
普通节点
3
WHERE id= #{id}
TEXT_NODE
文本节点
那么调用的入口代码清单3.20
第34行includeParser.applyIncludes(context.getNode());
传入的XNode
显然是<select>
节点。它会进入第二个条件,遍历自己的3个孩子节点。第一个节点和第二个节点的调用栈如下图
3.2.4.2 解析<selectKey>
节点
对于一些不支持自增主键的数据库来说,我们在插入数据时,需要明确指定主键数据。例如
1 2 3 4 5 6 7 8 9 10 <insert id ="saveAuthor" > <selectKey keyProperty ="id" resultType ="int" order ="BEFORE" > select author_seq.nextval from dual </selectKey > insert into Author (id, name, password) values (#{id}, #{username}, #{password}) </insert >
这部分的源码就不展开解析了。当Mybatis完成解析后,也会将<selectKey>
节点从XNODE
树中去掉
3.2.4.3 解析SQL语句生成SqlSource
经过上两节的解析,MyBatis已经把<select|insert|delete|create>
中所有的<include>
和<selectKey>
子孙节点全部都替换并删除掉了。现在XNODE
树中只有<if>
、<where>
等普通的ELEMENT
节点和文本节点。这一步,我们将分析MyBatis是如何解析<select|insert|delete|create>
节点的XNODE
树,来生成SqlSource
。当处理用户的实际调用时,MyBatis将通过SqlSource
来解析出具体的SQL语句。
我们从代码清单3.20
的70行SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
继续向下。
1 2 3 4 5 6 7 8 9 10 11 12 13 public SqlSource createSqlSource ( Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }
这个方法,只是通过调用XMLScriptBuilder
的parseScriptNode()
来实现生成SqlSource
的具体逻辑而已,因此我们继续向下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public SqlSource parseScriptNode () { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
这里首先解释一下什么是动态SQL什么是静态SQL,动态SQL指的包含${}
占位符或者<if>
、<where>
等动态语句节点的SQL。注意:只包含#{}
并不算动态SQL。
在继续分析主要逻辑之前,我们先看看MixSqlNode
是什么。对于每个XNODE
片段,在经过解析后都会变成一个SqlNode
节点,比如TEXT
节点将被解析为一个StaticTextSqlNode
,而<if>
节点将被解析为一个IfSqlNode
。比较特殊的是MixSqlNode
,它存储一个SqlNode
类型的列表。类图如下面两张图。
在大致了解了SqlNode
之后,我们从代码清单3.24
的第7行继续向下,看看<select|insert|delete|update>
这个XNode
是怎么被解析为一个MixedSqlNode
的。下面源码的逻辑如下
遍历<select|insert|delete|update>
节点的所有子节点
如果子节点是TEXT类型的,则根据是动态还是静态,解析TextSqlNode
或者StaticTextSqlNode
,并将解析结果放入contents
列表
如果子节点是ELEMENT类型的,那么根据标签名称来选取合适的NodeHandler
解析,解析结果也会被放入contents
列表
最后通过第2步和第3步得到的contents
列表,生成一个MixedSqlNode
,并作为SqlNode
树的根节点返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 protected MixedSqlNode parseDynamicTags (XNode node) { List<SqlNode> contents = new ArrayList<>(); 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) { String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null ) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement." ); } handler.handleNode(child, contents); isDynamic = true ; } } return new MixedSqlNode(contents); }
然后我们从上面代码清单的53行handler.handleNode(child, contents);
向下,以一个If
类型的NodeHandler
为例,看看ELEMENT
节点是怎么被解析的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private class IfHandler implements NodeHandler { public IfHandler () { } @Override public void handleNode (XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test" ); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }
简单来说,就是对于<select|create|insert|delete>
节点来说,它们的所有子节点内容将被解析为一棵节点类型为SqlNode
的树,例如下图:这棵树储存在MappedStatement.SqlSource.rootSqlNode
中,当运行时,用户调用传入具体参数,MyBatis就可以通过这棵树来生成具体的SQL语句了。至此,我们详细了解了SqlSource的生成过程,以及SqlSource的某些内部存储方式。
3.2.4.4 构建MappedStatement
接着,我们从代码清单3.20
的93行builderAssistant.addMappedStatement(xxx)
向下,看一下存储SQL语句节点解析结果的MappedStatement
是如何构建的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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; }
3.2.4.5 总结
下面总结下本大节的内容。本节主要完成<select|insert|delete|update>
节点的解析工作。每一个这类型节点通过解析后都会生成一个MappedStatement
实例,储存具体的信息。对于它的<inculde>
和<selectKey>
子节点,将被解析替换为正常的SQL节点。然后在完成了替换后<inculde>
和<selectKey>
子节点将被从XNode
树中删除。这之后,会解析这个干净的XNode
树,每个具体的SQL语句节点将被转义并存储到可以一颗SqlNode
类型的树中,在运行时,我们通过解析这棵树将获取具体的SQL语句。然后,我们把MappedStatement
节点存储到Configuration
中。一个<select|insert|delete|update>
节点的解析工作就完成了。
3.3 Mapper接口绑定过程
当我们完成 了<mapper>
文件的解析后,还需要通过绑定,将<mapper>
文件中的每个SQL语句节点与java代码中对应mapper
接口的对应方法绑定起来,存放到Configuration.MapperRegistry
的Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
中,它也一个Class
对象为键,以MapperProxyFactory
为值,这个工厂可以通过反射为给类型的mapper
接口生成实例。
这部分也不展开解释了,假如可以看懂第4章的sql执行过程,这个绑定过程也不在话下。
下面的代码是从代码清单3.1
的17行bindMapperForNamespace();
向下,完成具体的绑定过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private void bindMapperForNamespace () { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null ) { Class<?> boundType = null ; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null ) { if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }
接下来,我们从上述代码清单的26行configuration.addMapper(boundType);
向下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory<>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }