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

女爷i 2023-07-02 06:21 23阅读 0赞

转载自 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

  1. //mapper映射文件都是通过XMLMapperBuilder解析
  2. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  3. mapperParser.parse();
  4. //解析mapper文件
  5. public void parse() {
  6. // 判断是否已经加载过改映射文件
  7. if (!configuration.isResourceLoaded(resource)) {
  8. // 处理mapper节点
  9. configurationElement(parser.evalNode("/mapper"));
  10. // 将resource添加到configuration的loadedResources集合中保存 它是HashSet<String>
  11. configuration.addLoadedResource(resource);
  12. //注册mapper接口
  13. bindMapperForNamespace();
  14. }
  15. // 处理解析失败的resultMap节点
  16. parsePendingResultMaps();
  17. // 处理解析失败的cache-ref节点
  18. parsePendingCacheRefs();
  19. // 处理解析失败的sql节点
  20. parsePendingStatements();
  21. }
  22. private void configurationElement(XNode context) {
  23. try {
  24. String namespace = context.getStringAttribute("namespace");
  25. if (namespace == null || namespace.equals("")) {
  26. throw new BuilderException("Mapper's namespace cannot be empty");
  27. }
  28. // 记录当前命名空间
  29. builderAssistant.setCurrentNamespace(namespace);
  30. // 解析cache-ref节点
  31. cacheRefElement(context.evalNode("cache-ref"));
  32. // 解析cache节点
  33. cacheElement(context.evalNode("cache"));
  34. // 解析parameterMap节点,这个已经被废弃,不推荐使用
  35. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  36. // 解析resultMap节点
  37. resultMapElements(context.evalNodes("/mapper/resultMap"));
  38. // 解析sql节点
  39. sqlElement(context.evalNodes("/mapper/sql"));
  40. // 解析statement
  41. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  42. } catch (Exception e) {
  43. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  44. }
  45. }

1.cache节点

  1. 它通过调用CacheBuilder的相应方法完成cache的创建。每个cache内部都有一个唯一的ID,这个id的值就是namespace。创建好的cache对象存入configurationcache缓存中(该缓存以cacheID属性即namespacekey,这里再次体现了mybatisnamespace的强大用处)。
  2. /**
  3. * cache- 配置本定命名空间的缓存。
  4. * type- cache实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)
  5. * eviction- 回收算法,默认为LRU,可选的算法有:
  6. * LRU– 最近最少使用的:移除最长时间不被使用的对象。
  7. * FIFO– 先进先出:按对象进入缓存的顺序来移除它们。
  8. * SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  9. * WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  10. * flushInterval- 刷新间隔,默认为1个小时,单位毫秒
  11. * size- 缓存大小,默认大小1024,单位为引用数
  12. * readOnly- 只读
  13. * @param context
  14. * @throws Exception
  15. */
  16. private void cacheElement(XNode context) throws Exception {
  17. if (context != null) {
  18. String type = context.getStringAttribute("type", "PERPETUAL");
  19. Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
  20. String eviction = context.getStringAttribute("eviction", "LRU");
  21. Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
  22. Long flushInterval = context.getLongAttribute("flushInterval");
  23. Integer size = context.getIntAttribute("size");
  24. boolean readWrite = !context.getBooleanAttribute("readOnly", false);
  25. boolean blocking = context.getBooleanAttribute("blocking", false);
  26. Properties props = context.getChildrenAsProperties();
  27. builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  28. }
  29. }
  30. public Cache useNewCache(Class<? extends Cache> typeClass,
  31. Class<? extends Cache> evictionClass,
  32. Long flushInterval,
  33. Integer size,
  34. boolean readWrite,
  35. boolean blocking,
  36. Properties props) {
  37. Cache cache = new CacheBuilder(currentNamespace)
  38. .implementation(valueOrDefault(typeClass, PerpetualCache.class))
  39. .addDecorator(valueOrDefault(evictionClass, LruCache.class))
  40. .clearInterval(flushInterval)
  41. .size(size)
  42. .readWrite(readWrite)
  43. .blocking(blocking)
  44. .properties(props)
  45. .build();
  46. configuration.addCache(cache);
  47. currentCache = cache;
  48. return cache;
  49. }

2.cache-ref节点

  1. cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用。创建好的cache-ref引用关系存入configurationcacheRefMap缓存中。
  2. /**
  3. * cache-ref–从其他命名空间引用缓存配置。
  4. * 如果你不想定义自己的cache,可以使用cache-ref引用别的cache。
  5. * 因为每个cache都以namespace为id,
  6. * 所以cache-ref只需要配置一个namespace属性就可以了。
  7. * 需要注意的是,如果cache-ref和cache都配置了,以cache为准。
  8. * @param context
  9. */
  10. private void cacheRefElement(XNode context) {
  11. if (context != null) {
  12. configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
  13. CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
  14. try {
  15. cacheRefResolver.resolveCacheRef();
  16. } catch (IncompleteElementException e) {
  17. configuration.addIncompleteCacheRef(cacheRefResolver);
  18. }
  19. }
  20. }

