Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,environments节点的解析,总结一下,针对示例工程的configuration.xml文件来说properties节点的解析就是将dbConfig.properties中的数据库配置信息加载到了configuration实例的variables中,settings节点的解析让configuration使用了我们配置的log4j日志系统,environments节点的解析生成了数据库环境类(Environment)的实例对象,并将这个示例对象赋值给了 configuration的environment属性。那么接下来我们着重看一下mappers节点的解析。mappers节点的解析非常重要,所以本文篇幅会很长。

一,先看看示例工程的mappers节点和userDao-mapping.xml文件

  mappers节点:

1<!-- 映射文件,mybatis精髓 --> 2 <mappers> 3 <mapper resource="mapper/userDao-mapping.xml"/> 4 </mappers>

  userDao-mapping.xml文件:

1<?xml version="1.0" encoding="UTF-8" ?> 2<!DOCTYPE mapper 3PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" 4"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> 5<mapper namespace="com.zcz.learnmybatis.dao.UserDao"> 6 7 <select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 8 select * from user where id = #{id} 9 </select> 10 11</mapper>

  userDao-mapping.xml文件很简单,定义了一个namespace属性指向UserDao接口,定义了一个select标签声明。

二,看一下解析mappers节点的方法mapperElement的源码:

1private void mapperElement(XNode parent) throws Exception { 2 if (parent != null) { 3 for (XNode child : parent.getChildren()) { 4 if ("package".equals(child.getName())) { 5 //检测是否是package节点 6 String mapperPackage = child.getStringAttribute("name"); 7 configuration.addMappers(mapperPackage); 8 } else { 9 //读取<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml" 10 String resource = child.getStringAttribute("resource"); 11 //读取mapper节点的url属性 12 String url = child.getStringAttribute("url"); 13 //读取mapper节点的class属性 14 String mapperClass = child.getStringAttribute("class"); 15 if (resource != null && url == null && mapperClass == null) { 16 //根据rusource加载mapper文件 17 ErrorContext.instance().resource(resource); 18 //读取文件字节流 19 InputStream inputStream = Resources.getResourceAsStream(resource); 20 //实例化mapper解析器 21 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 22 //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件 23 mapperParser.parse(); 24 } else if (resource == null && url != null && mapperClass == null) { 25 //从网络url资源加载mapper文件 26 ErrorContext.instance().resource(url); 27 InputStream inputStream = Resources.getUrlAsStream(url); 28 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 29 mapperParser.parse(); 30 } else if (resource == null && url == null && mapperClass != null) { 31 //使用mapperClass加载文件 32 Class<?> mapperInterface = Resources.classForName(mapperClass); 33 configuration.addMapper(mapperInterface); 34 } else { 35 //resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错 36 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); 37 } 38 } 39 } 40 } 41 }

  一目了然,遍历mappers中的mapper节点,然后逐一解析。我们的配置文件中只有一个mapper节点,所以这里要解析的就是mapper/userDao-mapping.xml文件。

  解析mapper/userDao-mapping.xml文件的关键代码是

    //实例化mapper解析器
    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    //执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
    mapperParser.parse();

  接下来逐句进行分析

三,实例化mapper解析器:XMLMapperBuilder

  代码:XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());

  查看mapper解析器XMLMapperBuilder类的声明可以发现,mapper解析器类XMLMapperBuilder和xml配置解析器XMLConfigBuilder同时继承了父类BaseBuilder。其实后面还有几个类也继承了父类BaseBuilder。

public class XMLMapperBuilder extends BaseBuilder {}

  看一下使用到的XMLMapperBuilder构造方法

1public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { 2 this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), 3 configuration, resource, sqlFragments); 4 }

  这里也创建了一个XPathParser xml文件解析器,原因很简单,因为mapper/userDao-mapping.xml也是一个xml文件(手动捂脸)。至于最后一个参数 Map<String, XNode> sqlFragments 是什么?现在只知道sqlFragments = configuration.getSqlFragments(),但是具体是什么呢?稍后纤细介绍。

  接下来使用this关键字,调用了XMLMapperBuilder的私有构造方法:

1private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { 2 super(configuration); 3 this.builderAssistant = new MapperBuilderAssistant(configuration, resource); 4 this.parser = parser; 5 this.sqlFragments = sqlFragments; 6 this.resource = resource; 7 }

  看过Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)的同学想必已经知道supper()方法做了什么,这里就不再赘述了。值得一提的是MapperBuilderAssistant(mapper解析器助手),既然是助手,那肯定就是辅助mapper解析器解析mapper文件的了。

