前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis源码解析 - mapper代理对象的生成,你有想过吗

Mybatis源码解析 - mapper代理对象的生成,你有想过吗

作者头像
青石路
发布2019-03-15 11:28:59
9740
发布2019-03-15 11:28:59
举报
文章被收录于专栏:开发技术开发技术

前言

  开心一刻

    本人幼教老师,冬天戴帽子进教室,被小朋友看到,这时候,有个小家伙对我说:老师你的帽子太丑,赶紧摘了吧。我逗他:那你好好学习,以后给老师买个漂亮的?这孩子想都没想立刻回答:等我赚钱了,带你去韩国整形

简单示例

  我们先来看一个纯粹的mybatis示例(不集成spring等其他框架),代码很简单,结构如下

  完整代码地址:mybatis;mapper层和我们平时说的dao层指的是同一个内容,都是数据库操作的封装,但是在没有集成mybatis时,dao层的接口都是需要我们手动去写其实现类,可在上图中我们却发现:我们并没有手动去实现PersonMapper接口,但工程却能实实在在的查询数据库,获取我们需要的数据,如下图所示

  从上图我们发现,PersonMapper实例是一个代理对象,我们操作的其实是PersonMapper的代理实现;也就是说不用我们手动去实现PersonMapper接口,mybatis会动态生成PersonMapper的代理实例,然后由代理实例完成数据库的操作

  那么问题来了,mybatis是何时、何地、如何生成mapper代理实例的呢?我们接着往下看

源码分析

  针对上述问题,我们来跟下mybatis源码

  SqlSessionFactory的创建

    XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),将配置文件中各个属性解析到Configuration实例中,然后以Configuration实例构建SqlSessionFactory(实际是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具体过程,有兴趣的可以更深一步的去探究

代码语言:javascript
复制
/**
 * root是以configuration标签开始的文档树
 * 解析配置文件中的各个标签,并存放到Configuration实例对应的属性中
 * 解析完成之后,配置文件中的内容全部解析到了Configuration实例中
 * @param root
 */
private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));                                // 解析配置文件中的properties标签
        Properties settings = settingsAsProperties(root.evalNode("settings"));        // 解析配置文件中的settings标签
        loadCustomVfs(settings);
        typeAliasesElement(root.evalNode("typeAliases"));                            // 解析配置文件中的typeAliases标签
        pluginElement(root.evalNode("plugins"));                                    // 解析配置文件中的plugins标签
        objectFactoryElement(root.evalNode("objectFactory"));                        // 解析配置文件中的objectFactory标签
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));            // 解析配置文件中的objectWrapperFactory标签
        reflectorFactoryElement(root.evalNode("reflectorFactory"));                    // 解析配置文件中的reflectorFactory标签
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631            
        environmentsElement(root.evalNode("environments"));                            // 解析配置文件中的environments标签
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));                // 解析配置文件中的databaseIdProvider标签
        typeHandlerElement(root.evalNode("typeHandlers"));                            // 解析配置文件中的typeHandlers标签
        mapperElement(root.evalNode("mappers"));                                    // 解析配置文件中的mappers标签
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

    上述代码中的mapperElement(root.evalNode("mappers"));是不是很诱人?与我们的mapper有关系,是不是在这里就生成了mapper的代理实例,还是只是读取了mapper配置文件的内容?暂时还不敢肯定,那么我们跟进去看看

    其中有两个方法值得重点关注下,具体如下,里面的注释可以重点看下,有兴趣的可以更进一步的跟进去

代码语言:javascript
复制
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));        // 解析映射文件Person.xml
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();    // 将mapper与namespace绑定起来; 将PersonMapper接口与MapperProxyFactory关联起来
    }

    parsePendingResultMaps();    // 解析Configuration的incompleteResultMaps到Configuration的resultMaps
    parsePendingCacheRefs();    // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap
    parsePendingStatements();    // 解析Configuration的incompleteStatements到Configuration的mappedStatements
}

/**
 * context是映射文件:Person.xml的文档树,以mapper标签开始
 * 解析映射文件中的各个标签,并存放到MapperBuilderAssistant实例对应的属性中
 */
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");            // 解析mapper标签的namespace属性
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);    // namespace属性值解析到Configuration的mapperRegistry中
      cacheRefElement(context.evalNode("cache-ref"));    // 解析cache-ref标签到Configuration的cacheRefMap中
      cacheElement(context.evalNode("cache"));            // 解析cache标签到Configuration的caches中
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));    // 解析parameterMap标签到Configuration的parameterMaps中
      resultMapElements(context.evalNodes("/mapper/resultMap"));        // 解析resultMap标签到Configuration的resultMaps中
      sqlElement(context.evalNodes("/mapper/sql"));                        // 解析sql标签到XMLMapperBuilder的sqlFragments中
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    // 解析select|insert|update|delete标签到Configuration的mappedStatements中
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

    此时SqlSessionFactory已经创建,但PersonMapper的代理实例还没有创建;期间准备了很多东西,包括读取配置文件和映射文件的内容,并将其放置到Configuration实例的对应属性中

  SqlSession的创建

    实例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此时mapper代理实例仍未被创建

  Mapper代理对象的创建

    可以看到,最终还是利用了JDK的动态代理

代码语言:javascript
复制
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 利用JDK的动态代理生成mapper的代理实例
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

    生成了mapper的代理实例,后续就可以利用此代理实例进行数据库的操作了

总结

  1、我们用mytabis操作数据库,有一个固定流程:先创建SqlSessionFactory,然后创建SqlSession,然后再创建获取mapper代理对象,最后利用mapper代理对象完成数据库的操作;一次数据库操作完成后需要关闭SqlSession;

  2、创建SqlSessionFactory实例的过程中,解析mybatis配置文件和映射文件,将内容都存放到Configuration实例的对应属性中;创建SqlSession的过程中,有创建事务Transaction、执行器Executor,以及DefaultSqlSession;Mapper代理对象的创建,利用的是JDK的动态代理,InvocationHandler是MapperProxy,后续Mapper代理对象方法的执行都会先经过MapperProxy的invoke方法;

  3、很多细节没有讲到,但大体流程就是这样;另外提下,实际应用中,mybatis往往不会单独使用,绝大多数都是集成在spring中;关于在spring的集成下,mapper代理对象的创建过程请期待我的下篇博文

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-03-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 简单示例
  • 源码分析
    •   SqlSessionFactory的创建
      •   SqlSession的创建
        •   Mapper代理对象的创建
        • 总结
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档