MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。因为它的异常强大,映射器的 XML 文件就显得相对简单。若是拿它跟具备相同功能的 JDBC 代码进行对比,你会当即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,而且比普通的方法作的更好。html
SQL 映射文件有不多的几个顶级元素(按照它们应该被定义的顺序):java
对每一个标签的属性以及做用,这里不作解释, 能够参考官方文档:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.htmlnode
上一篇文章介绍了mybatis配置文件解析mappers节点的源码中有以下语句,从这里获得mapper映射文件时经过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); } }
它经过调用CacheBuilder的相应方法完成cache的建立。每一个cache内部都有一个惟一的ID,这个id的值就是namespace。建立好的cache对象存入configuration的cache缓存中(该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处)。sql
/** * 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; }
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); } } }
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; } }
sql节点用来定义可重用的sql语句片断, sqlElement方法负责解析sql元素。id属性用于区分不一样的sql元素,在同一个mapper配置文件中能够配置多个sql元素。mybatis
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 databaseId if (this.sqlFragments.containsKey(id)) { XNode context = this.sqlFragments.get(id); if (context.getStringAttribute("databaseId") != null) { return false; } } } return true; }
映射配置文件中还有一类比较重要的节点须要解析,其实就是select|insert|update|delete 节点,这些节点主要用于定义SQL语句,他们不在由XMLMapperBuilder进行解析,而是由XMLStatementBuilder负责进行解析,每一个节点会被解析成MappedStatement对象并存入到configuration对象中去。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素颇有帮助。app
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的id 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; } // 经过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); }
在解析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 values NamedNodeMap 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)); } }
在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")); //defaults boolean 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; // 生成SqlSource SqlSource 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接口,并注册到MapperRegistry中。
// 绑定mapper接口 private void bindMapperForNamespace() { //获取映射文件的命名空间 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 解析命名空间对应的类型 即dao boundType = 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); } } } }