Mybatis源码之 |
您所在的位置:网站首页 › xml框架代码 › Mybatis源码之 |
Mybatis源码之–XML完整解析流程
如何编译源码
仓库地址 1、idea中打开项目 2、等待解析依赖 备注:解析依赖的过程中有可能某些jar包的版本找不到,可自行在https://mvnrepository.com/ 找寻替代版本 XML解析mybatis3支持注解方式声明SQL语句,但是最终经过mybatis内部解析,作用和使用XML定义是一样的。 只要你理解了XML的整个解析流程,其实就已经吃透了mybatis。 在阅读源码的过程中,mybatis的中文官网必须时刻去翻阅。特别是如下几章内容: 入门XML配置XML映射器动态SQLXML配置讲解的是mybatis-config.xml (配置数据库地址、插件、类型别名) XML映射器讲解的是我们自定义业务SQL语句的***Mapper.xml 如何开始阅读源码呢?当然不可能一个一个包一个一个类去翻阅,这样是没有意义的。我们必须找到一个入口,也就是写一个最简单的例子跑完整个流程。然后使用idea跟踪这个流程的每一步。 可以参照入门搭建一个最小的示例。 入口示例我参照入门构造了一个示例,目录结构如下: 在mybatis-config.xml中,配置了数据库地址、以及业务mapper xml文件 (projectInfo.xml) Mybatis-config.xml projectInfo.xml p.id, p.project_name,p.url,p.description,p.tags, p.username,p.`password`,p.thumbnail_url,p.second_category_id, p.project_sort,p.maintainer,p.`status`,p.back_service,p.create_time select from project_info p LEFT JOIN second_category sc on sc.id = p.second_category_id INNER JOIN primary_category pc on pc.id = sc.p_id where p.logic_delete = 0 and pc.id = #{fId} and sc.id = #{sId} ORDER BY pc.sort,sc.sort,p.project_sort p.project_name,p.url,p.description,p.tags, p.username,p.`password`,p.thumbnail_url, pc.name as category,pc.sort as category_sort, sc.name as second_category,sc.sort as second_category_sort, p.project_sort,p.maintainer,p.create_time select from project_info p LEFT JOIN second_category sc on sc.id = p.second_category_id INNER JOIN primary_category pc on pc.id = sc.p_id WHERE p.back_service=0 ORDER BY pc.sort,sc.sort,p.project_sortmybatis解析xml文件的入口是 mybatis-config.xml。 依次解析properties->settings->typeAliases->plugins->objectFactory->objectWrapperFactory->reflectorFactory->environments->databaseIdProvider->typeHandlers->mappers 最后的mapperElement(root.evalNode("mappers")); 解析的就是我们的业务mapper: 我们先来看mybatis-config.xml的解析 mybatis-config.xml String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); assert inputStream!=null; SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);我们的入口程序中指定了mybatis-config.xml的路径,传递给了SqlSessionFactoryBuilder的build方法。 XMLConfigBuilder1我们进入看看: 注意XMLConfigBuilder对象,它负责解析mybatis-config.xml中除了mappers标签以外的元素。 我们进入XMLConfigBuilder的构造方法中看看,同时也要注意一下XMLConfigBuilder继承了一个BaseBuilder类: public class XMLConfigBuilder extends BaseBuilder { public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { //XMLMapperEntityResolver实现了org.xml.EntityResolver接口 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } }注意:构造方法中新建了一个new XPathParser对象,我们不深入这个类,它的作用是可以根据标签方便的获取xml内容。例如parser.evalNode("/configuration")就能获取到configuration中的内容。 刚刚看了XMLConfigBuilder的构造方法,我们继续: 仍然还是调用构造方法,注意在构造方法中调用了父类的构造方法,传进了一个新的Configuration对象。 特别说明,Configuration类在mybatis中可是非常非常重要,几乎无处不在。它保存着mybatis-config.xml中的配置信息以及我们业务mapper中的类信息。真的超级重要。 Configuration1既然这么重要,我们看看new Configuration的时候做了些什么操作吧。 /** * 初始化了内置别名、LanguageDriver */ public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); ... 省略了其他 }因为configuration的内容真的太多了,它有很多成员变量,这些成员变量都是new出来的,所以新建一个configuration对象是个耗时的工作。 TypeAliasRegistry我们先看看构造器中用到的这个typeAliasRegistry对象吧,它在成员变量中的定义如下: protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();我们再来看看它: public class TypeAliasRegistry { /** * 例子: */ private final Map typeAliases = new HashMap(); public TypeAliasRegistry() { registerAlias("string", String.class); registerAlias("byte", Byte.class); registerAlias("long", Long.class); registerAlias("short", Short.class); registerAlias("int", Integer.class); ... 省略 } }TypeAliasRegistry从它的名称就能知道它的作用:别名注册器,啥意思呢? 比如我在mybaits-config中定义了一个别名 ,最终就会调用TypeAliasRegistry的registerAlias方法 registerAlias(‘Author’,Author.class) 如何使用呢?遇到的时候再告诉你吧。 注意一下:在TypeAliasRegistry构造器中我们初始化了基本类型的别名 Configuration2现在我们回到Configuration1一节的最后,继续。 在Configuration的构造器中我们初始化了 typeAliasRegistry.registerAlias(“POOLED”, PooledDataSourceFactory.class); typeAliasRegistry.registerAlias(“JDBC”, JdbcTransactionFactory.class); 再来看mybatis-config.xml中的环境变量: 这里提一下,后面我们在解析environments标签的时候会用到我们的别名JDBC、POOLED 现在我们回到XMLConfigBuilder1中的构造器: private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { //注意:在此new Configuration实例 super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }构造器后续的几条语句就没什么好说的了。 我们又回到了XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 紧接着调用XMLConfigBuilder的parse方法,正式开始解析mybatis-config.xml 因为我们在mybatis-config.xml中只定义了plugins--environments--mappers这几个节点,所以我们就只看这几个节点的解析。(其中settings标签设置了很多默认值,比如下划线转驼峰,可以去看看,这里不讲解) plugin在mybatis中可是非常出名的,像我们最经常使用的分页插件,入口就在这。 因为这一节只是说xml的解析,所以不能说得太详细。 先贴一下xml中的配置: private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //获取plugin节点的interceptor属性 String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); //反射new Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); //添加到过滤器链interceptorChain中 configuration.addInterceptor(interceptorInstance); } } } 获取plugin节点的interceptor属性通过反射生成我们自定义的拦截器(比如分页插件)最后添加到过滤器链中过滤器链设计得非常巧妙,很精彩。以后再给你们分享。 解析environments环境变量主要是设置数据库连接地址、用户名、密码等 再贴xml配置: 配合着代码看: 我们来看dataSourceElement这个方法,正好把上面提到的TypeAliasRegistry用上: private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { //这里的type=POOLED String type = context.getStringAttribute("type"); //这里存储用户名、密码、数据库地址 Properties props = context.getChildrenAsProperties(); //如果type=POOLED => PooledDataSourceFactory //别名注册地:typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }请看代码里的注释。 其中resolveClass(type)在这里是resolveClass("POOLED") ,跟踪一下代码就很容易发现,resolveClass(type)最终调用的是typeAliasRegistry.resolveAlias(alias);。 通过resolveClass(type)拿到Class类型后,就能通过反射生成PooledDataSourceFactory实例了。 接下来就是最最复杂的mappers解析了,我们的select、update、delete都定义在此。 projectInfoMapper.xml //映射器--这里面非常复杂--转交给XMLMapperBuilder实现 mapperElement(root.evalNode("mappers"));配合着xml,来看看mapperElement方法吧: 由于我的演示示例中没有定义package标签,代码不会进入。 此次我会进入红色箭头指示的地方,因为我只定义了resource属性 来看硬核的地方:XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); XMLMapperBuilder进入构造方法看看,同时注意它也继承了父类BaseBuilder(之前已经提过了) 同样也new了个XPathParser解析器,跳过。看上图我红框标记的地方: this.builderAssistant = new MapperBuilderAssistant(configuration, resource);这也是个非常重要的类,看名字就知道它是个助手类,负责组装SQL用的。很重要很重要 参数: configuration: 之前我们在XMLConfigBuilder构造器中new出来的,还有印象吗resource: mapper文件路径,这里是com/twx/mybatis/mapper/xml/projectInfo.xml我们来看看它吧。 MapperBuilderAssistant public class MapperBuilderAssistant extends BaseBuilder { /** * xml文件中的命名空间 * 例如: */ private String currentNamespace; /** * mapper xml 文件的全路径 */ private final String resource; private Cache currentCache; private boolean unresolvedCacheRef; // issue #676 public MapperBuilderAssistant(Configuration configuration, String resource) { super(configuration); ErrorContext.instance().resource(resource); this.resource = resource; } }它也继承了BaseBuilder,所以它也有一个Configuration对象的引用(还记得我说过的,Configuration对象很重要,后面非常多类都要用到它。 注意它的几个成员变量: currentNamespace:业务xml文件中的命名空间resource:业务xml 文件的全路径,设置在mybatis-config.xml mapper标签的resource属性中currentCache:一级缓存,暂时不讲解它还有很多重要的方法,这里先列举几个常用的: applyCurrentNamespace-返回mapper接口方法的完全限定名称useNewCache–创建缓存对象addResultMap–生成ResultMap,add到configure对象的resultMaps中addMappedStatement–生成MappedStatement对象,set到config对象的mappedStatements中getStatementResultMaps–根据resultMap参数从config对象中获取ResultMap对象buildResultMapping–构建ResultMapping对象resolveResultJavaType-返回java bean属性对应的Java类型 XMLMapperBuilder解析 parse从XMLMapperBuilder构造器出来,紧接着就调用parse方法 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse();String namespace = context.getStringAttribute("namespace");拿到的是 我们再来看看更具体的configurationElement(XNode context)方法 接下来会着重讲解三个方法: resultMapElementssqlElementbuildStatementFromContext这三个方法对应下图xml文件中的三个标签 先来看如何解析resultMap标签的。 真正解析的方法很长。我们分段看。 省略 //type = com.twx.mybatis.entity.ProjectInfo String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));由于只传了一个type属性,所以String type=com.twx.mybatis.entity.ProjectInfo Class typeClass = resolveClass(type); if (typeClass == null) { typeClass = inheritEnclosingType(resultMapNode, enclosingType); }typeClass理所应当是com.twx.mybatis.entity.ProjectInfo 再来看上面这段代码,先解释一下ResultMapping. ResultMapping一个ResultMapping对应entity中的一个属性,亦即 上图xml中的每一行最后都被解析成一个一个的ResultMapping 对象。 理解了这点,我们回到java代码中的for循环。for循环就是解析这些id,result。由于我们没有定义constructor,discriminator,就跳过这几行代码。直接看id 和 result标签的解析。 我们来看这行代码: //解析 标签 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));来看buildResultMappingFromContext方法,buildResultMappingFromContext 用于生成一个ResultMapping对象。 上图这段java代码对照着下图看就很清晰了,不做过多解析。(注意,我们没对resultMap的每个属性声明typeHandler) 代码最后一行我特别标注,最终ResultMapping对象的生成是转交给builderAssistant助手实现的(前面我们提到过,还有印象吗,当时只是提到了方法名称,并没有具体去分析,现在我们去看看)。 我们来看builderAssistant的buildResultMapping方法: public ResultMapping buildResultMapping(Class resultType, String property, String column, Class javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class typeHandler, List flags, String resultSet, String foreignColumn, boolean lazy) { //根据属性property名称例如username获取对应的java类型String Class javaTypeClass = resolveResultJavaType(resultType, property, javaType); //typeHandler在我们的示例中是null,返回null TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler); //是否有组合对象 List composites; //假如 // 有配置select属性,那么这里的nestedSelect!=null //有配置foreignColumn属性,这里的foreignColumn!=null //在我们的示例中,显然两者都为空,所以composites=null if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) { composites = Collections.emptyList(); } else { 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(); }这个方法的作用是为了: 确定java bean属性对应的Class类型确定属性交给什么TypeHandler处理以及属性是否有嵌套查询最后的return语句其实就是new ResultMapping(),只不过用的是建造者模式而已。 resultMapElements2我们回到resultMapElements1一节的最后。 //每一个ResultMapping对应entity中的一个属性 List resultMappings = new ArrayList(additionalResultMappings); for (XNode resultChild : resultChildren) { .... 省略 //解析 标签 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); }经过我们上面对ResultMapping的分析,执行完上面这个for循环后,我们继续。 再次贴上xml: ... 省略了看代码注释就好。 最后调用ResultMapResolver.resolve()生成ResultMap对象。 ResultMapResolver.resolve() 最终还是调用我一再强调的助手类MapperBuilderAssistant的addResultMap public ResultMap resolve() { return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping); }我们来看MapperBuilderAssistant.addResultMap() /** * 生成ResultMap,add到configure对象的resultMaps中 * @param id resultMapId * @param type mapper返回类型 * @param extend * @param discriminator * @param resultMappings * @param autoMapping * @return */ public ResultMap addResultMap(String id, Class type, String extend, Discriminator discriminator, List resultMappings, Boolean autoMapping) { //resultMap的完全限定名称 //例如:com.twx.mybatis.mapper.ProjectInfoMapper.projectInfoMap id = applyCurrentNamespace(id, false); //TODO: 构造示例研究 extend = applyCurrentNamespace(extend, true); if (extend != null) { ... 省略 } //build方法内部做了很多操作,主要是提取ResultMapping中的属性 //ResultMapping存储了很多@Result的属性 ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); configuration.addResultMap(resultMap); return resultMap; }不需要关心太多,直接看最后new ResultMap.Builder().build() 这个方法不详细讲了,大概的内容就是遍历之前生成的List resultMappings 给自己的成员变量赋值。下面列举几个关键的成员变量。 /** * resultMap的id */ private String id; /** * 返回类型,例如com.twx.mybatis.entity.ProjectInfo */ private Class type; private List resultMappings; /** * id标签对应的ResultMapping */ private List idResultMappings; /** * 构造器对应的constructorResultMappings */ private List constructorResultMappings; /** * id+result标签对应的ResultMapping */ private List propertyResultMappings;至此,终于完成了 标签的解析了。 sqlElement这是我们的第二大标签。我们先看看xml中的定义与使用。你一定很熟悉。 p.id, p.project_name,p.url,p.description,p.tags, p.username,p.`password`,p.thumbnail_url,p.second_category_id, p.project_sort,p.maintainer,p.`status`,p.back_service,p.create_time select from project_info p代码实现很简单,只是把标签的id和内容存储到sqlFragments。 sqlFragments是Configuration中的sqlFragments引用 sqlFragments的定义: /** * 存储标签语句,key指代id */ protected final Map sqlFragments = new StrictMap("XML fragments parsed from previous mappers");注意 sqlFragments.put(id, context); 中的context是一个Xnode节点,后续会拿出来解析的。这里不做处理 buildStatementFromContext最重磅的一个方法了。非常复杂! 做好准备。这是xml解析最后一步了。 那来看buildStatementFromContext: 由于解析select delete insert 等语句比较复杂,转交给单独的类XMLStatementBuilder解析 parseStatementNode()同样继承了BaseBuilder,构造方法没什么好讲的了。之前都说过了。 注意一下context代表的是xml node节点内容。例如: select from project_info p LEFT JOIN second_category sc on sc.id = p.second_category_id INNER JOIN primary_category pc on pc.id = sc.p_id where p.logic_delete = 0 and pc.id = #{fId} and sc.id = #{sId} ORDER BY pc.sort,sc.sort,p.project_sort我们直接来看parseStatementNode(),这方法也是超级的长。慢慢分析。 String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId");id在此是getProjectInfo,databaseId=null String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));nodeName是select insert update delete其中一种,在此是select sqlCommandType=SqlCommandType.SELECT boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); //将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。 boolean useCache = context.getBooleanAttribute("useCache", isSelect); //这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 //这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);看代码中的注释。针对select查询语句,useCache默认是true //解析include语句 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode());上面两行用于解析标签,例如示例中的: select from project_info p上面替换标签的方法使用到了递归,最核心的是第一个红色框里的代码,找到include对应的标签,然后替换内容。因为递归的原因,不太好通过文字讲解。(可以自己调试代码感受) 我们继续回到parseStatementNode() String parameterType = context.getStringAttribute("parameterType"); Class parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang"); //如果未指定,默认是XMLLanguageDriver //初始化new Configuration时 //languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); LanguageDriver langDriver = getLanguageDriver(lang);parameterType我们没传,所以parameterType=null LanguageDriver langDriver 默认是XMLLanguageDriver,看注释 // // select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 // processSelectKeyNodes(id, parameterTypeClass, langDriver);用得不多,跳过 // Parse the SQL (pre: and 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 { //(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键 // (比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; }过程就不讲了。不重要。 keyStatementId="com.twx.mybatis.mapper.ProjectInfoMapper.getProjectInfo!selectKey" KeyGenerator keyGenerator = NoKeyGenerator //如果是动态的,返回DynamicSqlSource //如果是静态的,返回RawSqlSource //SqlSource接口有一个方法BoundSql getBoundSql(Object parameterObject); //能拿到BoundSql SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);这里面也是非常复杂的。如果你的select语句中有if | foreach | trim| when (...) 或者 ${} 那就是动态的,否则是静态的。 如果是动态的,返回DynamicSqlSource 如果是静态的,返回RawSqlSource 我们来看看XMLLanguageDriver的createSqlSource @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }内部又借助XMLScriptBuilder的parseScriptNode()实现。 那我们转战战场。 XMLScriptBuilder我们来看parseScriptNode()方法: public SqlSource parseScriptNode() { //解析标签中的动态变量 //MixedSqlNode 维护者一个列表List contents, // contents可能包含 StaticTextSqlNode和IfSqlNode或者WhereSqlNode或者其他 MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }parseDynamicTags(context)的作用是解析标签中的动态变量。 返回的MixedSqlNode维护着一个列表List contents,contents可能包含 StaticTextSqlNode和IfSqlNode或者WhereSqlNode或者其他。 我们粗略地看看parseDynamicTags(context)就行,内部的textSqlNode.isDynamic()非常复杂的。主要是数据处理。 解析完parseDynamicTags,必然是动态的(我们的示例),所以返回DynamicSqlSource 那我们继续回到parseStatementNode()的这个地方: SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);继续往下看。 如果未指定,默认是StatementType.PREPARED 这非常重要,在真正取数据的时候,会根据这个枚举类型new不同的statementHandler对象 详情参照RoutingStatementHandler构造器 switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); }在此我们只要知道StatementType statementType=StatementType.PREPARED,用到的时候再细聊。 接下来的这些代码没什么好说的,都是从xml配置取值。 parseStatementNode()方法的最后就是生产MappedStatement对象。 一个MappedStatement对象就代表着你定义的一个 或者或者等等 //非常重要: 生成MappedStatement对象,set到config对象的mappedStatements中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);我们结合idea看一下具体传进来的是些什么参数吧: 结合着上图的参数,我们来看看builderAssistant.addMappedStatement内部代码 无它–建造者模式生成MappedStatement对象 注意,最后把生成的statement添加到了Configuration对象中去了,即: /** * 很重要:用于存储select insert 等语句 */ protected final Map mappedStatements = new StrictMap("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource());添加到了Configuration对象的mappedStatements容器中。key是语句的id,即getProjectInfo 生成了MappedStatement后,xml解析工作基本已经完结了,剩下的都是收尾工作。 顺着堆栈一步步弹出。我们回到了下图这里,即XMLMapperBuilder.parse(): 所有xml文件都已经解析完毕了。 然后标记一下我们已经解析过的xml文件,避免重复加载,毕竟很耗时。 configuration.addLoadedResource(resource); 然后就是这行代码bindMapperForNamespace(); 它的作用是将我们声明的***Mapper.java接口添加到Configuration对象的mapperRegistry成员变量中。 我们示例声明的接口是ProjectInfoMapper: public interface ProjectInfoMapper { List getProjectFront(); List getProjectInfo(@Param("fId") Integer fId, @Param("sId") Integer sID); }调用bindMapperForNamespace 后,相当于 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); mapperRegistry.addMapper(ProjectInfoMapper.class); parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();这几个方法就不说了。 至此,xml解析流程全部完毕。 接下来,我们看看mybatis是如何查询的。 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |