mybatis 版本:v3.3.0
文章目录
- 构建流程
- SqlSessionFactoryBuilder
- XMLConfigBuilder
- typeAliasesElement
- typeHandlerElement
- mapperElement
- MapperRegistry
- MappedStatement
- MapperAnnotationBuilder
- XMLMapperBuilder
- MapperBuilderAssistant
- SqlSessionFactory
- SqlSession
mybatis的源码可以从构建流程和执行流程两个角度来看。
构建流程就是解析xml配置文件,将所配置的信息全部解析到一个Configuration对象中。
执行流程就是一个sql语句的执行,其中包括SQL语句的获取,类型转化等操作,参数处理,结果集映射等操作。
构建流程
先看我们是如何使用mybatis的
可以发现,使用前要先构建一个SqlSeesion。现在针对SqlSessionFactoryBuilder,SqlSessionFactory,SqlSession逐个分析。
SqlSessionFactoryBuilder
这里用到了构建者设计模式,build方法传入配置文件资源并通过XMLConfigBuilder解析配置文件得到一个Configuration对象,再把这个Configuration对象传递给SqlSessionFactory构造方法得到一个SqlSeesionFactory。
XMLConfigBuilder
而XMLConfigBuilder解析过程如下
解析XML的底层实现是w3c的dom技术,mybatis只是对其做了一些封装。
这里着重看一下typeAliasesElement、typeHandlerElement、mapperElement
typeAliasesElement
注册我们的自定义别名
同时mybatis会默认对一些数据类型注册别名,在TypeAliasRegistry的构造方法中就注册。
这也是为什么我们基本数据类型可以直接用string,int,long…而不用写全名的原因。
typeHandlerElement
注册我们自定义的类型处理器
同样,mybatis也会默认对一些数据类型注册类型处理器。
mapperElement
解析Mappers标签
if语句的4个分支分别对应包名,类路径,绝对url路径,类名配置的mapper。
MapperRegistry
针对引用包名和类(引用class)方式,mybatis调用configuration.addMapper(s),跟踪器代码发现本质上调用的是MapperRegistry.addMapper。而这个注册器通过MapperAnnotationBuilder解析Mapper。
而针对引用类路径,绝对url路径(引用资源)方式,mybatis直接通过XMLMapperBuilder去解析。
MapperAnnotationBuilder和XMLMapperBuilder看名字都是只解析注解或XML方式的配置,但实际上两者在解析的过程中是会相互调用的。所以无论你用何种方式,最终都会解析注解和xml配置。
MappedStatement
前面有提到,最终解析出来的配置都会设置到Configuration。所以MapperAnnotationBuilder、XMLMapperBuilder解析出来了啥呢?
其实就是一个个的MappedStatement,其属性如下。
每个MappedStatement对象都对应一个映射(注解或xml配置)中的select、insert、update或delete元素,它包含了执行SQL语句所需的所有信息,如SQL语句(sqlSource)、参数映射关系(parameterMap)、结果映射关系(resultMaps)等。
回到MapperAnnotationBuilder、XMLMapperBuilder,来看看这两个是怎么添加MappedStatement到Configuragtion的。
MapperAnnotationBuilder
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 else
SelectKey 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 #348
timeout = 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,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
null);
}
}
XMLMapperBuilder
来到configurationElement方法
来到buildStatementFromContext方法
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,一般是DynamicSqlSource
SqlSource 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.addMappedStatement
configuration.addMappedStatement(statement);
return statement;
}
SqlSessionFactory
SqlSessionFactory是一个接口,有两个实现,常用的是DefaultSqlSessionFactory。DefaultSqlSessionFactory中维护了一个Configuration对象,后续都会根据这个Configuration去创建SqlSession。
SqlSession
SqlSession同样也是一个接口,用到了门面设计模式,所有sql操作都通过这个门面去操作。而且也有两个实现,常用的是DefaultSqlSession,DefaultSqlSession中维护了Executor执行器,用来执行真正的sql逻辑。