接下来我们就编码测试下。

1.首先定义一个Mapper:

/*** @author: 君战* @since: 2021-05-10 **/
public interface OrgRealNameMapper {@Select("SELECT * FROM xxx WHERE id = #{id}")OrgRealNameDO selectByPrimaryKey(Integer id);@Select("SELECT * FROM xxx WHERE id = #{id} AND name = #{name}")OrgRealNameDO selectByPrimaryKey(Integer id,String name);}

然后启动(如何启动MyBatis,请查看官方文档,描述地很详细),查看控制台:

Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException:
### Error building SqlSession.
### The error may exist in com/xxx/spring/dao/OrgRealNameMapper.java (best guess)
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.xxx.spring.dao.OrgRealNameMapper.selectByPrimaryKey. please check com/xxx/spring/dao/OrgRealNameMapper.java (best guess) and com/xxx/spring/dao/OrgRealNameMapper.java (best guess)at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80)at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:64)at com.xxx.spring.Application.startApp(Application.java:88)at com.xxx.spring.Application.main(Application.java:49)
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.xxx.spring.dao.OrgRealNameMapper.selectByPrimaryKey. please check com/xxx/spring/dao/OrgRealNameMapper.java (best guess) and com/xxx/spring/dao/OrgRealNameMapper.java (best guess)at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:122)at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:99)at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:78)... 3 more
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.xxx.spring.dao.OrgRealNameMapper.selectByPrimaryKey. please check com/xxx/spring/dao/OrgRealNameMapper.java (best guess) and com/xxx/spring/dao/OrgRealNameMapper.java (best guess)at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:992)at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:948)at org.apache.ibatis.session.Configuration.addMappedStatement(Configuration.java:746)at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:297)at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.lambda$parseStatement$2(MapperAnnotationBuilder.java:358)at java.util.Optional.ifPresent(Optional.java:159)at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parseStatement(MapperAnnotationBuilder.java:300)at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse(MapperAnnotationBuilder.java:132)at org.apache.ibatis.binding.MapperRegistry.addMapper(MapperRegistry.java:72)at org.apache.ibatis.binding.MapperRegistry.addMappers(MapperRegistry.java:106)at org.apache.ibatis.binding.MapperRegistry.addMappers(MapperRegistry.java:118)at org.apache.ibatis.session.Configuration.addMappers(Configuration.java:815)at org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XMLConfigBuilder.java:367)at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:120)... 5 more

这个异常是由谁抛出的呢?我们可以通过异常堆栈找到答案:
Configuration类的第992行。这个是MyBatis自己实现的Map-StrictMap,其继承于HashMap,并重写了相关的get和put方法。与JDK提供的Map行为不同,MyBatis自己实现的StrictMap,在进行put操作的时候,如果发现已经哈希表中已经存在相同的key就抛出异常。

那么这个StrictMap是用来存什么呢?为什么使用到了它呢?
要回答这个问题,就要追溯到SqlSessionFactoryBuilder的build方法。

// org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}

在该build方法中,调用了重载的build方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 根据输入流以及environment和属性文件构建XMLConfigBuilder XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());// 重点是这里调用的XMLConfigBuilder的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.}}
}

在XMLConfigurationBuilder的parse方法中完成了MyBatis配置文件的解析。

// org.apache.ibatis.builder.xml.XMLConfigBuilder#parsepublic Configuration parse() {// 如果已经解析过,抛出异常。if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;// 重点!!!解析MyBatis配置文件的根节点<configuration>parseConfiguration(parser.evalNode("/configuration"));return configuration;}

在XMLConfigBuilder的parseConfiguration方法中完成了标签的解析。

 // org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfigurationprivate void parseConfiguration(XNode root) {try {// issue #117 read properties first// 从这里我们也能看出在MyBatis配置文件中各个标签的顺序。因为在代码中是按顺序解析的。propertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(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 #631environmentsElement(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);}}

从源码中我们也可以得出、、和这几个子标签的解析顺序。

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 优先解析的是<package>标签,如果同时配置了<package>、<resource>、<url>、<class>,// 那么只有<package>配置项会生效。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");// 优先解析<resource>,配置了<resource>就不能配置<url>以及<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();// 其次解析<url>,如果配置了<url>,就不能配置<resource>和<class>} 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();// 最后才是解析<class>,配置了<class>,就不能配置<resource>和<url>} 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.");}}}}}

这里我们以常用的标签解析来分析,在上面源码中获取到标签的属性值之后,调用了Configuration的addMappers方法。

// org.apache.ibatis.session.Configuration#addMappers(java.lang.String)
public void addMappers(String packageName) {// 未做任何处理,直接调用MapperRegistry的addMappers方法mapperRegistry.addMappers(packageName);
}

在MapperRegistry的addMappers方法中,调用重载的addMappers方法。

// org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String)
public void addMappers(String packageName) {addMappers(packageName, Object.class);
}