3.resultMap节点

  1. resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析。resultMap节点下除了discriminator子节点的其他子节点都会解析成对应的ResultMapping对象,而每个<resultMap>节点都会被解析成一个ResultMap对象,创建好的resultMap存入configurationresultMaps缓存中(该缓存以namespace+resultMapidkey,这里再次体现了mybatisnamespace的强大用处)。
  2. private void resultMapElements(List<XNode> list) throws Exception {
  3. for (XNode resultMapNode : list) {
  4. try {
  5. resultMapElement(resultMapNode);
  6. } catch (IncompleteElementException e) {
  7. // ignore, it will be retried
  8. }
  9. }
  10. }
  11. private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
  12. return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  13. }
  14. private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
  15. ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  16. String id = resultMapNode.getStringAttribute("id",
  17. resultMapNode.getValueBasedIdentifier());
  18. String type = resultMapNode.getStringAttribute("type",
  19. resultMapNode.getStringAttribute("ofType",
  20. resultMapNode.getStringAttribute("resultType",
  21. resultMapNode.getStringAttribute("javaType"))));
  22. String extend = resultMapNode.getStringAttribute("extends");
  23. Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  24. Class<?> typeClass = resolveClass(type);
  25. Discriminator discriminator = null;
  26. List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
  27. resultMappings.addAll(additionalResultMappings);
  28. List<XNode> resultChildren = resultMapNode.getChildren();
  29. for (XNode resultChild : resultChildren) {
  30. if ("constructor".equals(resultChild.getName())) {
  31. processConstructorElement(resultChild, typeClass, resultMappings);
  32. } else if ("discriminator".equals(resultChild.getName())) {
  33. discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
  34. } else {
  35. List<ResultFlag> flags = new ArrayList<ResultFlag>();
  36. if ("id".equals(resultChild.getName())) {
  37. flags.add(ResultFlag.ID);
  38. }
  39. resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
  40. }
  41. }
  42. ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  43. try {
  44. return resultMapResolver.resolve();
  45. } catch (IncompleteElementException e) {
  46. configuration.addIncompleteResultMap(resultMapResolver);
  47. throw e;
  48. }
  49. }

4.sql节点解析

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

  1. private void sqlElement(List<XNode> list) throws Exception {
  2. if (configuration.getDatabaseId() != null) {
  3. sqlElement(list, configuration.getDatabaseId());
  4. }
  5. sqlElement(list, null);
  6. }
  7. private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
  8. for (XNode context : list) {
  9. String databaseId = context.getStringAttribute("databaseId");
  10. String id = context.getStringAttribute("id");
  11. id = builderAssistant.applyCurrentNamespace(id, false);
  12. if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
  13. // 记录到sqlFragments中保存,其实 构造函数中可以看到该字段指向了configuration的sqlFragments集合中
  14. sqlFragments.put(id, context);
  15. }
  16. }
  17. }
  18. private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
  19. if (requiredDatabaseId != null) {
  20. if (!requiredDatabaseId.equals(databaseId)) {
  21. return false;
  22. }
  23. } else {
  24. if (databaseId != null) {
  25. return false;
  26. }
  27. // skip this fragment if there is a previous one with a not null databaseId
  28. if (this.sqlFragments.containsKey(id)) {
  29. XNode context = this.sqlFragments.get(id);
  30. if (context.getStringAttribute("databaseId") != null) {
  31. return false;
  32. }
  33. }
  34. }
  35. return true;
  36. }

二、XMLStatementBuilder

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

1.MappedStatement

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

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

解析过程代码如下:

  1. public void parseStatementNode() {
  2. // 获取sql节点的id以及databaseId如果和当前不匹配不加载改节点,
  3. // 如果存在id相同且databaseId不为空的节点也不在加载改节点
  4. String id = context.getStringAttribute("id");
  5. String databaseId = context.getStringAttribute("databaseId");
  6. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  7. return;
  8. }
  9. // 获取节点的多种属性
  10. Integer fetchSize = context.getIntAttribute("fetchSize");
  11. Integer timeout = context.getIntAttribute("timeout");
  12. String parameterMap = context.getStringAttribute("parameterMap");
  13. String parameterType = context.getStringAttribute("parameterType");
  14. Class<?> parameterTypeClass = resolveClass(parameterType);
  15. String resultMap = context.getStringAttribute("resultMap");
  16. String resultType = context.getStringAttribute("resultType");
  17. String lang = context.getStringAttribute("lang");
  18. LanguageDriver langDriver = getLanguageDriver(lang);
  19. Class<?> resultTypeClass = resolveClass(resultType);
  20. String resultSetType = context.getStringAttribute("resultSetType");
  21. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  22. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  23. // 根据节点的名称设置sqlCommandType的类型
  24. String nodeName = context.getNode().getNodeName();
  25. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  26. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  27. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  28. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  29. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  30. // 在解析SQL语句之前先处理include节点
  31. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  32. includeParser.applyIncludes(context.getNode());
  33. // 处理selectKey节点
  34. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  35. // 完成节点的解析 该部分是核心
  36. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  37. // 获取resultSets keyProperty keyColumn三个属性
  38. String resultSets = context.getStringAttribute("resultSets");
  39. String keyProperty = context.getStringAttribute("keyProperty");
  40. String keyColumn = context.getStringAttribute("keyColumn");
  41. KeyGenerator keyGenerator;
  42. // 获取selectKey节点对应的selectKeyGenerator的id
  43. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  44. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  45. if (configuration.hasKeyGenerator(keyStatementId)) {
  46. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  47. } else {
  48. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  49. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  50. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  51. }
  52. // 通过MapperBuilderAssistant创建MappedStatement对象,
  53. // 并添加到configuration.mappedStatements集合中保存
  54. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  55. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  56. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  57. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  58. }

