一:映射文件介绍
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。简单说:就是写SQL代码的地方。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
二:映射文件解析流程
映射文件解析是包含在配置文件解析过程中的,在上一章节有提到的,即配置文件解析篇(https://blog.youkuaiyun.com/qq_41961316/article/details/107403237)。
1.在配置文件中配置映射文件(映射器)的常见配置
<!-- 使用映射器接口实现类的完全限定类名 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
2.常见的映射文件
<mapper namespace="org.apache.ibatis.mytest.MyDao"> <insert id="insertAuthor" parameterType="org.apache.ibatis.domain.blog.Author"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert> <select id="selectAll" parameterType="java.lang.String" resultType="java.util.HashMap"> select * from Author where id = #{id} </select> </mapper>
3.映射文件解析流程
在配置文件解析过程中,解析映射文件
XMLConfigBuilder:
private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析properties标签 propertiesElement(root.evalNode("properties")); //解析setting配置,并转换为properties Properties settings = settingsAsProperties(root.evalNode("settings")); //加载vfs? loadCustomVfs(settings); loadCustomLogImpl(settings); // 解析 typeAliases 配置 typeAliasesElement(root.evalNode("typeAliases")); // 解析 plugins 配置 pluginElement(root.evalNode("plugins")); // 解析 objectFactory 配置 objectFactoryElement(root.evalNode("objectFactory")); // 解析 objectWrapperFactory 配置 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析 reflectorFactory 配置 reflectorFactoryElement(root.evalNode("reflectorFactory")); // settings 中的信息设置到 Configuration 对象中 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 解析 environments 配置 environmentsElement(root.evalNode("environments")); // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析 typeHandlers 配置 typeHandlerElement(root.evalNode("typeHandlers")); // 解析 mappers 配置 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }/** * 解析mapper配置文件 .xml * @param parent * @throws Exception */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { // 获取 <package> 节点中的 name 属性 String mapperPackage = child.getStringAttribute("name"); // 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置 configuration.addMappers(mapperPackage); } else { //resource,url,class 只能有一个 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.1先来分析根据配置的包路径,如何解析映射文件,如以下配置:
<mappers> <package name="org.mybatis.builder"/> </mappers>
Configuration:public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); }MapperRegistry:public void addMappers(String packageName) { addMappers(packageName, Object.class); }public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>(); //通过 VFS(虚拟文件系统) 获取包下面的所有文件,上一章有说过 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); //获取文件集合 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } }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 { //将接口 传进去,方便创建代理对象,这一步是连接dao层接口和 映射器配置文件 的 桥梁 knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. //注解解析器 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //解析xml文件,以及 type接口上的方法注解 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
接下来通过 MapperAnnotationBuilder这个类解析 xml中的信息,已经接口层的注解,如@CacheNamespace,@Options
MapperAnnotationBuilder:public void parse() { String resource = type.toString(); //检查是否已经解析过映射文件 if (!configuration.isResourceLoaded(resource)) { //解析映射器xml文件 loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); //解析接口中的缓存注解 @CacheNamespace parseCache(); //解析接口中的共享缓存注解 @CacheNamespaceRef parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { //解析接口上面 方法的注解 parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }private void loadXmlResource() { // Spring may not know the real resource name so we check a flag // to prevent loading again a resource twice // this flag is set at XMLMapperBuilder#bindMapperForNamespace if (!configuration.isResourceLoaded("namespace:" + type.getName())) { /** * 根据接口名 获取 xml文件路径,这里可以看出 * <mappers> * <package name="org.mybatis.builder"/> * </mappers> * 这种包扫描方式,扫描的是 该路径下的接口, * 如果在接口路径下 ,存在接口对应的 xml映射文件,那么才能解析到xml映射文件 * 如: org.apache.test.MyTest.class * org/apache/test/MyTest.xml * * <mappers> * <mapper class="org.mybatis.builder.AuthorMapper"/> * <mapper class="org.mybatis.builder.BlogMapper"/> * <mapper class="org.mybatis.builder.PostMapper"/> * </mappers> * 这种方式 也是同理 */ String xmlResource = type.getName().replace('.', '/') + ".xml"; // #1347 InputStream inputStream = type.getResourceAsStream("/" + xmlResource); if (inputStream == null) { // Search XML mapper that is not in the module but in the classpath. try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e2) { // ignore, resource is not required } } if (inputStream != null) { //将 输入流 转为 Document对象 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); //解析 Document对象 xmlParser.parse(); } } }
看看XMLMapperBuilder具体解析 xml映射文件过程,分为3个步骤
1.解析xml文件,主要解析 cache,cache-ref,resultMap,sql,select|insert|update|delete 这些标签
2.根据xml中配置的命名空间,绑定相应的接口(namespace)
3.对于解析xml过程中有异常的,进行补偿处理
首先,看看解析 xml文件解析步骤
XMLMapperBuilder:public void parse() { //检查resource是否被解析过 if (!configuration.isResourceLoaded(resource)) { // 解析 mapper 节点 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); //通过命名空间绑定接口,这一步并没有创建代理对象 bindMapperForNamespace(); } //处理未完成解析的节点,resultMap,cache-ref,statements parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }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> 节点 //在 MyBatis 中,二级缓存是可以共用的。这需要通过<cache-ref>节点为命名空间配置参 //照缓存,引用其他命名空间的缓存,在一个mapper.xml文件中,cache 和 cache-ref一般只会存在一个 //如果都存在,看具体解析的顺序 cacheRefElement(context.evalNode("cache-ref")); //解析<cache> 节点,二级缓存,在解析 select|insert|update|delete 会将二级缓存设置进去 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); } }先看 cache,再看cache-ref
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子节点,结合第三方缓存 Properties props = context.getChildrenAsProperties(); //构建缓存对象,缓存用到了装饰者模式,自行分析 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }cache-ref
/** * Mapper1 与 Mapper2 共用一个二级缓存 * <cache-ref namespace="xyz.coolblog.dao.Mapper2"/> * @param context */ private void cacheRefElement(XNode context) { if (context != null) { configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { //应用 引用的缓存到当前xml映射器 cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { //捕捉异常,添加到未完成 map,后续步骤会继续尝试添加 configuration.addIncompleteCacheRef(cacheRefResolver); } } }CacheRefResolver:public Cache resolveCacheRef() { return assistant.useCacheRef(cacheRefNamespace); }MapperBuilderAssistant:public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { //后续构造 MappedStatement对象时判断该变量,true表明 Cache-ref已经解析成功 //false表明失败,MappedStatement也创建失败,抛异常(补偿措施) unresolvedCacheRef = true; // 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例 Cache cache = configuration.getCache(namespace); /* * 若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到 * cache 实例,分别如下: * 1.使用者在 <cache-ref> 中配置了一个不存在的命名空间, * 导致无法找到 cache 实例 * 2.使用者所引用的缓存实例还未创建(后续步骤会补全),和xml加载顺序相关 */ if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } //设置当前 xml映射文件的 缓存对象 currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }resultMap:结果映射,建议调试几遍,容易理解XMLMapperBuilder:private void resultMapElements(List<XNode> list) throws Exception { for (XNode resultMapNode : list) { try {//解析单个 <resultMap></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属性 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) {//解析子标签<result> </result>,<id> if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { //discriminator 根据数据库返回结果值,决定引用 哪个resultMap discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } //解析id和type, 创建ResultMapping,获取<result> </result>标签中的所有属性 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } //解析id属性 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 { // 根据前面获取到的信息构建 ResultMap 对象, id --> ResultMap return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }解析查询语句:select|insert|update|delete
XMLMapperBuilderprivate 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: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); // Include Fragments before parsing,解析include标签 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); // Parse selectKey after includes and remove them. //解析<selectKey>标签 // <selectKey keyProperty="id" resultType="int" order="BEFORE"> // select author_seq.nextval from dual // </selectKey> processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 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; } //解析sql语句 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"); //通过别名解析 resultType 对应的类型 Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); //创建 MappedStatement,包含了sql的所有信息,如sql语句,入参,结果映射,缓存, 保存到configuration中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }主要看一下解析sql:SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
XMLLanguageDriver:public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { //会初始化一些 nodeHandler,处理动态节点,if ,where等等 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }XMLScriptBuilder:public SqlSource parseScriptNode() { //解析sql语句,以及包含的动态标签 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { //这种类型的sql,后续执行sql时,需要将继续解析,因为sql语句依赖输入参数 sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { //这一步会将 rootSqlNode里面contents集合拼接成完整字符串, //同时将 #{}转为 ?,并保存参数信息 sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }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));//获取下一个完整标签 //判断标签的type,是否只包含 text(没有动态标签) 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()) {//检查text中是否包含 ${} 这种字符串,是说明是动态的 contents.add(textSqlNode); //动态节点标志位 isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {//标签元素,如set,if等等// issue #628 String nodeName = child.getNode().getNodeName(); //获取对应的动态节点handler,在初始化步骤添加了 NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } //用 各种动态节点NodeHandler 解析 XNode,将结果添加到 contents handler.handleNode(child, contents); //动态节点标志位 isDynamic = true; } } return new MixedSqlNode(contents); }
xml映射文件解析,最终每个select,insert,update,delete 都会生成一个 MappedStatement,包含了所有信息,如sql语句,入参,结果映射,缓存, 保存到configuration中
然后,看看 根据xml中配置的命名空间,绑定相应的接口(namespace)步骤
XMLMapperBuilderprivate void bindMapperForNamespace() { //获取命名空间,解析xml时以保存 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { //加载类 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { if (!configuration.hasMapper(boundType)) {//检查是否重复 // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); //绑定 namespace对应的接口,上面已经讲过 configuration.addMapper(boundType); } } } }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 { //将接口 传进去,方便创建代理对象,这一步是连接dao层接口和 映射器配置文件 的 桥梁 knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. //注解解析器 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); //解析xml文件,以及 type接口上的方法注解 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
最后,看看 对于解析xml过程中有异常的,如何进行补偿处理
public void parse() { //检查resource是否被解析过 if (!configuration.isResourceLoaded(resource)) { // 解析 mapper 节点 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); //通过命名空间绑定接口,这一步并没有创建代理对象 bindMapperForNamespace(); } //处理未完成解析的节点,resultMap,cache-ref,statements parsePendingResultMaps(); //cacheRef异常 parsePendingCacheRefs(); parsePendingStatements(); }private void parsePendingCacheRefs() { //在捕获异常的时候,添加相应的异常信息 Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs(); synchronized (incompleteCacheRefs) { Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator(); while (iter.hasNext()) { try { //在已解析过的 cache标签中寻找 cacheRef对应的 namespace 引用cache, iter.next().resolveCacheRef(); iter.remove(); } catch (IncompleteElementException e) { // Cache ref is still missing a resource... } } } }在解析 cacheRef标签时,出现异常,进行捕获
private void cacheRefElement(XNode context) { if (context != null) { configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { // cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { //捕捉异常,添加到未完成 map,后续步骤会继续尝试添加 configuration.addIncompleteCacheRef(cacheRefResolver); } } }
xml映射文件解析过程大致如上,其中,对于sql 动态节点的解析需要多调试,因为确实不太好说。。
剩下的其他配置映射文件的方式,如
<!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> </mappers>
解析过程的代码都是大致相同的,自行调试
4.总结
映射文件解析过程相对复杂,尤其是结果映射中的嵌套映射,sql语句中的动态节点解析,需要多调试。
接下去将进入到真正的 执行sql流程,有兴趣可以关注。
最后,来一张映射文件解析流程图:
结语:大风起兮云飞扬,安得猛士兮走四方