在重载的addMappers方法中,首先通过ResolverUtil的find方法来找到packageName路径下的所有superType的子类,前面传递的superType为Object。我们都知道Java中的所有类(接口)均继承于Object,因此find方法的作用就是找到指定包路径下的所有类(接口)。

// org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String, java.lang.Class<?>)
public void addMappers(String packageName, Class<?> superType) {ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();for (Class<?> mapperClass : mapperSet) {//对于找到的每一个类调用addMapper方法处理addMapper(mapperClass);}
}

在addMapper方法中,通过创建MapperAnnotationBuilder实例来解析指定接口中方法上的、、等标签。

// org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {//如果扫描到的类不是接口跳过if (type.isInterface()) {// 判断传入的Class是否已处理过if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 保存到knownMappers,表示该Class已经处理过knownMappers.put(type, new MapperProxyFactory<>(type));// 解析接口方法上的@Select、@Insert、@Update、@Delete等标签MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);// 重点是这里调用的parse方法!!!parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}
}

在MapperAnnotationBuilder的parse方法中,对接口中的方法进行了初步解析。

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {String resource = type.toString();// 如果未加载过if (!configuration.isResourceLoaded(resource)) {loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());// 解析接口中添加的@CacheNamespace注解,该注解的作用是启用MyBatis的二级缓存。// 关于MyBatis的一级缓存、二级缓存后面会单开一篇文章来分析。parseCache();parseCacheRef();//获取接口中的所有方法for (Method method : type.getMethods()) {if (!canHaveStatement(method)) {continue;}//如果接口中的方法存在@Select或@SelectProvider并且存在@ResultMap注解if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()&& method.getAnnotation(ResultMap.class) == null) {parseResultMap(method);}try {// 重点是这里调用的parseStatement方法parseStatement(method);} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}

MapperAnnotationBuilder的parseStatement方法就比较复杂了。之所以说它复杂,是因为它要解析MyBatis支持的可以在方法上添加的注解。

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
void parseStatement(Method method) {final LanguageDriver languageDriver = getLanguageDriver(method);getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {// 重点,这个mappedStatementId是根据接口名 + “.” + 方法名拼接得到的。final String mappedStatementId = type.getName() + "." + method.getName();// 删除与本次分析无关代码...}String resultMapId = null;if (isSelect) {ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);if (resultMapAnnotation != null) {resultMapId = String.join(",", resultMapAnnotation.value());} else {resultMapId = generateResultMapName(method);}}// 删除与本次分析无关的代码...// 本次分析的重点是这里调用的addMappedStatement方法,// 你敢相信这就是源码吗?多少代码规范告诉我们如果一个方法的参数多于三个就要封装为对象。assistant.addMappedStatement(mappedStatementId,sqlSource,statementType,sqlCommandType,fetchSize,timeout,// ParameterMapIDnull,parameterTypeClass,resultMapId,getReturnType(method),resultSetType,flushCache,useCache,// TODO gcode issue #577false,keyGenerator,keyProperty,keyColumn,statementAnnotation.getDatabaseId(),languageDriver,// ResultSetsoptions != null ? nullOrEmpty(options.resultSets()) : null);});
}

在MapperBuilderAssistant的addMappedStatement方法中,借助于MappedStatement的一个静态内部类Builder来构建MappedStatement,使用了构建器模式。

// org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;// 使用 MappedStatement.Builder来构建MappedStatement 。注意这里构建MappedStatement时传递的id是调用方法(MapperAnnotationBuilder#parseStatement)传递的。MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();// 兜兜转转又调用了Configuration的addMappedStatement方法。configuration.addMappedStatement(statement);return statement;
}

把目光回到Configuration的addMappedStatement方法。这已经接近我们本次分析的尾声了。

// org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {// 在该方法中未作其他处理,直接调用mappedStatements的put方法。// 注意这里的传递的Key为MappedStatement的id属性值。mappedStatements.put(ms.getId(), ms);
}

put方法,这个方法名熟悉吗?从事Java开发的小伙伴都知道,put(K key,V value)、get(K key)这些方法出自JDK中鼎鼎大名的容器类Map接口,我们日常常用的是其实现类HashMap。那么这里调用的是HashMap的put方法吗?

这里实际调用的是MyBatis自己定义的StrictMap,这就和我们前面的分析对上了。至此,本次分析完毕!

// org.apache.ibatis.session.Configuration#mappedStatements
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection").conflictMessageProducer((savedValue, targetValue) ->". please check " + savedValue.getResource() + " and " + targetValue.getResource());

总结

相信看完这篇博客的小伙伴,就知道为什呢使用MyBatis后,DAO层方法不能重载了。因为MyBatis将将接口中方法解析后构造成了MappedStatement对象后,在设置其id属性时,传递的值是接口名 + “.” + 方法名(Java中的方法重载规则是方法名相同,但参数类型或参数位置不同)。而在StrictMap的put方法中,会先判断容器中是否已包含相同key,如果已包含就抛出异常,而不是覆盖已有值。

其实解决这个问题很简单,只需要在设置MappedStatement的id属性值时,要把方法的参数类型作为参与项拼接上去。

