cache – 对给定命名空间的缓存配置。
cache-ref – 对其他命名空间缓存配置的引用。
resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在将来被移除。
sql – 可被其他语句引用的可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
接着上一节往下走:配置mapper节点的方式有四种:package,resource,url,class 由于项目中常用的是xml,这里详细解读resource方式加载mapper
/**
* 解析mappers节点,加载mapper配置
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
//变量所有的mapper.xml配置有多个
for (XNode child : parent.getChildren()) {
//如果mapper是,配置mapper的四种方式:package,resource,url,class
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//获取子节点属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//通过xml方式配置mapper
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
//读取配置:mapper.xml
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过mapper.xml构建xml解析对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//解析mapper.xml
mapperParser.parse();
//通过url的方式配置mapper
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
//通过class的方式配置mapper
} 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.");
}
}
}
}
}
private void configurationElement(XNode context) {
try {
//获取namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//保持并校验namespace
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref标签 属性namespace相同的缓存将共享
cacheRefElement(context.evalNode("cache-ref"));
//解析cache标签
cacheElement(context.evalNode("cache"));
//解析paramteterMap标签 (由于该标签已被启用,就不解读了)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete标签
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);
}
}
由于解析标签的方法类似,这里取最常用的resultMap和sql标签解读
先看看官方属性说明:
源码:
/**
* 解析resultMap标签及其子节点
*/
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取type属性 如果没有则获取 ofType > ofType > resultType > javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//获取type类型的class对象
Class<?> typeClass = resolveClass(type);
// association和case可能会进入
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
//获取所有子节点 id,result,constructor...
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
//构造器
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) { //使用结果值来决定使用哪个 resultMap
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
//着重解读 id和result标签统一处理,id会额外添加一个枚举标记
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
//ResultMapping对象对应mapper.xml中的ResultMap的子节点
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//获取id,extends,autoMapping属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//ResultMapResolver对象用于构建 ResultMap对应mapper.xml中的ResultMap节点
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//返回构建ResultMap对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
/**
* 构建ResultMapping
*/
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
//获取属性,构建ResultMapping对象
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.emptyList(), resultType));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
//获取java的数据类型
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
//获取jdbc的数据类型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
/**
* 解析sql节点并保存到全局配置(configuration)中
*/
public void parseStatementNode() {
//在命名空间中唯一的标识符,可以被用来引用这条语句。
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取节点名称
String nodeName = context.getNode().getNodeName();
//获取sql的类型 UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
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());
//获取参数类型
String parameterType = context.getStringAttribute("parameterType");
//获取参数class
Class<?> parameterTypeClass = resolveClass(parameterType);
//获取语言格式驱动 默认使用Xml XMLLanguageDriver
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//在include之后解析selectKey并删除它们。
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
//解析SQL (pre: 和被解析和删除)
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//组装sql XMLLanguageDriver
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//将会传入这条语句的参数类的完全限定名或别名
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
//这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
Integer fetchSize = context.getIntAttribute("fetchSize");
//这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
Integer timeout = context.getIntAttribute("timeout");
//这是引用外部 parameterMap
String parameterMap = context.getStringAttribute("parameterMap");
//从这条语句中返回的期望类型的类的完全限定名或别名。
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
//外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
//可以使用 resultMap 或 resultType,但不能同时使用。
String resultMap = context.getStringAttribute("resultMap");
//FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
//(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值
// 或者通过 insert 语句的 selectKey 子元素设置它的键值
String keyProperty = context.getStringAttribute("keyProperty");
//(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,
// 当主键列不是表中的第一列的时候需要设置。
String keyColumn = context.getStringAttribute("keyColumn");
//这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。
String resultSets = context.getStringAttribute("resultSets");
//解析sql标签信息并保存到全局配置中去 MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
sql语句中等标签在调用sql语句执行的时候才能解析取值
代码到这里,mybatis初始化配置文件完成,后续就是通过SqlSession操作我们初始化的sql了
XML配置解析器(用于解析:mapper.xml)
ResultMapResolver对象用于构建ResultMap对象
对应mapper.xml中的ResultMap节点
ResultMapping对象对应着mapper.xml中的ResultMap>Result,Id,constructor…
sql命令的类型
保存解析后的sql(select|insert|update|delete)语句信息