Mybatis源码分析系列之配置文件加载(三)
也许文中的一些说明能解决你的困惑。也许有表述不清晰的地方。请大家多多指点。欢迎下方留言。本人准备系统的总结一下各种框架的一些源码。也希望大家能给我提出宝贵意见
我们今天继续上文 传送门:Mybatis源码分析系列之配置文件加载(二)
2.11 mapperElement
有很多说刚开始用的小朋友在开发中mappers扫描 配置然后 想要用注解跟xml混合开发但是配置的是resource报错。然后不得解!现在说明 如果你是使用注解开发 应该用包扫描或者class配置,如果是 纯xml则用包或者URL或Resource至于为什么。 在看完这篇文章之后你会豁然开朗的。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果是package标签
String mapperPackage = child.getStringAttribute("name");
//从制定包下 加载mepper接口 并根据mapper接口映射
configuration.addMappers(mapperPackage);
} else {
//下面分别是根据URL resource class 方式进行mapper接口查找以及接口映射
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 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 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.");
}
}
}
}
}
下面我们一次来分析这些源码。首先看到的是通过package标签如何进行mapper加载的以及接口映射
//第一步
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
//第二步
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
//第三步
public void addMappers(String packageName, Class<?> superType) {
//通过反射工具类。找到包下所有的类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
//继续调用 addMapper方法
addMapper(mapperClass);
}
}
//第四步
public <T> void addMapper(Class<T> type) {
//如果是接口
if (type.isInterface()) {
//hasMapper是 查找已知的knownMapper的Map 如果有就抛出解析异常 说明这个Mapper已经被注册了
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//1.注意一点这个是通过构建一个Map 来存放已经注册的Mapper
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.
/**我用这个区分Mybatis自己的注释。下面这个方法用于
*是注解解析器 因为可以通过xml以及注解来进行
*/
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//解析注解中的信息
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
// 返回这个类的对象的字符串表示形式
String resource = type.toString()
//判断是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
// **这个方法 是比较重要的方法因为 这个是resource配置mapper 跟url 配置都走的方法**
loadXmlResource();
//把解析的mapper加载 到 configuration中由于是包扫描 所以会加载 xml 以及mapper
configuration.addLoadedResource(resource);
//这个是辅助构建工具
assistant.setCurrentNamespace(type.getName());
//解析缓存
parseCache();
parseCacheRef();
//获得mapper中的方法
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();
}
下面继续分析 loadXmlResource
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
/**还是判断 例如 namespace:com.ly.mapper.UserMapper 在configuration中是否被加载过了。也就是是否存在*/
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//获得xml 路径
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
//加载XML为流
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
//如果流不为空
if (inputStream != null) {
//xmlMapper构建器 进行构建 我们跟如看一下
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
XmlMapperBuilder 没什么可说的 主要是看如何解析
parse()下面我们看下 parse()方法 重点重点重点 因为上面 4种解析 其实都走的这一个。。所以这个是最关键的一个点了
public void parse() {
// 会从已经解析过的 loadedResources 中去找这个是不是已经解析了。
//我们第一次跟进来势必不会被解析
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
下面才是重要的解析步骤 我们依次去分离 剖析
configurationElement方法
private void configurationElement(XNode context) {
//还是把XML中的NameSpace 这个节点 传入
try {
//获得namespace标签的属性
String namespace = context.getStringAttribute("namespace");
//如果为空 或者是 null 抛出以及构建异常。。所以说。namespace是必须的
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//这个是一个mybatis的辅助构建器。。
builderAssistant.setCurrentNamespace(namespace);
//二级缓存的配置 我后续会讲解。目前不做说明。反正知道这个是干嘛的就好 在后续的缓存种会分析
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//parameterMap 已经废弃。。就别看了。没用了。。真的非要看。可以自己看看。我反正不看 O(∩_∩)O
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//重点说明 这里面就分析 这个 以及 buildStatementFromContext
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql 片段的。。
sqlElement(context.evalNodes("/mapper/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);
}
}
resultMapElements 方法处理 resultMap标签
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
//把所有的resultMap都搂出来 你配置了多少ResultMap 这里面循环 遍历 调用resultMapElement
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
//中间转折方法。。用于调用最下面ResultMapElement方法
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
//核心方法
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
//错误内容 这是利用instance实例 然后给activity赋值。。 例如 rocessing mapper_resultMap[BaseResultMap]
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//获取id
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//获取类型 先找javatype 在去找resultType在找ofType 在找type的顺序 去获得
//ofType是用来映射到list集合属性中pojo的类型。
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//查找继承标签
String extend = resultMapNode.getStringAttribute("extends");
//获得全局配置。。开启后会自动设置嵌套查询中的属性 总开关 即使局部没有写属性 也会映射
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();
for (XNode resultChild : resultChildren) {
//查找constructor
if ("constructor".equals(resultChild.getName())) {
//下面我们会说明这个方法中的内容
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//鉴别器
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
// 应该是其他标签 比如 resultMap 然后 Collection 标签
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//调用resolve 返回 ResultMap 对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
constructor 标签
这个是有参构造方法。Mybatis 在映射ResultMap时候默认会找无参构造方法。但是有的时候我们可能为实体类生成了有参的构造方法,并且也没有给该实体类生成无参的构造方法,这个时候如果不做特殊配置,resultMap在生成实体类的时候就会报错,因为它没有找到无参构造方法
所以constructor 的作用在于制定 有参构造方法。
里面属性
- idArg ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
- arg 将被注入到构造方法的一个普通结果
processConstructorElement
这个只是一部分 我们还没有真正的分析最核心的 buildResultMappingFromContext代码。
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
//以上代码并不复杂 就是加载节点而已。
buildResultMappingFromContext方法
说明一下:
其实这个方法基本也就是找节点把所有的属性名称都写出来。比如列名|java类型|jdbc类型|
不为空的列才创建子类什么的|通过javaType或者jdbcType找到对应的类。
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
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.<ResultMapping> emptyList()));
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"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
buildResultMapping方法
这个没什么可讲的。。就是通过建造者模式的设计模式返回ResultMapping对象
public ResultMapping buildResultMapping(
Class<?> resultType,
String property,
String column,
Class<?> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class<? extends TypeHandler<?>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
List<ResultMapping> composites = parseCompositeColumnName(column);
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<ResultFlag>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
discriminator 标签
discriminator既不是一对多也不是一对一,这个我们称之为鉴别器级联,使用它我们可以在不同的条件下执行不同的查询匹配不同的实体类
里面的属性为:
- case – 基于某些值的结果映射
嵌套结果映射 – 一个 case 也是一个映射它本身的结果,因此可以包含很多相 同的元素,或者它可以参照一个外部的 resultMap。
我们可以通过鉴别器 来获得 我们要返回的对应的类
processDiscriminatorElement方法
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
//解析column标签
String column = context.getStringAttribute("column");
//解析javaType标签
String javaType = context.getStringAttribute("javaType");
//解析jdbcType标签
String jdbcType = context.getStringAttribute("jdbcType");
//解析typpeHandler标签
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<String, String>();
for (XNode caseChild : context.getChildren()) {
//遍历节点 拿到 value 属性。以及resultMap 然后放到鉴别器的Map中
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
buildDiscriminator 方法
这个方法也是通过建造者模式。返回对应的鉴别器类。
public Discriminator buildDiscriminator(
Class<?> resultType,
String column,
Class<?> javaType,
JdbcType jdbcType,
Class<? extends TypeHandler<?>> typeHandler,
Map<String, String> discriminatorMap) {
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<ResultFlag>(),
null,
null,
false);
Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
resultMapResolver.resolve()
public ResultMap resolve() {
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);
//判断是否有继承关系
if (extend != null) {
//判断是否已经解析过继承的resultMap
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
//有的话 获得继承的resulMap
ResultMap resultMap = configuration.getResultMap(extend);
//获得已经继承的ResultMap节点
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
//从继承的ResultMapping中删除 儿子的节点
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
//遍历儿子节点
for (ResultMapping resultMapping : resultMappings) {
//如果含有constructor 节点的标记
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
//跳出循环
if (declaresConstructor) {
//遍历父ResultMap 节点
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
//如果父节点中 也有 就删除
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
//然后加载所有的父 进ResultMap
resultMappings.addAll(extendedResultMappings);
}
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
//把resultMap加载到configuration中
configuration.addResultMap(resultMap);
return resultMap;
}
sqlElement
用于解析sql 片段
private void sqlElement(List<XNode> list) throws Exception {
//首先 先判断是不是配置了多sql方言。。比如 mysql oracle 等等
if (configuration.getDatabaseId() != null) {
// 如果有的话。。传入
sqlElement(list, configuration.getDatabaseId());
}
//没有走这个。。都是一个方法 传递参数不一样而已
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
// 下面俩个 一个是databasedid 一个id 分别获得属性
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
//调用下面的applyCurrentNamespace 方法。获得名称
id = builderAssistant.applyCurrentNamespace(id, false);
//判断数据库方言 是否匹配 下面有详细说明
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//qlFragment存储于Configuration内部。
sqlFragments.put(id, context);
}
}
}
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
//上面代码isReference 是false 所以走else方法
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
//判断字符串是不是从currentNamespace +“.”开始 如果是直接返回
if (base.startsWith(currentNamespace + ".")) {
return base;
}
//如果包含点 抛出异常。。等于就是没有 mapper方法的路径
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
//返回 一个 例如 com.ly.mapper.SysRolePrivilegeMapper.BaseResultMap
return currentNamespace + "." + base;
}
//MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//从configuration去除配置的 databasedid
//判断传入的databasedid 与这当前数据库databaseId 是否一致
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
//如果没有配置 但是下面写了。。那么这个废弃
if (databaseId != null) {
return false;
}
// skip this fragment if there is a previous one with a not null databaseId
if (this.sqlFragments.containsKey(id)) {
//如果没有配置 那么 相同的俩句话。。肯定要没有配置的。。有配置的舍弃
XNode context = this.sqlFragments.get(id);
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}
重点重点 buildStatementFromContext
用于加载select |insert|update|delete标签
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
parseStatementNode
解析语句节点 主要是那些sql语句 映射成Statement对象 然后加载到configuration中
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
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);
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
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
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))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
重点重点 langDriver.createSqlSource(configuration, context, parameterTypeClass)
这个是LanguageDriver接口。。然后对应的实现类有 XMLLanguageDriver
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
createSqlSource 创建sqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
//创建XmlScriptBuilder
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//解析成SqlSource
return builder.parseScriptNode();
}
parseScriptNode 方法解析节点
public SqlSource parseScriptNode() {
//解析出最小的sql节点的集合
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
//判断是否为动态sql
if (isDynamic) {
//1.是动态sql
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//2.不是动态sql
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
protected MixedSqlNode parseDynamicTags(XNode node) {
// 创建一个 sqlNode节点的contents 集合
List<SqlNode> contents = new ArrayList<SqlNode>();
//获得NodeList //是
NodeList children = node.getNode().getChildNodes();
//其实就是看节点中有多少。。然后判断长度。遍历 也就是把所有的sql 中的节点都搂出来。去循环。比如
/**select * from user where
*<if test="id!=null and id!=''">
*id=#{id}
*</if>
*/
// 这个节点是多少个呢。。也就是length。。我们这个是3个。。分别是#text | if |#text
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
//这个是俩个 一个是cdata 一个是普通文本。。我们这个 第一次循环进来的时候肯定去走
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//获得sql语句
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
//判断是动态sql
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//获得节点
String nodeName = child.getNode().getNodeName();
//节点判断是if where ...等
NodeHandler handler = nodeHandlerMap.get(nodeName);
//如果为空证明这个节点 并没有 是错误的节点。。
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//加入到目标节点contents中。动态sql
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
说明:我用几句话简单说明下他的运行原理
- 第一步 通过 parseDynamicTags 方法解析出最小的sql节点。比如 刚才上面写的。。一共有三个 并且是动态sql
- 第二步:判断是否为动态sql. 是通过isDynamic 来实现的。 在parseDynamicTags 判断节点为1 也就是标签节点。
- 第三部。返回不同的sqlSource
上面的方法很好理解 handleNode 方法是处理动态sql 比如 if标签吧
就把if标签 处理过的IfSqlNode 加入到contents中。。因为他们都是sqlNode接口的实现类。
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
最终由MapperBuilderAssistant完成MappedStatement对象的封装,并且将MappedStatement对象放入Configuration对象的mappedStatements容器中