资讯专栏INFORMATION COLUMN

Mybatis是怎么工作的(二)

paulli3 / 2733人阅读

摘要:目标理清加载解析文件的过程理清执行的过程。先看源码生成解析配置文件考虑到项目的配置,看下生成和的代码。在生成的过程中,使用了,这个类继承了。在该类的构造器中加载了和。下面看一下代码从缓存中获取对象执行下面是方法至此,执行完成。

目标:

理清mybatis加载解析mapper文件的过程;

理清mybatis执行SQL的过程。

上一篇文章分析mybatis加载配置的源码时提到了org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration方法,现在继续分析其中的mapperElement方法。先看源码:

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          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
            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.");
          }
        }
      }
    }
  }

考虑到项目的配置,看下生成XMLMapperBuilder和mapperParser.parse()的代码。
在生成XMLMapperBuilder的过程中,使用了MapperBuilderAssistant,这个类继承了BaseBuilder。在该类的构造器中加载了TypeAliasRegistry和TypeHandlerRegistry。
下面重点看mapperParserparse()

  public void parse() {
    // 判断是否已经加载资源
    if (!configuration.isResourceLoaded(resource)) {
      // 配置/mapper节点下的子节点
      configurationElement(parser.evalNode("/mapper"));
      // 加载resource资源
      configuration.addLoadedResource(resource);
      // 绑定命名空间
      bindMapperForNamespace();
    }
    // 加载未加载完成的资源
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

下面主要看下configurationElement的代码:

  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"));
      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);
    }
  }

可以看出主要是解析不同的节点,并放进builderAssistant里面去。
下面看下执行SQL的过程。

            ClipsDAO clipsDAO = session.getMapper(ClipsDAO.class);
            ClipsEntity clipsEntity = clipsDAO.selectById(1);

查看session.getMapper()的实现:

  // org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
  @Override
  public  T getMapper(Class type) {
    return configuration.getMapper(type, this);
  }
  
  // org.apache.ibatis.session.Configuration#getMapper
    public  T getMapper(Class type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  
  // org.apache.ibatis.binding.MapperRegistry#getMapper
    public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  

可以看出,mybatis通过动态代理为接口生成了代理类,我们知道在加载配置时,bindMapperForNamespace方法调用了configuration.addMapper()方法把Class映射到org.apache.ibatis.binding.MapperRegistry#knownMappers中去的。
下面看一下MapperProxy代码:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 从缓存中获取MapperMethod对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行SQL
    return mapperMethod.execute(sqlSession, args);
  }

下面是MapperMethod.execute()方法:

  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;
  }

至此,SQL执行完成。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72929.html

相关文章

  • 手撕面试官系列():开源框架面试题Spring+SpringMVC+MyBatis

    摘要:跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来期间也没有准备充分,到底是因为技术原因影响自己的发展,偏移自己规划的轨迹,还是钱给少了,不受重视。 跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技...

    Flink_China 评论0 收藏0
  • Java面试前需要了解东西

    摘要:我在面试前针对基础也花了不少的时间,期间也将自己写过的博文粗略地刷了一遍,同时也在网上找了不少比较好的资料部分是没看完的。看面试题也是校验自己是否真正理解了这个知识点,也很有可能会有新的收获。 一、前言 只有光头才能变强 回顾前面: 广州三本找Java实习经历 上一篇写了自己面试的经历和一些在面试的时候遇到的题目(笔试题和面试题)。 我在面试前针对Java基础也花了不少的时间,期间也将...

    renweihub 评论0 收藏0
  • Mybatis常见面试题

    摘要:执行没有,批处理不支持,将所有都添加到批处理中,等待统一执行,它缓存了多个对象,每个对象都是完毕后,等待逐一执行批处理。 Mybatis常见面试题 #{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是预编译处理,${}是字符串替换。 使用#{}可以有效的防止...

    liuchengxu 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<