Mybatis源码之

您所在的位置:网站首页 xml框架代码 Mybatis源码之

Mybatis源码之

2024-07-16 21:34| 来源: 网络整理| 查看: 265

Mybatis源码之–XML完整解析流程 如何编译源码

仓库地址

1、idea中打开项目

2、等待解析依赖

备注:解析依赖的过程中有可能某些jar包的版本找不到,可自行在https://mvnrepository.com/ 找寻替代版本

XML解析

mybatis3支持注解方式声明SQL语句,但是最终经过mybatis内部解析,作用和使用XML定义是一样的。

只要你理解了XML的整个解析流程,其实就已经吃透了mybatis。

在阅读源码的过程中,mybatis的中文官网必须时刻去翻阅。特别是如下几章内容:

入门XML配置XML映射器动态SQL

XML配置讲解的是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_sort

mybatis解析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

XMLConfigBuilder.parse

因为我们在mybatis-config.xml中只定义了plugins--environments--mappers这几个节点,所以我们就只看这几个节点的解析。(其中settings标签设置了很多默认值,比如下划线转驼峰,可以去看看,这里不讲解)

解析plugins

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文件中的三个标签

resultMapElements1

先来看如何解析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

同样继承了BaseBuilder构造方法中调用了initNodeHandlerMap()initNodeHandlerMap内部有些TrimHandler之类的东西,这些是内部类

我们来看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()2

那我们继续回到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