转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析

MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

对每个标签的属性以及作用,这里不做解释, 可以参考官方文档:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

上一篇文章介绍了mybatis配置文件解析mappers节点的源码中有如下语句,从这里得到mapper映射文件时通过XMLMapperBuilder解析的。

一、XMLMapperBuilder

//mapper映射文件都是通过XMLMapperBuilder解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();
//解析mapper文件
public void parse() {// 判断是否已经加载过改映射文件if (!configuration.isResourceLoaded(resource)) {// 处理mapper节点configurationElement(parser.evalNode("/mapper"));// 将resource添加到configuration的loadedResources集合中保存 它是HashSet<String>configuration.addLoadedResource(resource);//注册mapper接口bindMapperForNamespace();}// 处理解析失败的resultMap节点parsePendingResultMaps();// 处理解析失败的cache-ref节点parsePendingCacheRefs();// 处理解析失败的sql节点parsePendingStatements();
}
private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}// 记录当前命名空间builderAssistant.setCurrentNamespace(namespace);// 解析cache-ref节点cacheRefElement(context.evalNode("cache-ref"));// 解析cache节点cacheElement(context.evalNode("cache"));// 解析parameterMap节点,这个已经被废弃,不推荐使用parameterMapElement(context.evalNodes("/mapper/parameterMap"));// 解析resultMap节点resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析sql节点sqlElement(context.evalNodes("/mapper/sql"));// 解析statement 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);}
}

1.cache节点

它通过调用CacheBuilder的相应方法完成cache的创建。每个cache内部都有一个唯一的ID,这个id的值就是namespace。创建好的cache对象存入configuration的cache缓存中(该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处)。

/*** cache- 配置本定命名空间的缓存。*         type- cache实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)*         eviction- 回收算法,默认为LRU,可选的算法有:*             LRU– 最近最少使用的:移除最长时间不被使用的对象。*             FIFO– 先进先出:按对象进入缓存的顺序来移除它们。*             SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。*             WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。*         flushInterval- 刷新间隔,默认为1个小时,单位毫秒*         size- 缓存大小,默认大小1024,单位为引用数*         readOnly- 只读* @param context* @throws Exception*/
private void cacheElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");boolean readWrite = !context.getBooleanAttribute("readOnly", false);boolean blocking = context.getBooleanAttribute("blocking", false);Properties props = context.getChildrenAsProperties();builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}
}
public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class)).addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache;
}

2.cache-ref节点

cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用。创建好的cache-ref引用关系存入configuration的cacheRefMap缓存中。

/***  cache-ref–从其他命名空间引用缓存配置。*         如果你不想定义自己的cache,可以使用cache-ref引用别的cache。*         因为每个cache都以namespace为id,*         所以cache-ref只需要配置一个namespace属性就可以了。*         需要注意的是,如果cache-ref和cache都配置了,以cache为准。* @param context*/
private void cacheRefElement(XNode context) {if (context != null) {configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));try {cacheRefResolver.resolveCacheRef();} catch (IncompleteElementException e) {configuration.addIncompleteCacheRef(cacheRefResolver);}}
}

3.resultMap节点

resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析。resultMap节点下除了discriminator子节点的其他子节点都会解析成对应的ResultMapping对象,而每个<resultMap>节点都会被解析成一个ResultMap对象,创建好的resultMap存入configuration的resultMaps缓存中(该缓存以namespace+resultMap的id为key,这里再次体现了mybatis的namespace的强大用处)。