2、解析include节点

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

  1. private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
  2. if (source.getNodeName().equals("include")) { // ---(2)处理include节点
  3. // 查找refid属性指向的<sql>,返回的是深克隆的Node对象
  4. Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
  5. Properties toIncludeContext = getVariablesContext(source, variablesContext);
  6. //递归处理include节点
  7. applyIncludes(toInclude, toIncludeContext, true);
  8. if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
  9. toInclude = source.getOwnerDocument().importNode(toInclude, true);
  10. }
  11. // 将<include>节点替换<sql>节点
  12. source.getParentNode().replaceChild(toInclude, source);
  13. while (toInclude.hasChildNodes()) {
  14. // 将<sql>节点的子节点添加到<sql>节点的前面
  15. toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
  16. }
  17. // 替换后删除<sql>节点
  18. toInclude.getParentNode().removeChild(toInclude);
  19. } else if (source.getNodeType() == Node.ELEMENT_NODE) {// ---(1)
  20. if (included && !variablesContext.isEmpty()) {
  21. // replace variables in attribute values
  22. NamedNodeMap attributes = source.getAttributes();
  23. for (int i = 0; i < attributes.getLength(); i++) {// 遍历当前sql的子节点
  24. Node attr = attributes.item(i);
  25. attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
  26. }
  27. }
  28. NodeList children = source.getChildNodes();
  29. for (int i = 0; i < children.getLength(); i++) {
  30. applyIncludes(children.item(i), variablesContext, included);
  31. }
  32. } else if (included && source.getNodeType() == Node.TEXT_NODE
  33. && !variablesContext.isEmpty()) {// ---(3)
  34. // replace variables in text node 替换对应的占位符
  35. source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
  36. }
  37. }

3、解析selectKey节点

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

  1. private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
  2. String resultType = nodeToHandle.getStringAttribute("resultType");
  3. Class<?> resultTypeClass = resolveClass(resultType);
  4. StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  5. String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
  6. String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
  7. boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
  8. //defaults
  9. boolean useCache = false;
  10. boolean resultOrdered = false;
  11. KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
  12. Integer fetchSize = null;
  13. Integer timeout = null;
  14. boolean flushCache = false;
  15. String parameterMap = null;
  16. String resultMap = null;
  17. ResultSetType resultSetTypeEnum = null;
  18. // 生成SqlSource
  19. SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
  20. // selectKey节点中只能配置select语句
  21. SqlCommandType sqlCommandType = SqlCommandType.SELECT;
  22. // 创建MappedStatement对象,并添加到configuration的mappedStatements集合中保存
  23. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  24. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  25. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  26. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);
  27. id = builderAssistant.applyCurrentNamespace(id, false);
  28. MappedStatement keyStatement = configuration.getMappedStatement(id, false);
  29. // 创建对应的KeyGenerator(主键自增策略),添加到configuration中
  30. configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
  31. }

三、绑定Mapper接口

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

  1. // 绑定mapper接口
  2. private void bindMapperForNamespace() {
  3. //获取映射文件的命名空间
  4. String namespace = builderAssistant.getCurrentNamespace();
  5. if (namespace != null) {
  6. Class<?> boundType = null;
  7. try {
  8. // 解析命名空间对应的类型 即dao
  9. boundType = Resources.classForName(namespace);
  10. } catch (ClassNotFoundException e) {
  11. //ignore, bound type is not required
  12. }
  13. if (boundType != null) {
  14. if (!configuration.hasMapper(boundType)) {// 是否已经加载了
  15. // Spring may not know the real resource name so we set a flag
  16. // to prevent loading again this resource from the mapper interface
  17. // look at MapperAnnotationBuilder#loadXmlResource
  18. //注册
  19. configuration.addLoadedResource("namespace:" + namespace);
  20. configuration.addMapper(boundType);
  21. }
  22. }
  23. }
  24. }

发表评论

表情:
评论列表 (有 0 条评论,23人围观)

还没有评论,来说两句吧...

相关阅读