> 文章列表 > mybatis3源码篇(1)——构建流程

mybatis3源码篇(1)——构建流程

mybatis3源码篇(1)——构建流程

mybatis 版本:v3.3.0

文章目录

  • 构建流程
    • SqlSessionFactoryBuilder
      • XMLConfigBuilder
        • typeAliasesElement
        • typeHandlerElement
        • mapperElement
          • MapperRegistry
      • MappedStatement
        • MapperAnnotationBuilder
        • XMLMapperBuilder
        • MapperBuilderAssistant
    • SqlSessionFactory
    • SqlSession

mybatis的源码可以从构建流程和执行流程两个角度来看。
构建流程就是解析xml配置文件,将所配置的信息全部解析到一个Configuration对象中。
执行流程就是一个sql语句的执行,其中包括SQL语句的获取,类型转化等操作,参数处理,结果集映射等操作。
mybatis3源码篇(1)——构建流程

构建流程

先看我们是如何使用mybatis的
mybatis3源码篇(1)——构建流程
可以发现,使用前要先构建一个SqlSeesion。现在针对SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession逐个分析。

SqlSessionFactoryBuilder

这里用到了构建者设计模式,build方法传入配置文件资源并通过XMLConfigBuilder解析配置文件得到一个Configuration对象,再把这个Configuration对象传递给SqlSessionFactory构造方法得到一个SqlSeesionFactory。
mybatis3源码篇(1)——构建流程

XMLConfigBuilder

而XMLConfigBuilder解析过程如下
mybatis3源码篇(1)——构建流程
解析XML的底层实现是w3c的dom技术,mybatis只是对其做了一些封装。
这里着重看一下typeAliasesElement、typeHandlerElement、mapperElement

typeAliasesElement

注册我们的自定义别名
mybatis3源码篇(1)——构建流程
同时mybatis会默认对一些数据类型注册别名,在TypeAliasRegistry的构造方法中就注册。
mybatis3源码篇(1)——构建流程
这也是为什么我们基本数据类型可以直接用string,int,long…而不用写全名的原因。

typeHandlerElement

注册我们自定义的类型处理器
mybatis3源码篇(1)——构建流程
同样,mybatis也会默认对一些数据类型注册类型处理器。
mybatis3源码篇(1)——构建流程

mapperElement

解析Mappers标签
mybatis3源码篇(1)——构建流程
if语句的4个分支分别对应包名,类路径,绝对url路径,类名配置的mapper。

MapperRegistry

针对引用包名和类(引用class)方式,mybatis调用configuration.addMapper(s),跟踪器代码发现本质上调用的是MapperRegistry.addMapper。而这个注册器通过MapperAnnotationBuilder解析Mapper。
mybatis3源码篇(1)——构建流程
而针对引用类路径,绝对url路径(引用资源)方式,mybatis直接通过XMLMapperBuilder去解析。

MapperAnnotationBuilder和XMLMapperBuilder看名字都是只解析注解或XML方式的配置,但实际上两者在解析的过程中是会相互调用的。所以无论你用何种方式,最终都会解析注解和xml配置。

MappedStatement

前面有提到,最终解析出来的配置都会设置到Configuration。所以MapperAnnotationBuilder、XMLMapperBuilder解析出来了啥呢?
其实就是一个个的MappedStatement,其属性如下。
mybatis3源码篇(1)——构建流程
每个MappedStatement对象都对应一个映射(注解或xml配置)中的select、insert、update或delete元素,它包含了执行SQL语句所需的所有信息,如SQL语句(sqlSource)、参数映射关系(parameterMap)、结果映射关系(resultMaps)等。

回到MapperAnnotationBuilder、XMLMapperBuilder,来看看这两个是怎么添加MappedStatement到Configuragtion的。

MapperAnnotationBuilder

mybatis3源码篇(1)——构建流程
parseStatement方法如下
先读取注解配置的所有信息,然后调用MapperBuilderAssistant.addMappedStatement添加MappedStatement到Configuration。