private void resultMapElements(List<XNode> list) throws Exception {for (XNode resultMapNode : list) {try {resultMapElement(resultMapNode);} catch (IncompleteElementException e) {// ignore, it will be retried}}
}private ResultMap resultMapElement(XNode resultMapNode) throws Exception {return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());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) {if ("constructor".equals(resultChild.getName())) {processConstructorElement(resultChild, typeClass, resultMappings);} else if ("discriminator".equals(resultChild.getName())) {discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);} else {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 {return resultMapResolver.resolve();} catch (IncompleteElementException  e) {configuration.addIncompleteResultMap(resultMapResolver);throw e;}
}

4.sql节点解析

sql节点用来定义可重用的sql语句片段, sqlElement方法负责解析sql元素。id属性用于区分不同的sql元素,在同一个mapper配置文件中可以配置多个sql元素。

private void sqlElement(List<XNode> list) throws Exception {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) {String databaseId = context.getStringAttribute("databaseId");String id = context.getStringAttribute("id");id = builderAssistant.applyCurrentNamespace(id, false);if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {// 记录到sqlFragments中保存,其实 构造函数中可以看到该字段指向了configuration的sqlFragments集合中sqlFragments.put(id, context);}}
}private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {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 databaseIdif (this.sqlFragments.containsKey(id)) {XNode context = this.sqlFragments.get(id);if (context.getStringAttribute("databaseId") != null) {return false;}}}return true;
}

二、XMLStatementBuilder

映射配置文件中还有一类比较重要的节点需要解析,其实就是select|insert|update|delete 节点,这些节点主要用于定义SQL语句,他们不在由XMLMapperBuilder进行解析,而是由XMLStatementBuilder负责进行解析,每个节点会被解析成MappedStatement对象并存入到configuration对象中去。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素很有帮助。

1.MappedStatement

MappedStatement包含了这些节点的很多属性,其中比较重要的如下:

private String resource;//节点中的id 包括命名空间
private SqlSource sqlSource;//SqlSource对象,对应一条SQL语句
private SqlCommandType sqlCommandType;//SQL的类型,insert,delete,select,update

解析过程代码如下:

public void parseStatementNode() {// 获取sql节点的id以及databaseId如果和当前不匹配不加载改节点,// 如果存在id相同且databaseId不为空的节点也不在加载改节点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);// 根据节点的名称设置sqlCommandType的类型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);// 在解析SQL语句之前先处理include节点XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// 处理selectKey节点processSelectKeyNodes(id, parameterTypeClass, langDriver);// 完成节点的解析 该部分是核心SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);// 获取resultSets keyProperty keyColumn三个属性String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;// 获取selectKey节点对应的selectKeyGenerator的idString 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;}// 通过MapperBuilderAssistant创建MappedStatement对象,// 并添加到configuration.mappedStatements集合中保存builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

2、解析include节点

在解析statement节点之前首先通过XMLIncludeTransformer解析include节点改过程会将include节点替换<sql>节点中定义的sql片段,并将其中的${xx}占位符换成真实的参数,

private void applyIncludes(Node source, final Properties variablesContext, boolean included) {if (source.getNodeName().equals("include")) {  // ---(2)处理include节点// 查找refid属性指向的<sql>,返回的是深克隆的Node对象Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);Properties toIncludeContext = getVariablesContext(source, variablesContext);//递归处理include节点applyIncludes(toInclude, toIncludeContext, true);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 将<include>节点替换<sql>节点source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 将<sql>节点的子节点添加到<sql>节点的前面toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 替换后删除<sql>节点toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {// ---(1)if (included && !variablesContext.isEmpty()) {// replace variables in attribute valuesNamedNodeMap attributes = source.getAttributes();for (int i = 0; i < attributes.getLength(); i++) {// 遍历当前sql的子节点Node attr = attributes.item(i);attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));}}NodeList children = source.getChildNodes();for (int i = 0; i < children.getLength(); i++) {applyIncludes(children.item(i), variablesContext, included);}} else if (included && source.getNodeType() == Node.TEXT_NODE&& !variablesContext.isEmpty()) {// ---(3)// replace variables in text node 替换对应的占位符source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}
}

3、解析selectKey节点

在insert,update节点中可以定义selectKey节点来解决主键自增问题。

private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {String resultType = nodeToHandle.getStringAttribute("resultType");Class<?> resultTypeClass = resolveClass(resultType);StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));String keyProperty = nodeToHandle.getStringAttribute("keyProperty");String keyColumn = nodeToHandle.getStringAttribute("keyColumn");boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));//defaultsboolean useCache = false;boolean resultOrdered = false;KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;Integer fetchSize = null;Integer timeout = null;boolean flushCache = false;String parameterMap = null;String resultMap = null;ResultSetType resultSetTypeEnum = null;// 生成SqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);// selectKey节点中只能配置select语句SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 创建MappedStatement对象,并添加到configuration的mappedStatements集合中保存builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);id = builderAssistant.applyCurrentNamespace(id, false);MappedStatement keyStatement = configuration.getMappedStatement(id, false);// 创建对应的KeyGenerator(主键自增策略),添加到configuration中configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