使用MyBatis后,DAO层的方法还能重载吗?相关推荐

  1. (转)MyBatis框架的学习(三)——Dao层开发方法

    http://blog.csdn.net/yerenyuan_pku/article/details/71700957 使用MyBatis开发Dao层,通常有两个方法,即原始Dao开发方法和Mappe ...

  2. Spring整合Mybatis之DAO层、Service层开发

    3. Spring整合Mybatis编程DAO层开发 1. 项目引入相关依赖spring mybatis mysql mybatis-spring druid2. 编写spring.xml整合:spr ...

  3. MyBatis-学习笔记06【06.使用Mybatis完成DAO层的开发】

    Java后端 学习路线 笔记汇总表[黑马程序员] MyBatis-学习笔记01[01.Mybatis课程介绍及环境搭建][day01] MyBatis-学习笔记02[02.Mybatis入门案例] M ...

  4. MyBatis的Dao层实现方式

    MyBatis的Dao层实现方式 MyBatis的Dao层实现 1.1 传统开发方式 1. 编写UserDao接口 public interface UserDao { List<User> ...

  5. 详解MyBatis的Dao层实现和配置文件深入

    这篇文章主要为大家详细介绍了MyBatis的Dao层实现和配置文件深入,文中的示例代码讲解详细,感兴趣的小伙伴快来跟随小编一起学习一下 目录 [Mybatis的Dao层实现] [传统开发方式] [代理 ...

  6. MyBatis之Dao层实现

    文章目录 1.传统开发方式 2.代理开发方式 我们在了解 MyBatis 的映射文件和配置文件时,所举例的实例都是通过单元测试的方式实现.但在实际开发中,我们需要将 MyBatis 的实现写到 Dao ...

  7. Mybatis的Dao层实现

    Mybatis的Dao层实现 1.传统方式实现 创建接口com.dao.UserMapper package com.dao;import com.domain.User;import java.io ...

  8. IDEA项目搭建四——使用Mybatis实现Dao层

    一.引入mybatis及mysql的jar包 可以从阿里云上面查找版本,db操作放在dao层所以打开该层的pom.xml文件,找到<dependencies>节点增加两个引入 <de ...

  9. SpringBoot+MyBatis项目Dao层最简单写法

    前言 DAO(Data Access Object) 是数据访问层,说白了就是跟数据库打交道的,而数据库都有哪几种操作呢?没错,就是增删改查.这就意味着Dao层要提供增删改查操作. 不知道大家是怎么写 ...

  10. SSM项目调用Dao层查询方法传入正确参数但查不到数据

    2021年2月17日 2用户登录业务层调用Dao层的查询方法后返回的结果不是预期结果,预期结果为执行方法传入正确参数后查询得到查询结果封装的User对象的List集合,实际结果为传入正确参数执行方法后 ...

最新文章

  1. 生命或可在星际间传播
  2. JavaScript--关于实例对象带不带参数和构造函数带不带参数的关系
  3. 数据结构学习笔记(六):二叉树(Binary Tree)
  4. 网页视频播放php拉伸代码,网页在线视频播放代码大全
  5. 18_Android中Service的生命周期,远程服务,绑定远程服务,aidl服务调用,综合服务案例,编写一个应用程序调用远程支付宝远程服务场景
  6. 视频光端机常见故障问题及处理方法大全
  7. Python 数据分析三剑客之 Pandas(四):函数应用、映射、排序和层级索引
  8. 用反卷积(Deconvnet)可视化理解卷积神经网络还有使用tensorboard
  9. 中国移动互联网流量半年度分析报告
  10. curl命令详解_命令行学习(一)基础命令
  11. SharePoint服务器端对象模型 之 使用CAML进行数据查询(Part 4)
  12. 解决Mac电脑报错:You don‘t have permission to access that port.
  13. 如何在word 里插入代码?
  14. Centos6.7安装ES6.1.3
  15. Python3爬取美女妹子图片
  16. 企业开源办公虚拟专用网工具
  17. Kubernetes入门一:介绍及环境准备
  18. 二维数字图像相关算法软件Ncorr的使用心得
  19. 滚动轴承特征频率计算推导过程
  20. @TOM VIP邮箱,打造商务办公新场景,定位职场人的贴心助手!

热门文章

  1. 上传文件_Spring Boot文件上传
  2. python tkinter 定时_如何使用tkinter创建计时器?
  3. node 多进程 vs java_如何理解node的多进程
  4. Google Cloud资源层级, IAM Identity and Access Management, 控制台云交互
  5. 深度神经网络的正则化
  6. 数字序列中某一位的数字
  7. adc分辨率和精度的区别_STM32学习笔记—ADC采集数据常见问题
  8. 32位程序如何在64位系统上运行_32位支持:使用 GCC 交叉编译 | Linux 中国
  9. Chrome开发者工具对于异常请求不显示Response内容
  10. 《Web漏洞防护》读书笔记——第5章,数据安全