之前我在【Mybatis源码学习】初始化阶段中重点讲述了核心配置类XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder各自的功能。我们先熟悉下这“三剑客”,看下图即可。
本章节中,我们重点跟一下XMLMapperBuilder、XMLStatementBuilder解析sql的源码过程。
XMLMapperBuilder、XMLStatementBuilder均实现了BaseBuilder。
XMLMapperBuilder主要功能:
遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder,比如user.xml,内部会使用XMLStatementBuilder处理xml中的每个节点。
XMLStatementBuilder主要功能:
解析xml文件中各个节点,比如select,insert,update,delete节点,内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。
在【Mybatis源码学习】初始化阶段中,我们分析了XMLConfigBuilder的主要工作流程。还是以下面的代码为例,重点分析下XMLMapperBuilder和XMLStatementBuilder解析sql的流程。
@Before
public void init() throws IOException {
//--------------------第一阶段---------------------------
// 1.读取mybatis配置文件创SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
}
进入new SqlSessionFactoryBuilder().build(inputStream)方法,我们看看parse的方法。
而我这里的mybatis-config.xml文件如下:
<configuration>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
<typeAliases>
<package name="com.enjoylearning.mybatis.entity"/>
typeAliases>
<plugins>
<plugin interceptor="com.enjoylearning.mybatis.Interceptors.ThresholdInterceptor">
<property name="threshold" value="10"/>
plugin>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="pageSizeZero" value="true"/>
plugin>
plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc_driver}"/>
<property name="url" value="${jdbc_url}"/>
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="sqlmapper/UserInfoMapper.xml"/>
<mapper resource="sqlmapper/TUserTestMapper.xml"/>
<mapper resource="sqlmapper/TRoleMapper.xml"/>
<mapper resource="sqlmapper/TJobHistoryMapper.xml"/>
<mapper resource="sqlmapper/TPositionMapper.xml"/>
<mapper resource="sqlmapper/THealthReportFemaleMapper.xml"/>
<mapper resource="sqlmapper/THealthReportMaleMapper.xml"/>
mappers>
configuration>
对于parser.evalNode("/configuration"),这里我就不赘述了,重点关注parseConfiguration里面的mapperElement()方法。
这个mapperElement()主要就是解析
/**
* 解析mappers节点,例如:
*
*
*
*
* @param parent mappers节点
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 处理mappers的子节点,即mapper节点或者package节点
if ("package".equals(child.getName())) { // package节点
// 取出包路径
String mapperPackage = child.getStringAttribute("name");
// 全部加入Mappers中
configuration.addMappers(mapperPackage);
} else {
// resource、url、class这三个属性只有一个生效
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获取文件的输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用XMLMapperBuilder解析映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 从网络获得输入流
InputStream inputStream = Resources.getUrlAsStream(url);
// 使用XMLMapperBuilder解析映射文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 配置的不是映射文件,而是映射接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
for循环里的if主要是针对
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
mappers>
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
mappers>
<mappers>
<package name="org.mybatis.builder"/>
mappers>
这里关注两个方法:
它们最终的目的就是将映射器的class对象,以及其代理类设置到集合中,采用的是JDK代理。
我们先看addMapper()做了些啥。
//将mapper接口的工厂类添加到mapper注册中心
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//解析接口上的注解信息,并添加至configuration对象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
而XMLMapperBuilder的parse()方法:
public void parse() {
//判断是否已经加载该配置文件
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));//处理mapper节点
configuration.addLoadedResource(resource);//将mapper文件添加到configuration.loadedResources中
bindMapperForNamespace();//注册mapper接口
}
//处理解析失败的ResultMap节点
parsePendingResultMaps();
//处理解析失败的CacheRef节点
parsePendingCacheRefs();
//处理解析失败的Sql语句节点
parsePendingStatements();
}
我们在看看MapperAnnotationBuilder的parse方法,该类主要是以注解的方式构建mapper,用的比较少。
/**
* 解析包含注解的接口文档
*/
public void parse() {
String resource = type.toString();
// 防止重复分析
if (!configuration.isResourceLoaded(resource)) {
// 寻找类名称对应的 resource路径下是否有 xml 配置,如果有则直接解析掉,这样就支持注解和xml一起混合使用了
loadXmlResource();
// 记录资源路径
configuration.addLoadedResource(resource);
// 设置命名空间
assistant.setCurrentNamespace(type.getName());
// 处理缓存
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// 排除桥接方法,桥接方法是为了匹配泛型的类型擦除而由编译器自动引入的,并非用户编写的方法,因此要排除掉。
// issue #237
if (!method.isBridge()) {
// 解析该方法
parseStatement(method);
}
} catch (IncompleteElementException e) {
// 异常方法暂存起来
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 处理异常的方法
parsePendingMethods();
}
关键看看parseStatement()方法,主要就是解析注解上的信息。再通过getSqlSourceFromAnnotations方法获取sqlSource.
/**
* 解析该方法,主要是解析方法上的注解信息
* @param method
*/
void parseStatement(Method method) {
// 通过字方法获取参数类型
Class<?> parameterTypeClass = getParameterType(method);
// 获取方法的脚本语言渠道
LanguageDriver languageDriver = getLanguageDriver(method);
// 通过注解获取SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
// 获取方法上可能存在的配置信息,配置信息由@Options注解指定
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 = configuration.getDefaultResultSetType();
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
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() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
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();
if (options.resultSetType() != ResultSetType.DEFAULT) {
resultSetType = options.resultSetType();
}
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 将获取到的信息存入 configuration
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
getSqlSourceFromAnnotations()方法。
/**
* 通过注解获取SqlSource对象
*
* @param method 含有注解的方法
* @param parameterType 参数类型
* @param languageDriver 语言渠道
* @return SqlSource对象
*/
private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
try {
// 遍历寻找是否有 Select、Insert、Update、Delete四个注解之一
Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
// 遍历寻找是否有 SelectProvider、InsertProvider、UpdateProvider、DeleteProvider 四个注解之一
Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
// 两类注解不能同时使用
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
// 取出Select、Insert、Update、Delete四个注解之一
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
// 取出value值
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 基于字符串构建SqlSource,直接注解获取SQL
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
// // 取出 SelectProvider、InsertProvider、UpdateProvider、DeleteProvider 四个注解之一
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
// 根据对应的方法获取SqlSource,间接注解获取SQL
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
总结下解析mapper的parse()方法有两种方式:
MapperAnnotationBuilder的parse方法与XMLMapperBuilder的parse方法逻辑上略有不同,主要体现在对节点的解析上。
上面只是大致分析了*mapper.java与*mapper.xml映射注册的过程,由于我们的*mapper.xml还有很多其他的xml标签,这里我们需要具体了解下其中的解析流程。还是回到XMLMapperBuilder的parse()方法。
我们看看configurationElement()方法干了些啥。
/**
* 解析映射文件的下层节点
* @param context 映射文件根节点
*/
private void configurationElement(XNode context) {
try {
//获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置builderAssistant的namespace属性
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
//重点分析 :解析cache节点----------------1-------------------二级缓存
cacheElement(context.evalNode("cache"));
//解析parameterMap节点(已废弃)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//重点分析 :解析select、insert、update、delete节点 ----------------3-------------------sql解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
重点关注这几个方法:
//解析select、insert、update、delete节点
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//处理所有的sql语句节点并注册至configuration对象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//创建XMLStatementBuilder 专门用于解析sql语句节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句节点,并将解析结果存储到 configuration 的 mappedStatements 集合中
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
点进去看看parseStatementNode()方法。
public void parseStatementNode() {
//获取sql节点的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/*获取sql节点的各种属性*/
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//根据sql节点的名称获取SqlCommandType(INSERT, UPDATE, DELETE, SELECT)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//在解析sql语句之前先解析节点(查询的结果有哪些参数)
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//在解析sql语句之前,处理子节点,并在xml节点中删除
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
//解析sql语句是解析mapper.xml的核心,实例化sqlSource,使用sqlSource封装sql语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//获取resultSets属性
String resultSets = context.getStringAttribute("resultSets");
//获取主键信息keyProperty
String keyProperty = context.getStringAttribute("keyProperty");
//获取主键信息keyColumn
String keyColumn = context.getStringAttribute("keyColumn");
//根据获取对应的SelectKeyGenerator的id
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
//获取keyGenerator对象,如果是insert类型的sql语句,会使用KeyGenerator接口获取数据库生产的id;
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//通过builderAssistant实例化MappedStatement,并注册至configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
总结下它干了哪些活儿:
解析