mybatis mapper解析(4)
开篇
这篇文章的目的主要是想讲清楚mapper标签的解析过程,核心目的是想讲清楚/mapper/resultMap、/mapper/sql、select|insert|update|delete 这三大类标签的解析过程,以及生成最终的解析对象并保存到Configuration当中。
解析过程时序图
mapper标签解析过程解析过程源码分析
mapper.xml文件的常用格式
mapper.xml当中通用的几大类标签,分别是resultMap,sql,以及select相关语句,我们的解析就是针对这几类标签进行解析,当然千万千万不要遗漏了namespace这个标签。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qbd.mapper.StudentMappers">
<select id="findbyid" parameterType="Integer" resultMap="StudentResult">
select *from student where id=#{id}
</select>
<select id="findbygradeid" parameterType="Integer" resultMap="StudentResult">
select *from student where gid=#{gid}
</select>
<resultMap type="Student" id="StudentResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<association property="address" column="addid" select="com.qbd.mapper.AddressMappers.findbyid">
</association>
<association property="grade" column="gid" select="com.qbd.mapper.GradeMappers.findbyid">
</association>
</resultMap>
</mapper>
mapper源码解析入口
仅关注mapperElement(root.evalNode("mappers")),因为mappers里面包含了真正执行的SQL语句的定义。
<mappers>
<mapper resource="map/query.xml" />
<mapper resource="map/insert.xml" />
<mapper resource="map/update.xml" />
<mapper resource="map/delete.xml" />
</mappers>
---------------------XMLConfigBuilder.java--------------------------------------
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
开始遍历mappers标签下的所有mapper逐一进行解析,一般我们通过resource标签引入SQL定义的xml文件,进而通过XMLMapperBuilder对象进行parse操作。
//10.映射器
// 10.1使用类路径
// <mappers>
// <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
// <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
// <mapper resource="org/mybatis/builder/PostMapper.xml"/>
// </mappers>
//
// 10.2使用绝对url路径
// <mappers>
// <mapper url="file:///var/mappers/AuthorMapper.xml"/>
// <mapper url="file:///var/mappers/BlogMapper.xml"/>
// <mapper url="file:///var/mappers/PostMapper.xml"/>
// </mappers>
//
// 10.3使用java类名
// <mappers>
// <mapper class="org.mybatis.builder.AuthorMapper"/>
// <mapper class="org.mybatis.builder.BlogMapper"/>
// <mapper class="org.mybatis.builder.PostMapper"/>
// </mappers>
//
// 10.4自动扫描包下所有映射器
// <mappers>
// <package name="org.mybatis.builder"/>
// </mappers>
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
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) {
//10.3使用java类名
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.");
}
}
}
}
}
----------------------XMLMapperBuilder.java---------------------
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
Map<String, XNode> sqlFragments) {
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
//解析
public void parse() {
//如果没有加载过再加载,防止重复加载
if (!configuration.isResourceLoaded(resource)) {
//配置mapper
configurationElement(parser.evalNode("/mapper"));
//标记一下,已经加载过了
configuration.addLoadedResource(resource);
//绑定映射器到namespace
bindMapperForNamespace();
}
//还有没解析完的东东这里接着解析?
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
解析SQL定义的xml文件
核心标签在于我们解析 /mapper/resultMap标签,/mapper/sql标签,select|insert|update|delete标签。
-------------------XMLMapperBuilder.java-------------------
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已经废弃,老式风格的参数映射)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高级功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
/mapper/resultMap解析
解析resultMap放置到Configuration当中,这里把所有resultMap的标签列表开始进行解析,configuration.addResultMap(resultMap)。
private void resultMapElements(List<XNode> list) throws Exception {
//基本上就是循环把resultMap加入到Configuration里去,保持2份,一份缩略,一分全名
for (XNode resultMapNode : list) {
try {
//循环调resultMapElement
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
//5.1 配置resultMap
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
//错误上下文
//取得标示符 ("resultMap[userResultMap]")
// <resultMap id="userResultMap" type="User">
// <id property="id" column="user_id" />
// <result property="username" column="username"/>
// <result property="password" column="password"/>
// </resultMap>
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//一般拿type就可以了,后面3个难道是兼容老的代码?
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//高级功能,还支持继承?
// <resultMap id="carResult" type="Car" extends="vehicleResult">
// <result property="doorCount" column="door_count" />
// </resultMap>
String extend = resultMapNode.getStringAttribute("extends");
//autoMapping
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
//解析这个xml文件当中所有的<resultMap></resultMap> 节点
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//解析result map的constructor
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//解析result map的discriminator
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//调5.1.1 buildResultMappingFromContext,得到ResultMapping
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//最后再调ResultMapResolver得到ResultMap
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass,
extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType,
List<ResultFlag> flags) throws Exception {
//<id property="id" column="author_id"/>
//<result property="username" column="author_username"/>
String property = context.getStringAttribute("property");
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
//处理嵌套的result map
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resulSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType",
configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//又去调builderAssistant.buildResultMapping
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass,
jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn,
columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
}
public ResultMap resolve() {
//解析又去调用MapperBuilderAssistant.addResultMap
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
//建造者模式
ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
resultMapBuilder.discriminator(discriminator);
ResultMap resultMap = resultMapBuilder.build();
configuration.addResultMap(resultMap);
return resultMap;
}
/mapper/sql片段解析
遍历所有的sql标签挨个进行解析,然后放置到sqlFragments的map当中,sqlFragments.put(id, context)。
//6 配置sql(定义可重用的 SQL 代码段)
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
//6.1 配置sql
//<sql id="userColumns"> id,username,password </sql>
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
//比较简单,就是将sql片段放入hashmap,不过此时还没有解析sql片段
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
select|insert|update|delete语句解析
遍历所有的select|insert|update|delete的标签,挨个进行解析并保存到configuration当中,configuration.addMappedStatement(statement)。后面使用的时候通过select等标签的id直接获取statement从而实现了interface和xml定义SQL语句的映射关系了。
核心的SQL替换在方法includeParser.applyIncludes(context.getNode())当中实现。
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);这里的SqlSource有多个实现细节。
---------------------XMLMapperBuilder.java------------------------
//7.配置select|insert|update|delete
private void buildStatementFromContext(List<XNode> list) {
//调用7.1构建语句
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//7.1构建语句
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//构建所有语句,一个mapper下可以有很多select
//语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant,
context, requiredDatabaseId);
try {
//核心XMLStatementBuilder.parseStatementNode
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
//如果出现SQL语句不完整,把它记下来,塞到configuration去
configuration.addIncompleteStatement(statementParser);
}
}
}
------------------------XMLStatementBuilder.java-------------------------
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);
}
-------------------------XMLLanguageDriver.java---------------------
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
//用XML脚本构建器解析
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
---------------------XMLIncludeTransformer.java-----------------------------------
//<select id="selectUsers" resultType="map">
// select <include refid="userColumns"/>
// from some_table
// where id = #{id}
//</select>
public void applyIncludes(Node source) {
if (source.getNodeName().equals("include")) {
//走到这里,单独解析<include refid="userColumns"/>
//拿到SQL片段
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"));
//递归调用自己,应用上?
applyIncludes(toInclude);
//总之下面就是将字符串拼接进来,看不懂。。。
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
toInclude.getParentNode().removeChild(toInclude);
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
//一开始会走这段,取得所有儿子
NodeList children = source.getChildNodes();
for (int i=0; i<children.getLength(); i++) {
//递归调用自己
applyIncludes(children.item(i));
}
}
}
------------------------------------MapperBuilderAssistant.java-----------------------------
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;
}