三、绑定Mapper接口

每个映射配置文件的命名空间可以绑定一个Mapper接口,并注册到MapperRegistry中。

// 绑定mapper接口
private void bindMapperForNamespace() {//获取映射文件的命名空间String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 解析命名空间对应的类型 即daoboundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// 是否已经加载了// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResource//注册configuration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}
}

mybatis源码阅读(三):mybatis初始化(下)mapper解析相关推荐

  1. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  2. Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  3. mybatis源码阅读(八) ---Interceptor了解一下

    转载自  mybatis源码阅读(八) ---Interceptor了解一下 1 Intercetor MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis允许 ...

  4. 封装成jar包_通用源码阅读指导mybatis源码详解:io包

    io包 io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作. 说到输入/输出,首先想到的就是对磁盘文件的读写.在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的 ...

  5. mybatis源码阅读

    说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...

  6. mybatis源码阅读(一):SqlSession和SqlSessionFactory

    转载自  mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...

  7. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

  8. Mybatis 源码阅读环境搭建

    Mybatis源码阅读环境搭建 前言 一.下载mybatis的源码 二.编译源码 三.创建测试项目 前言     mybatis源码阅读环境搭建还是比较简单的,接下来我们讲解一下如何搭建该源码阅读环境 ...

  9. mybatis源码阅读(七) ---ResultSetHandler了解一下

    转载自  mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...

最新文章

  1. 验证码识别,发票编号识别(转)
  2. asp.net访问sqlserver获取数据、IsPostBack属性和VS可视化调试的概念
  3. java 文件路径表达式_Java基础(二十二) Lambda表达式和File类
  4. MM中如何更改物料的评估类
  5. richtextbox自动滚动到最下面_工业自动化直线运动部件大全,导轨、轴承、衬套、丝杠、导向轴简介说明...
  6. hdu 4983 Goffi and GCD(欧拉函数)
  7. Dancing Stars on Me HDU - 5533
  8. 技术社区_如何加入技术社区
  9. MemCache对PHP页面的缓存加速优化
  10. 人工智能究竟会不会让程序员失业?
  11. 85.一致性哈希算法:hash模块
  12. java类的加载与初始化_Java类何时以及如何加载和初始化?
  13. 使用原生js 监听video 当前播放时间和是否点击了播放或者暂停按钮
  14. 中小企业OA系统自动办公软件
  15. (银行案例)智能营销赋能大零售转型
  16. 关于坐标系(大地坐标、平面坐标、投影、北京54、西安80、WGS84)的一些理解
  17. C# Speech学习笔记(一)
  18. edge浏览器怎么设置activex_Microsoft Edge拥抱HTML5和JavaScript,不再支持IE上的ActiveX技术...
  19. Python爬虫:Python+WebSocket获取体育实时赛事数据
  20. LTE网络中UU与X2接口研究

热门文章

  1. java怎么判断数据类型_数据类型判断
  2. 7-6 区间覆盖 (10 分)(思路+详解)Come 宝!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  3. [SpringSecurity]web权限方案_用户注销
  4. [C++STL]queue容器用法介绍
  5. 数据结构与算法--查找与排序另类用法-旋转数组中的最小数字
  6. html程序国庆节祝福,2018国庆节祝福祖国的话
  7. java开发中准则怎么写_Java开发中通用的方法和准则20条
  8. Java线程的6种状态
  9. 多项式除法,多项式取模
  10. 最大子序和:单调队列维护一个上升序列