void parseStatement(Method method) {Class<?> parameterTypeClass = getParameterType(method);LanguageDriver languageDriver = getLanguageDriver(method);SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {Options options = method.getAnnotation(Options.class);final String mappedStatementId = type.getName() + "." + method.getName();Integer fetchSize = null;Integer timeout = null;StatementType statementType = StatementType.PREPARED;ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;SqlCommandType sqlCommandType = getSqlCommandType(method);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = !isSelect;boolean useCache = isSelect;KeyGenerator keyGenerator;String keyProperty = "id";String keyColumn = null;if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {// first check for SelectKey annotation - that overrides everything elseSelectKey selectKey = method.getAnnotation(SelectKey.class);if (selectKey != null) {keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);keyProperty = selectKey.keyProperty();} else if (options == null) {keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();keyProperty = options.keyProperty();keyColumn = options.keyColumn();}} else {keyGenerator = new NoKeyGenerator();}if (options != null) {flushCache = options.flushCache();useCache = options.useCache();fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348timeout = options.timeout() > -1 ? options.timeout() : null;statementType = options.statementType();resultSetType = options.resultSetType();}String resultMapId = null;ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);if (resultMapAnnotation != null) {String[] resultMaps = resultMapAnnotation.value();StringBuilder sb = new StringBuilder();for (String resultMap : resultMaps) {if (sb.length() > 0) {sb.append(",");}sb.append(resultMap);}resultMapId = sb.toString();} else if (isSelect) {resultMapId = parseResultMap(method);}assistant.addMappedStatement(mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout,// ParameterMapIDnull,parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,useCache,// TODO issue #577false,keyGenerator,keyProperty,keyColumn,// DatabaseIDnull,languageDriver,// ResultSetsnull);}}

XMLMapperBuilder

mybatis3源码篇(1)——构建流程
来到configurationElement方法
mybatis3源码篇(1)——构建流程
来到buildStatementFromContext方法
mybatis3源码篇(1)——构建流程

XMLStatementBuilder
因为XML配置比较复杂,所以mybatis再次用到构建者的设计模式,通过XMLStatementBuilder去构建MappedStatement。
parseStatementNode方法如下

public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");//如果databaseId不匹配,退出if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}//暗示驱动程序每次批量返回的结果行数Integer fetchSize = context.getIntAttribute("fetchSize");//超时时间Integer timeout = context.getIntAttribute("timeout");//引用外部 parameterMap,已废弃String parameterMap = context.getStringAttribute("parameterMap");//参数类型String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);//引用外部的 resultMap(高级功能)String resultMap = context.getStringAttribute("resultMap");//结果类型String resultType = context.getStringAttribute("resultType");//脚本语言,mybatis3.2的新功能String lang = context.getStringAttribute("lang");//得到语言驱动LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);//结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种String resultSetType = context.getStringAttribute("resultSetType");//语句类型, STATEMENT|PREPARED|CALLABLE 的一种StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);//获取命令类型(select|insert|update|delete)String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否要缓存select结果boolean useCache = context.getBooleanAttribute("useCache", isSelect);//仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。//这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。 boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsing//解析之前先解析<include>SQL片段XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.//解析之前先解析<selectKey>processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)//解析成SqlSource,一般是DynamicSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty = context.getStringAttribute("keyProperty");//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyColumn = context.getStringAttribute("keyColumn");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))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}//又去调助手类builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

最终跟注解的方式一样,调用MapperBuilderAssistant.addMappedStatement添加MappedStatement到Configuration。

MapperBuilderAssistant

 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加上namespace前缀id = applyCurrentNamespace(id, false);//是否是select语句boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//又是建造者模式MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);statementBuilder.resource(resource);statementBuilder.fetchSize(fetchSize);statementBuilder.statementType(statementType);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);statementBuilder.keyColumn(keyColumn);statementBuilder.databaseId(databaseId);statementBuilder.lang(lang);statementBuilder.resultOrdered(resultOrdered);statementBuilder.resulSets(resultSets);setStatementTimeout(timeout, statementBuilder);//1.参数映射setStatementParameterMap(parameterMap, parameterType, statementBuilder);//2.结果映射setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();//建造好调用configuration.addMappedStatementconfiguration.addMappedStatement(statement);return statement;}

SqlSessionFactory

SqlSessionFactory是一个接口,有两个实现,常用的是DefaultSqlSessionFactory。DefaultSqlSessionFactory中维护了一个Configuration对象,后续都会根据这个Configuration去创建SqlSession。
mybatis3源码篇(1)——构建流程
mybatis3源码篇(1)——构建流程
mybatis3源码篇(1)——构建流程

SqlSession

SqlSession同样也是一个接口,用到了门面设计模式,所有sql操作都通过这个门面去操作。而且也有两个实现,常用的是DefaultSqlSession,DefaultSqlSession中维护了Executor执行器,用来执行真正的sql逻辑。
mybatis3源码篇(1)——构建流程
mybatis3源码篇(1)——构建流程
mybatis3源码篇(1)——构建流程