Mybatis的工作流程及原理

Mybatis是在Java实际开发中用到的最多的ORM框架了。我们只需要引入Mybatis依赖,配置好mybatis-config.xml文件,写一个接口dao层,再写一个对应的xml文件,就可以了。上手真的很简单方便,相比于hibernate而言,学习成本要低很多。

既然是用的最多的,那么对于它的相关工作流程以及原理我们肯定是需要掌握的。
在这里插入图片描述

@Test
    public void MybatisTest() throws IOException {
        String resource = "mybatis-config.xml";
        //获取mybatis配置文件的IO流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        DailyReportDao mapper = sqlSession.getMapper(DailyReportDao.class);

        mapper.findList(null);
    }

在这里插入图片描述

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    //创建出一个解析对象,重点看parser.parse()方法
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //mybatis-config.xml总是从<configuratin> . . .</configuration>开始的
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

在这里插入图片描述

private void parseConfiguration(XNode root) {
    try {
      //将mybatis-config.xml中的各个配置set到Configuration对象实例中去
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //看这里,mappers是我们配置的包扫描路径
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

在这里插入图片描述

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

在这里插入图片描述

<mappers>
 <mapper url="file:///var/mappers/AuthorMapper.xml"/>
 <mapper url="file:///var/mappers/BlogMapper.xml"/>
 <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers> 

在这里插入图片描述

<mappers>
 <mapper class="org.mybatis.builder.AuthorMapper"/>
 <mapper class="org.mybatis.builder.BlogMapper"/>
 <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

在这里插入图片描述

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

在这里插入图片描述

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
    //遍历mappers标签下的所有子标签
      for (XNode child : parent.getChildren()) {
      //如果是package,说明是配置的包扫描路径
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //调用addMappers()方法,下面再说这个方法
          configuration.addMappers(mapperPackage);
        } else {
          //其他三种方法都是直接告诉了mybatis应该去哪里去取
          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.");
          }
        }
      }
    }
  }

在这里插入图片描述

public <T> void addMapper(Class<T> type) {
	//先判断dao是不是接口,不是接口不会去进行处理
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
      //将type放入map中,这里发现实际的value类型是MapperProxyFactory,后面getMapper的时候再来看为什么?
        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.
        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)) {
    //顾名思义,加载xml资源文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
          //这里是获取注解式的SQL,很贴心
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

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
    //老规矩,先判断是不是已经加载过了
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    //获取xml的路,所以还是要求dao层的名称和mapper.xml的名称一样,直接替换后缀
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
      	//和加载mybatis-config.xml一样的套路,我们不关心是怎么把xml文件流转化为XMLMapperBuilder 解析对象的哈
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //还是深入parse()方法看一下
        xmlParser.parse();
      }
    }
  }
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //解析出mapper.xml中的各个标签,并且将一个mapper.xml最终转化为了一个MappedStatement对象
      configurationElement(parser.evalNode("/mapper"));
      //已经加载过这个dao,做标记
      configuration.addLoadedResource(resource);
      //这个方法看起来像是为了整合Spring弄出来的一个,会重新走一遍addMapper()方法
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

//这个方法会解析出mapper.xml里的所有的标签以及相应的sql语句,至于到底是怎么解析的,后面有时间可以去深入研究一下
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);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
//构建一个MappedStatement对象
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);
    }
  }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //首先判断执行的方法的类型
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

在这里插入图片描述
转载:https://blog.csdn.net/weixin_41392674/article/details/120890084

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值