Spring mybatis源码篇章-MybatisDAO文件解析

谁践踏了优雅 2021-06-24 15:57 555阅读 0赞

http://www.cnblogs.com/question-sky/p/6612604.html

默认加载mybatis主文件方式

  1. XMLConfigBuilder xmlConfigBuilder = null;
  2. if (this.configLocation != null) {
  3. xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
  4. configuration = xmlConfigBuilder.getConfiguration();
  5. } else {
  6. if (this.logger.isDebugEnabled()) {
  7. this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
  8. }
  9. //sqlSessionFactoryBean不指定configLocation属性则采用默认的Configuration对象
  10. configuration = new Configuration();
  11. configuration.setVariables(this.configurationProperties);
  12. }

简单的看下Configuration的无参数构造函数

  1. public Configuration() {
  2. //预存常用的别名
  3. typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  4. typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
  5. typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  6. typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  7. typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
  8. typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  9. typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  10. typeAliasRegistry.registerAlias("LRU", LruCache.class);
  11. typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  12. typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
  13. typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
  14. typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  15. typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
  16. typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  17. typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  18. typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  19. typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  20. typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  21. typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  22. typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
  23. typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  24. typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
  25. languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  26. languageRegistry.register(RawLanguageDriver.class);
  27. }

简单的发现其并不去设置前一章节的mybatis主文件中的相关属性,比如settings、environment等

mapper sql 配置文件的加载