public class MapperBuilderAssistant extends BaseBuilder {}

  看看MapperBuilderAssistant类的声明,它也是继承了BaseBuilder的。

  到这里mapper解析器(XMLMapperBuilder)的实例化工作就已经完成了。但是为了更好了进行接下来的分析,我们有必要再认识Configuration类的一些属性和方法:

1public class Configuration { 2 3 protected Environment environment; 4 protected Properties variables = new Properties(); 5 ...... 6 //初始化值为null 7 protected String databaseId; 8 protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); 9 protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); 10 11 // 这是一个HashMap ,存放的是已经解析过的sql声明,String 类型的键,例如com.zcz.learnmybatis.entity.User.findUserById,值是MappedStatement实例对象 12 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); 13 protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); 14 ...... 15 //这是一个无序不重复的Set集合,里面存放的是已经加载解析过的 mapper文件名。例如<mapper resource="mapper/userDao-mapping.xml"/>中的mapper/userDao-mapping.xml 16 protected final Set<String> loadedResources = new HashSet<String>(); 17 ...... 18 //sql碎片Map,键String 值XNode,这个Map中存放的是已经在先前的mapper中解析过的碎片 19 protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers"); 20 ...... 21 public Configuration() { 22 ..... 23 // 注册默认的XML语言驱动 24 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 25 languageRegistry.register(RawLanguageDriver.class); 26 } 27 ...... 28 //将resource 添加到 加载解析完成Set loadedResources中 29 public void addLoadedResource(String resource) { 30 loadedResources.add(resource); 31 } 32 //检测mapper文件是否已经被加载解析过,resource是<mapper resource="mapper/userDao-mapping.xml"/>中的resource 33 public boolean isResourceLoaded(String resource) { 34 return loadedResources.contains(resource); 35 } 36 37 ...... 38 39 //根据标签声明类(MappedStatement) 实例对象的id获取 解析过的标签声明类实例对象 // 标签声明是什么,在下文中会给出解释 40 public MappedStatement getMappedStatement(String id) { 41 return this.getMappedStatement(id, true); 42 } 43 44 //根据标签声明类(MappedStatement) 实例对象的id获取 解析过的标签声明类实例对象 45 public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { 46 if (validateIncompleteStatements) { 47 buildAllStatements(); 48 } 49 return mappedStatements.get(id); 50 } 51 52 //获取sql碎片 53 public Map<String, XNode> getSqlFragments() { 54 return sqlFragments; 55 } 56 57 ...... 58 // 根据检查是否存在 标签声明名称 为statementName 的标签声明 59 public boolean hasStatement(String statementName) { 60 return hasStatement(statementName, true); 61 } 62 // 根据检查是否存在 标签声明名称 为statementName 的标签声明 63 public boolean hasStatement(String statementName, boolean validateIncompleteStatements) { 64 if (validateIncompleteStatements) { 65 buildAllStatements(); 66 } 67 return mappedStatements.containsKey(statementName); 68 } 69 ...... 70}

四,执行解析mapper文件,即解析mapper/userDao-mapping.xml文件

  代码:mapperParser.parse();

  看一下负责解析mapper文件的parser方法的源代码:

1public void parse() { 2 // 先判断mapper文件是否已经解析 3 if (!configuration.isResourceLoaded(resource)) { 4 //执行解析 5 configurationElement(parser.evalNode("/mapper")); 6 //保存解析记录 7 configuration.addLoadedResource(resource); 8 // 绑定已经解析的命名空间 9 bindMapperForNamespace(); 10 } 11 12 ...... 13 }

  很明显,真正解析mapper文件的代码是configurationElement方法:

1private void configurationElement(XNode context) { 2 try { 3 //获取mapper文件中的mapper节点的namespace属性 com.zcz.learnmybatis.entity.UserDao 4 String namespace = context.getStringAttribute("namespace"); 5 if (namespace.equals("")) { 6 throw new BuilderException("Mapper's namespace cannot be empty"); 7 } 8 //将namespace赋值给映射 mapper解析器助理builderAssistant.currentNameSpace,即告诉mapper解析器助理现在解析的是那个mapper文件 9 builderAssistant.setCurrentNamespace(namespace); 10 //解析cache-ref节点 11 cacheRefElement(context.evalNode("cache-ref")); 12 //解析cache节点 13 cacheElement(context.evalNode("cache")); 14 //解析parameterMap节点,这里为什么要使用"/mapper/parameterMap"而不是直接使用"parameterMap",因为parameterMap可以配置多个,而且使用的是context.evalNodes方法,注意不是evalNode了,是evalNodes。 15 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 16 //解析resultMap节点 17 resultMapElements(context.evalNodes("/mapper/resultMap")); 18 //解析sql节点 19 sqlElement(context.evalNodes("/mapper/sql")); 20 //解析select|insert|update|delete节点,注意context.evalNodes()方法,返回的是一个List集合。 21 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 22 } catch (Exception e) { 23 throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); 24 } 25 }

  从上面代码看到,处理完namespace之后,就是解析mapper文件中的节点了,但是我们的userDao-mapping.xml文件中只有一个select标签:

1<select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 2 select * from user where id = #{id} 3 </select>

  那么只需要分析最有一个方法buildStatementFromContext就可以了。

  看源码:

  这个方法中的唯一的参数list 就是userDao-mapping.xml中的select,update,delete,insert标签们。注意是一个List。也就是说可能会有多个。

1private void buildStatementFromContext(List<XNode> list) {   //这里的confuration.getDatabaseId 是 null ,因为 configuration初始化时没有给默认值,在虚拟机实例化configuration对象时,赋予默认值null 2 if (configuration.getDatabaseId() != null) { 3 buildStatementFromContext(list, configuration.getDatabaseId()); 4 } 5 buildStatementFromContext(list, null); 6 }

  又调用了buildStatementFromContext 重载方法:

  在这个方法中遍历了上面我们提到的list.而我们的userDao-mapping.xml中的select标签就是在这个list中。

  我们都知道,在mapper文件中,select标签,update标签,delete标签,insert标签的id属性对应着namespace中的接口的方法名。所以我们就称一个select标签,update标签,delete标签或者一个insert标签为一个标签声明。

  那么这个方法中就是遍历了所有的标签声明,并逐一解析。

1private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 2 for (XNode context : list) {    //初始化标签声明解析器(XMLStatementBuilder) 3 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); 4 try {      // 执行标签声明的解析 5 statementParser.parseStatementNode(); 6 } catch (IncompleteElementException e) { 7 configuration.addIncompleteStatement(statementParser); 8 } 9 } 10 }

  到现在我们终于明白,原来buildStatementFromContext 也是不负责解析的,真正负责解析的是final 修饰的 XMLStatementBuilder类 的实例对象 statementParser。

public class XMLStatementBuilder extends BaseBuilder {}

  发现了什么?

  XMLStatementBuilder也是继承BaseBuilder的。

  看看构造方法:

1public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) { 2 super(configuration); 3 this.builderAssistant = builderAssistant; 4 this.context = context; 5 this.requiredDatabaseId = databaseId; 6 }

  只有一些赋值操作。

  重点就是try-catch块中的执行标签声明的解析的:statementParser.parseStatementNode();这一句代码了。

  看源码:

1 1 public void parseStatementNode() { 2 2 // 获取的是select 标签的id属性,即id="findUserById" 3 3 String id = context.getStringAttribute("id"); 4 4 // 没有databaseId属性,即databaseId = null; 5 5 String databaseId = context.getStringAttribute("databaseId"); 6 6    // 判断databaseId,这一行代码下方有详细介绍 7 7 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; 8 8 // 没有fetchSize属性,即fetchSize = null; 9 9 Integer fetchSize = context.getIntAttribute("fetchSize"); 1010 // 没有timeout属性,即timeout = null; 1111 Integer timeout = context.getIntAttribute("timeout"); 1212 // 没有 parameterMap 属性,即 parameterMap = null; 1313 String parameterMap = context.getStringAttribute("parameterMap"); 1414 // 没有 parameterType 属性,即 parameterType = null; 1515 String parameterType = context.getStringAttribute("parameterType"); 1616 // parameterType = null,即parameterTypeClass = null 1717 Class<?> parameterTypeClass = resolveClass(parameterType); 1818 // 没有 resultMap 属性,即 resultMap = null; 1919 String resultMap = context.getStringAttribute("resultMap"); 2020 // 获取的是select 标签的resultType属性,即resultType="com.zcz.learnmybatis.entity.User" 2121 String resultType = context.getStringAttribute("resultType"); 2222 // 没有 lang 属性,即 lang = null; 2323 String lang = context.getStringAttribute("lang"); 2424 //获取默认的语言驱动 XMLLanguageDriver 2525 LanguageDriver langDriver = getLanguageDriver(lang); 2626 2727 // 获取User类的类对象 2828 Class<?> resultTypeClass = resolveClass(resultType); 2929 // 没有 resultSetType 属性,即 resultSetType = null; 3030 String resultSetType = context.getStringAttribute("resultSetType"); 3131 // 没有 statementType 属性,返回默认的 “PREPARED” 即statementType = PREPARED 3232 StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); 3333 // 没有 resultSetType 属性,即 resultSetType = null; 3434 ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 3535 3636 // nodeName = "select" 3737 String nodeName = context.getNode().getNodeName(); 3838 // sql类型是 sqlCommandType = SELECT 3939 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); 4040 // isSelect = true; 4141 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 4242 // 没有 flushCache 属性,即 flushCache = null; 取默认flushCache = !isSelect = false; 4343 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); 4444 // 没有 useCache 属性,即 useCache = null; 取默认useCache = isSelect= true; 4545 boolean useCache = context.getBooleanAttribute("useCache", isSelect); 4646 // 没有 resultOrdered 属性,即 resultOrdered = null; 取默认resultOrdered= false; 4747 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); 4848 4949 // Include Fragments before parsing 5050 // 处理include 标签,我们的select标签中没有用到include标签 5151 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); 5252 includeParser.applyIncludes(context.getNode()); 5353 5454 // Parse selectKey after includes and remove them. 5555 // 处理selectKey 标签,我们的select标签中没有用到selectKey标签 5656 processSelectKeyNodes(id, parameterTypeClass, langDriver); 5757 5858 // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 5959 // 在解析完<selectKey> 和 <include> 之后开始解析 SQL语句 6060 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 6161 // 没有 resultSets 属性,即 resultSets = null; 6262 String resultSets = context.getStringAttribute("resultSets"); 6363 // 没有 keyProperty 属性,即 keyProperty = null; 6464 String keyProperty = context.getStringAttribute("keyProperty"); 6565 // 没有 keyColumn 属性,即 keyColumn = null; 6666 String keyColumn = context.getStringAttribute("keyColumn"); 6767 6868 //接下来处理的是主键生成器 6969 KeyGenerator keyGenerator; 7070 String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; 7171 keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); 7272 if (configuration.hasKeyGenerator(keyStatementId)) { 7373 keyGenerator = configuration.getKeyGenerator(keyStatementId); 7474 } else { 7575 // 应为我们的是select类型的语句,所以SqlCommandType.INSERT.equals(sqlCommandType) == false。所以keyGenerator = new NoKeyGenerator() 7676 keyGenerator = context.getBooleanAttribute("useGeneratedKeys", 7777 configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) 7878 ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 7979 } 8080 8181 // 这一步 就是让mapper解析器助理创建MappedStatement实例对象,并将新建的实例对象添加到 configuration的 mappedStatements中,表示这个标签声明被解析过了。 8282 //根据上方的解析过程,我们可以清晰的知道各个参数的值: 8383 //id = "findUserById",sqlSource = RawSqlSource 实例对象,statementType = PREPARED,sqlCommandType = SELECT 8484 //fetchSize,timeout,parameterMap,parameterTypeClass,resultMap= null 8585 //resultTypeClass = User类对象 8686 //resultSetTypeEnum = null 8787 //flushCache=false,useCache=true,resultOrdered=false,keyGenerator = new NoKeyGenerator() 8888 //keyProperty, keyColumn, databaseId,=null 8989 //langDriver=XMLLanguageDriver实例对象 9090 //resultSets=null。 9191 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 9292 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, 9393 resultSetTypeEnum, flushCache, useCache, resultOrdered, 9494 keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 9595 }

  通过代码中的注释,相信大家都能看的明白,这里着重解释一下,第7行,第60行,第91行:

  第7行:if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;中的databaseIdMatchesCurrent方法源码:  

1 1 //比较需要使用的databaseId 和 标签声明中的databaseId 是否相同,这时id="findUserById",同时databaseId和requiredDatabaseId 都是null 2 2 private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) { 3 3 if (requiredDatabaseId != null) { 4 4 if (!requiredDatabaseId.equals(databaseId)) { 5 5 //如果不同就返回false,停止解析 6 6 return false; 7 7 } 8 8 } else { 9 9 if (databaseId != null) { 1010 // 这个时候requiredDatabaseId == null, 在这个requiredDatabaseId 等于 null的情况下databaseId 却不等于null,说明需要使用的databaseId 和 标签声明中的databaseId 是不相同的,就放回false,停止解析 1111 return false; 1212 } 1313 // skip this statement if there is a previous one with a not null databaseId 如果存在已经解析过的并且databaseId不为null的标签声明,则返回false跳过解析 1414 // 获取id,这个id就是标签解析器的id,从接下来的分析中可以明确看出:这个id也是标签声明类(MappedStatement)实例化对象的id。 1515 id = builderAssistant.applyCurrentNamespace(id, false); 1616 1717 if (this.configuration.hasStatement(id, false)) { 1818 MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2 1919 if (previous.getDatabaseId() != null) { 2020 return false; 2121 } 2222 } 2323 } 2424 return true; 2525 }

  而源码中的applyCurrentNamespace源码是:

1public String applyCurrentNamespace(String base, boolean isReference) { 2 if (base == null) return null; 3 if (isReference) { 4 // is it qualified with any namespace yet? 5 if (base.contains(".")) return base; 6 } else { 7 // is it qualified with this namespace yet? 8 if (base.startsWith(currentNamespace + ".")) return base; 9 if (base.contains(".")) throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); 10 } 11 // 返回com.zcz.learnmybatis.entity.UserDao.findUserById 12 // currentNamespace 在前面已经设置过了,就是mapper文件中的namespace 13 return currentNamespace + "." + base; 14 }

  第60行是用来处理SQL语句的,也就是用来处理:

select * from user where id = #{id}

  这一部分的,处理了什么呢?简单来说就是根据”${“是否存在来判断SQL语句是否是动态SQL语句,并且把 select * from user where id = #{id} 转换为select * from user where id = ?。同时把#{id}中的id保存起来。具体细节源码不展开了,需要的话,再写一篇文章详细解析吧。

  第91行,就是把一个select ,update,delete 或者insert标签声明转换为MappedStatement对象实例,更明白点说,就是把

1<select id="findUserById" resultType="com.zcz.learnmybatis.entity.User" > 2 select * from user where id = #{id} 3 </select>

  这一部分转换为MappedStatement对象并保持到configuration中,实现保存的代码源码如下:

  但是要注意下面代码里的id = "findUserById",但是经过applyCurrentNameSpace()方法后,id= "com.zcz.learnmybatis.dao.UserDao.findUserById",即在原来的id前,添加了UserDao类全包名+类名+“.”;

1public MappedStatement addMappedStatement( 2 String id, 3 SqlSource sqlSource, 4 StatementType statementType, 5 SqlCommandType sqlCommandType, 6 Integer fetchSize, 7 Integer timeout, 8 String parameterMap, 9 Class<?> parameterType, 10 String resultMap, 11 Class<?> resultType, 12 ResultSetType resultSetType, 13 boolean flushCache, 14 boolean useCache, 15 boolean resultOrdered, 16 KeyGenerator keyGenerator, 17 String keyProperty, 18 String keyColumn, 19 String databaseId, 20 LanguageDriver lang, 21 String resultSets) { 22 23 if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved"); 24 25 id = applyCurrentNamespace(id, false); 26 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 27  //初始化MappenStatement.Builder 28 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType); 29 statementBuilder.resource(resource); 30 statementBuilder.fetchSize(fetchSize); 31 statementBuilder.statementType(statementType); 32 statementBuilder.keyGenerator(keyGenerator); 33 statementBuilder.keyProperty(keyProperty); 34 statementBuilder.keyColumn(keyColumn); 35 statementBuilder.databaseId(databaseId); 36 statementBuilder.lang(lang); 37 statementBuilder.resultOrdered(resultOrdered); 38 statementBuilder.resulSets(resultSets); 39 setStatementTimeout(timeout, statementBuilder); 40 41 setStatementParameterMap(parameterMap, parameterType, statementBuilder); 42 setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder); 43 setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder); 44  // 构造MappedStatement 45 MappedStatement statement = statementBuilder.build();  // 保存 46 configuration.addMappedStatement(statement); 47 return statement; 48 }

  值得一提的是在MappedStatement statement = statementBuilder.build();我们先看看源码:

11 public MappedStatement build() { 22 assert mappedStatement.configuration != null; 33 assert mappedStatement.id != null; 44 assert mappedStatement.sqlSource != null; 55 assert mappedStatement.lang != null; 66 mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps); 77 return mappedStatement; 88 }

  在第六行有一个Collections.unmodifiableList方法,这个方法是一个很有趣的方法,想要了解一下的话,请查阅:Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet作用及源码解析

到这里 select的标签声明的解析就结束了,同时Mapper文件的解析也结束了。

总结一下,mappers节点解析完成之后,所有的mybatis有关的配置文件都已经解析完成了,包括:configuration.xml文件,dbConfig.properties文件,userDa0-mapping.xml文件。并且都保存到Configuration 的实例化对象中了。


 原创不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9682487.html

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析 - HelloWorld