从源码角度分析:为什么MyBatis的Mapper接口方法不能重载?

1:我们从MyBatis配置开始解析

解析入口在 SqlSessionFactoryBean.class中

2:开始解析

public void parse() {
    //判断该配置文件(编写Sql的xml文件)是否已经被解析过,解析过的配置文件会被放到Set集合当中
    if (!configuration.isResourceLoaded(resource)) {
      //开始解析(编写Sql的xml文件)mapper节点,我们进入configurationElement方法
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

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

3:进入configurationElement方法

//这里就开始解析mapper节点下的各种属性信息了,我们重点看下解析select|insert|update|delete等元素信息
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        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);
    }
  }
  

4:进入buildStatementFromContext()方法

 private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
  

5: 继续进入下面buildStatementFromContext(list, null);

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //进入这个方法
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }
  

6:进入statementParser.parseStatementNode();

public void parseStatementNode() {
    /**
    * 中间省略很多代码,但是重点是下面这行代码
   */
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

7:我们继续跟进builderAssistant.addMappedStatement方法

    //这里我截取了一段代码,重点是MapperStatement这个对象,可以说我们执行一条Sql语句的相关信息都在这个对象里面了
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

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

8:查看MapperStatement对象

  • MapperStatement有几个很重要的属性
    • id: 在我们执行Mapper接口一个方法的时候,需要依靠这个id找到对应的MapperStatement对象,然后拿到其中的Sql语句
    • SqlSource:这里面就封装了我们在xml配置文件中编写的SQL语句,大家在图中就可以看到了

9: MapperStatement对象总结

  • 现在可以对MapperStatement做个总结了:MyBatis在解析我们编写的xml配置的时候,当我们解析一条Sql语句的时候,那么就会把对应的id,sql语句都封装到MapperStatement这个对象中

10:MapperStatement中id的组成

  • id = namespace + id
  • 所以我们在执行Mapper接口的时候,会通过Mapper接口的 全路径名+方法名组成的id去寻找对应的MapperStatement对象,就是通过对比id来进行比较的

11:现在继续第7步

  • 在生成了MapperStatement对象之后,MyBatis会把这个对象存储到一个Map中,而key值就是MapperStatement对象中的id

12:总结

  • 所以如果我们在Mapper接口中有同样名称的方法,那么在生成MapperStatement对象的时候,id就会重复,也就意味着之前存储的MapperStatement对象就会被覆盖掉
  • map.put("com.coco.mapper.UserDao.query",MapperStatement1);
  • map.put("com.coco.mapper.UserDao.query",MapperStatement2);
  • 那么MapperStatement1就会被覆盖掉,那么会出现什么问题呢?
  • 比如Mapper接口方法如下
    • User query(Integer id);
    • 所对应的sql = select * from user where id = #{id}
    • User query(String name);
    • 所对应的sql = select * from user where name = #{name}
  • 因为先解析的MapperStatement对象被后面的覆盖掉了,比如MyBatis先解析了第一个方法,然后再解析第二个方法,那么第一个方法被覆盖掉了,当我们执行第一个方法的时候,通过id就会找到第二个方法的MapperStatement对象了,那么Sql语句就错了

13:结束语

  • 现在大家能理解为什么Mapper接口方法不能重载的原理了嘛
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值