前提是sqlSessionFactoryBean设置了mapperLocations属性,比如<property name="mapperLocations" value="classpath:com/du/wxServer/mapper/*.xml" />

  1. 查看sqlSessionFactoryBean#buildSqlSessionFactory()方法

    1. if (!isEmpty(this.mapperLocations)) {
    2. //具体的如何从string转为Resource[],暂且不知何处加载获得,有兴趣的读者可补充
    3. for (Resource mapperLocation : this.mapperLocations) {
    4. if (mapperLocation == null) {
    5. continue;
    6. }
    7. try {
    8. //对扫描包及其子包下的每个sql mapper配置文件进行解析
    9. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
    10. configuration, mapperLocation.toString(), configuration.getSqlFragments());
    11. xmlMapperBuilder.parse();
    12. } catch (Exception e) {
    13. throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
    14. } finally {
    15. ErrorContext.instance().reset();
    16. }
    17. if (this.logger.isDebugEnabled()) {
    18. this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
    19. }
    20. }
    21. } else {
    22. if (this.logger.isDebugEnabled()) {
    23. this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
    24. }
    25. }
  2. 直接进入XMLMapperBuilder#parse方法

    1. public void parse() {
    2. //对每个xml资源只加载一次
    3. if (!configuration.isResourceLoaded(resource)) {
    4. //解析xml配置,其中配置的根节点必须为mapper
    5. configurationElement(parser.evalNode("/mapper"));
    6. configuration.addLoadedResource(resource);
    7. //绑定mapper的工作区间
    8. bindMapperForNamespace();
    9. }
    10. parsePendingResultMaps();
    11. parsePendingChacheRefs();
    12. parsePendingStatements();
    13. }
  3. 分析XMLMapperBuilder#configurationElement方法

    1. try {
    2. //表明mapper根节点的namespace属性是必须的,且不为空
    3. String namespace = context.getStringAttribute("namespace");
    4. if (namespace.equals("")) {
    5. throw new BuilderException("Mapper's namespace cannot be empty");
    6. }
    7. //设置工作区间
    8. builderAssistant.setCurrentNamespace(namespace);
    9. //解析相应的属性
    10. cacheRefElement(context.evalNode("cache-ref"));
    11. cacheElement(context.evalNode("cache"));
    12. //解析<parameterMap>节点集合
    13. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    14. //解析<resultMap>节点集合
    15. resultMapElements(context.evalNodes("/mapper/resultMap"));
    16. //解析<sql>节点集合
    17. sqlElement(context.evalNodes("/mapper/sql"));
    18. //创建MappedStatement,这里与注解方式的加载方式还是类似的
    19. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    20. } catch (Exception e) {
    21. throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    22. }

    这里讨论下与前一章节不同的解析同一属性配置方法:

    • XMLMapperBuilder#resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings)解析单个<resultMap>节点方法

      1. ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
      2. //读取id属性,最好配置以免不必要的错误
      3. String id = resultMapNode.getStringAttribute("id",
      4. resultMapNode.getValueBasedIdentifier());
      5. //优先级为type>ofType>resultType>javaType
      6. String type = resultMapNode.getStringAttribute("type",
      7. resultMapNode.getStringAttribute("ofType",
      8. resultMapNode.getStringAttribute("resultType",
      9. resultMapNode.getStringAttribute("javaType"))));
      10. String extend = resultMapNode.getStringAttribute("extends");
      11. //是否开启自动映射,默认值为unset
      12. Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
      13. Class<?> typeClass = resolveClass(type);
      14. //<discriminator><case /><case/></discriminator>根据结果值进行结果类型的映射,类似java的switch-case语法
      15. Discriminator discriminator = null;
      16. //ResultMap节点信息转化为ResultMapping集合
      17. List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
      18. resultMappings.addAll(additionalResultMappings);
      19. List<XNode> resultChildren = resultMapNode.getChildren();
      20. for (XNode resultChild : resultChildren) {
      21. if ("constructor".equals(resultChild.getName())) {
      22. //<resultMap>节点下<constructor>节点处理
      23. processConstructorElement(resultChild, typeClass, resultMappings);
      24. } else if ("discriminator".equals(resultChild.getName())) {
      25. //<resultMap>节点下<discriminator>节点处理
      26. discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      27. } else {
      28. //<id>/<result>/<collection>/<association>节点的解析
      29. ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
      30. if ("id".equals(resultChild.getName())) {
      31. flags.add(ResultFlag.ID);
      32. }
      33. resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      34. }
      35. }
      36. ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
      37. try {
      38. //组装成ResultMap对象保存到Configuration对象的私有集合变量resultMaps
      39. return resultMapResolver.resolve();
      40. } catch (IncompleteElementException e) {
      41. configuration.addIncompleteResultMap(resultMapResolver);
      42. throw e;
      43. }
    • XMLMapperBuilder#sqlElement(List<XNode list>)sql节点信息的解析,主要作用是将每个sql节点对象都保存到Configuration对象中的Map<String, XNode> sqlFragments属性中。

      1. for (XNode context : list) {
      2. //sql节点的databaseId属性
      3. String databaseId = context.getStringAttribute("databaseId");
      4. //sql节点的id属性
      5. String id = context.getStringAttribute("id");
      6. //id=${namespace}+"."+id
      7. id = builderAssistant.applyCurrentNamespace(id, false);
      8. //true的前提是
      9. //主配置文件指定了databaseId属性
      10. //或者主配置和sql节点的databaseId属性均不存在,但sql节点的id属性存在
      11. if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      12. sqlFragments.put(id, context);
      13. }
      14. }
    • XMLMapperBuilder#buildStatementFromContext(context.evalNodes("select|insert|update|delete"))CRUD语句节点解析,
      这里直接看 XMLStatementBuilder#parseStatementNode()方法的部分代码

      1. //节点上支持的常见属性
      2. Integer fetchSize = context.getIntAttribute("fetchSize");
      3. Integer timeout = context.getIntAttribute("timeout");
      4. String parameterMap = context.getStringAttribute("parameterMap");
      5. String parameterType = context.getStringAttribute("parameterType");
      6. Class<?> parameterTypeClass = resolveClass(parameterType);
      7. String resultMap = context.getStringAttribute("resultMap");
      8. String resultType = context.getStringAttribute("resultType");
      9. String lang = context.getStringAttribute("lang");
      10. LanguageDriver langDriver = getLanguageDriver(lang);
      11. ...
      12. String resultSetType = context.getStringAttribute("resultSetType");
      13. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
      14. ...
      15. // Include Fragments before parsing 导入<include>标签内容,其内部可以含有<if>/<where>/<set>等标签
      16. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
      17. includeParser.applyIncludes(context.getNode());
  1. // Parse selectKey after includes and remove them.导入<selectKey>标签内容
  2. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  3. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 如何解析sql语句?放置下一章节讲解
  4. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  5. ...
  6. //这里就跟上一章节的注解生成MappedStatement是一致的,最终都是保存在Configuration的Map<String, MappedStatement> mappedStatement集合属性
  7. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  8. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  9. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  10. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

总结

  • 不管是通过注解模式还是配置文件模式,都会生成MappedStatement对象保存到Configuration对象中
  • 注解模式可以很好的直接通过上一章节的讲解模式来达到sql语句与类直接绑定;但本章的sql 配置文件并没有讲到如何绑定对应namespace指向的class对象。这在MapperScannerConfigurer源码分析中讲解
  • 每个select|update|insert|delete标签均会被解析为单个MappedStatement对象,其中的id为namespace_id作为唯一标志

下节预告

Spring mybatis源码篇章-XMLLanguageDriver解析sql包装为SqlSource

发表评论

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

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

相关阅读

    相关 MyBatis框架

    MyBatis是一个优秀的持久层框架,通过简洁的XML配置方式就能消除以前传统 使用JDBC出现大量重复的代码,以及参数的设置和结果集的映射. 如果能够懂的底层代码和原理,

    相关 spring

    SpringMVC作为Struts2之后异军突起的一个表现层框架,正越来越流行,相信javaee的开发者们就算没使用过SpringMVC,也应该对其略有耳闻。我试图通过对Spr