Mybatis的Mapper代理对象生成及调用过程示例详解

 更新时间:2023年09月14日 09:00:55   作者:福  
这篇文章主要为大家介绍了Mybatis的Mapper代理对象生成及调用过程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

导读

你在mapper.xml文件中写的sql语句最终是怎么被执行的?我们编写的mapper接口最终是怎么生成代理对象并被调用执行的?

这部分内容应该是Mybatis框架中最关键、也是最复杂的部分,今天文章的主要目标是要搞清楚:

  • mapper.xml文件是怎么初始化到Mybatis框架中的?
  • mapper接口生成动态代理对象的过程。
  • mapper接口动态代理对象的执行过程。

掌握了以上3个问题,我们就掌握了Mybatis的核心。

Mapper初始化过程

指的是mapper.xml文件的解析过程。

这个动作是在SqlSessionFactory创建的过程中同步完成的,或者说是在SqlSessionFactory被build出来之前完成。

XMLMapperBuilder负责对mapper.xml文件做解析,SqlSessionFactorBean的buildSqlSessionFactory()方法中会针对不同配置情况进行解析。其中我们最常用的是在配置文件中指定mapper.xml文件的路径(就是源码中的这个mapperLocations):

if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);

创建XMLMapperBuilder对象并调用parse()方法完成解析。

XMLMapperBuilder#configurationElement()

parse方法会调用configurationElement()方法,对mapper.xml的解析的关键部分就在configurationElement方法中。

我们今天把问题聚焦在mapper.xml文件中sql语句的解析,也就是其中的insert、update、delete、select等标签的解析。

private void configurationElement(XNode context) {
    try {
      //获取namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
    //二级缓存Ref解析
      cacheRefElement(context.evalNode("cache-ref"));
   //二级缓存配置的解析
      cacheElement(context.evalNode("cache"));
  //parameterMap标签的解析 
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
   //resultMap标签的解析 
resultMapElements(context.evalNodes("/mapper/resultMap"));
   //sql标签的解析 
sqlElement(context.evalNodes("/mapper/sql"));
    //关键部分: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);
    }
  }

对sql语句解析部分继续跟踪会发现,最终sql语句解析完成之后会创建MappedStatement并保存在configuration对象中(以xml文件中的id为key值的Map中):

MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;

这样我们就明白了,mapper.xml中编写的sql语句被解析后,最终保存在configuration对象的mappedStatements中了,mappedStatements其实是一个以mapper文件中相关标签的id值为key值的hashMap。

Mapper接口生成动态代理过程

我们都知道Mapper对象是通过SqlSession的getMapper方法获取到的,其实Mapper接口的代理对象也就是在这个调用过程中生成的:

@Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

调用Configuration的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

调用mapperRegistry的getMapper方法,首先从knownMappers(以namespace为key值保存mapperProxyFactory的HashMap)中获取到mapperProxyFactory,mapperProxyFactory人如其名,就是mapper代理对象工厂,负责创建mapper代理对象。

获取到mapperProxyFactory之后,调用newInstance方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
    }
  }

调用MapperProxy的newInstance方法:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

典型的JDK动态代理生成逻辑,传入的回调参数为mapperProxy对象,也就是说我们对Mapper接口的方法调用,最终通过生成的动态代理对象,会调用到这个回调对象mapperProxy的invoke方法。

至此,mapper接口动态代理的生成逻辑我们就从源码的角度分析完毕,现在我们已经搞清楚mapper动态代理的生成过程,最重要的是,我们知道mapper接口的方法调用最终会转换为对mapperProxy的invoke方法的调用。

mapper接口动态代理对象的执行过程

这个问题现在已经明确了,其实就是MapperProxy的invoke方法。

对MapperProxy稍加研究,我们发现他的invoke方法最终会调用MapperMethod的execute方法:

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

execute方法根据调用方法的sql类型分别调用sqlSession的insert、update、delete、select方法,相应的方法会根据调用方法名从configuration中匹配MappedStatement从而执行我们在mapper.xml中配置的sql语句(参考XMLMapperBuilder#configurationElement()部分)。

因此我们也就明白了为什么mapper.xml文件中配置的sql语句的id必须要对应mapper接口中的方法名,因为Mybatis要通过mapper接口中的方法名去匹配sql语句、从而最终执行该sql语句!

任务完成!

其实虽然我们知道SqlSession默认的落地实现对象是DefaultSqlSession,我们在mapper.xml中编写的sql语句其实是DefaultSqlSession负责执行的,但是MapperMethod中sqlSession其实也是代理对象(DefaultSqlSession的代理对象),所以说Mybatis中到处都是动态代理,这部分我们下次再分析。

以上就是Mybatis的Mapper代理对象生成及调用过程示例详解的详细内容,更多关于Mybatis Mapper代理对象生成调用的资料请关注脚本之家其它相关文章!

相关文章

  • java高级用法之注解和反射讲义

    java高级用法之注解和反射讲义

    这篇文章主要给大家介绍了关于java高级用法之注解和反射讲义的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • springboot创建文件夹失败的解决

    springboot创建文件夹失败的解决

    这篇文章主要介绍了springboot创建文件夹失败的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 最新IntelliJ IDEA 2021版配置 Tomcat 8.5 的详细步骤

    最新IntelliJ IDEA 2021版配置 Tomcat 8.5 的详细步骤

    idea开发工具一直是java环境最好用,很受广大开发者喜爱,今天通过本文给大家分享最新IntelliJ IDEA 2021版配置 Tomcat 8.5 的详细步骤,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友可以参考下
    2021-06-06
  • Java中使用阻塞队列控制线程集实例

    Java中使用阻塞队列控制线程集实例

    这篇文章主要介绍了Java控制阻塞队列线程集实例,本文用一个程序展示了如何使用阻塞队列来控制线程集,程序功能是在一个目录及它的所有子目录下搜索所有文件,打印出包含指定关键字的文件列表,需要的朋友可以参考下
    2015-01-01
  • 29个要点帮你完成java代码优化

    29个要点帮你完成java代码优化

    本文给大家分享的是个人总结的29个java优化需要注意的地方,非常的全面细致,推荐给大家,有需要的小伙伴可以参考下
    2015-03-03
  • 基于Spring AI+Milvus的RAG混合检索的实战指南

    基于Spring AI+Milvus的RAG混合检索的实战指南

    这篇文章主要给大家记录了从零搭建企业级 RAG 知识库问答系统的工程,涵盖意图路由、混合检索、RRF 融合、query 改写、rerank 精排全链路,并通过代码示例讲解的非常详细,需要的朋友可以参考下
    2026-06-06
  • java反射实现javabean转json实例代码

    java反射实现javabean转json实例代码

    基于java反射机制实现javabean转json字符串实例,大家参考使用吧
    2013-12-12
  • Spring @Cacheable自定义缓存过期时间的实现示例

    Spring @Cacheable自定义缓存过期时间的实现示例

    本文主要介绍了Spring @Cacheable自定义缓存过期时间的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-05-05
  • 最新hadoop安装教程及hadoop的命令使用(亲测可用)

    最新hadoop安装教程及hadoop的命令使用(亲测可用)

    这篇文章主要介绍了最新hadoop安装教程(亲测可用),本文主要讲解了如何安装hadoop、使用hadoop的命令及遇到的问题解决,需要的朋友可以参考下
    2022-06-06
  • MybatisPlus中静态工具DB的实现

    MybatisPlus中静态工具DB的实现

    本文主要介绍了使用MybatisPlus的Db静态工具类来避免Service之间的循环依赖问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-12-12

最新评论