mybatis如何通过接口查找对应的mapper.xml及方法执行详解

 更新时间:2017年06月16日 14:20:09   作者:兴国First  
这篇文章主要给大家介绍了利用mybatis如何通过接口查找对应的mapper.xml及方法执行的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编一起来学习学习吧。

本文主要介绍的是关于mybatis通过接口查找对应mapper.xml及方法执行的相关内容,下面话不多说,来看看详细的介绍:

在使用mybatis的时候,有一种方式是

BookMapper bookMapper = SqlSession().getMapper(BookMapper.class)

获取接口,然后调用接口的方法。只要方法名和对应的mapper.xml中的id名字相同,就可以执行sql。

那么接口是如何与mapper.xml对应的呢?

首先看下,在getMapper()方法是如何操作的。

在DefaultSqlSession.Java中调用了configuration.getMapper()

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

在Configuration.java中调用了mapperRegistry.getMapper(type, sqlSession);

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

下面重点来了,在MapperRegistry.java中实现了动态代理

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

这个函数分两部分来看,首先是从map集合中获取接口代理,map集合的来源,第二部分获取代理后实例化,获取接口的方法,执行sql。

对于第一部分:集合的来源。

这个MapperRegistry.java中有个方法是addMappers();共有两个重载。

public void addMappers(String packageName, Class<?> superType) {
 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
 //通过包名,查找该包下所有的接口进行遍历,放入集合中
 resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
 Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
 for (Class<?> mapperClass : mapperSet) {
  addMapper(mapperClass);
 }
 }

 //解析包名下的接口
 public void addMappers(String packageName) {
 addMappers(packageName, Object.class);
 }

往上追溯该方法的调用是在SqlSessionFactory.build();时对配置文件的解析,其中对节点mappers的解析,这里先不赘述,

mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
 if (parent != null) {
  for (XNode child : parent.getChildren()) {
  //使用package节点进行解析配置
  if ("package".equals(child.getName())) {
   String mapperPackage = child.getStringAttribute("name");
   //注册包下的接口
   configuration.addMappers(mapperPackage);
  } else {
  //使用mapper节点
   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 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.");
   }
  }
  }
 }
 }

这是调用addMapper()的顺序。

同时在改方法中还有一个方法很重要

 public <T> void addMapper(Class<T> type) {
 if (type.isInterface()) {
  if (hasMapper(type)) {
  throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  }
  boolean loadCompleted = false;
  try {
  knownMappers.put(type, new MapperProxyFactory<T>(type));
  //根据接口名寻找同包下同名的xml或者mapper的namespace是该接口的xml
  //找到对用的xml后进行解析mapper节点里面的节点
  MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  parser.parse();
  loadCompleted = true;
  } finally {
  if (!loadCompleted) {
   knownMappers.remove(type);
  }
  }
 }
 }

这是通过接口的全路径来查找对应的xml。这里有两种方式解析,也就是我们平常xml文件放置位置的两种写法。

第一种是不加namespace,把xml文件放在和接口相同的路径下,同时xml的名字与接口名字相同,如接口名为Student.java,xml文件为Student.xml。在相同的包下。这种当时可以不加namespace.

第二种是加namespace,通过namespace来查找对应的xml.

到这就是接口名和xml的全部注册流程。

下面再说下第二部分就是通过动态代理获取接口名字来对应xml中的id。

主要有两个类MapperProxyFactory.java和MapperProxy.java

对于MapperProxyFactory.java

public class MapperProxyFactory<T> {

 private final Class<T> mapperInterface;
 private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 //构造函数,获取接口类
 public MapperProxyFactory(Class<T> mapperInterface) {
 this.mapperInterface = mapperInterface;
 }

 public Class<T> getMapperInterface() {
 return mapperInterface;
 }

 public Map<Method, MapperMethod> getMethodCache() {
 return methodCache;
 }

 @SuppressWarnings("unchecked")
 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<T>(sqlSession, mapperInterface, methodCache);
 return newInstance(mapperProxy);
 }

}

在MapperProxy.java中进行方法的执行

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 if (Object.class.equals(method.getDeclaringClass())) {
  try {
  return method.invoke(this, args);
  } catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
  }
 }
 final MapperMethod mapperMethod = cachedMapperMethod(method);
 //方法的执行
 return mapperMethod.execute(sqlSession, args);
 }

 private MapperMethod cachedMapperMethod(Method method) {
 MapperMethod mapperMethod = methodCache.get(method);
 if (mapperMethod == null) {
  mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  methodCache.put(method, mapperMethod);
 }
 return mapperMethod;
 }

至此,就是mybatis所有接口和xml的加载,以及通过动态代理来进行接口的执行的过程。

总结

以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • 详解Springboot中的异步、定时、邮件任务

    详解Springboot中的异步、定时、邮件任务

    这篇文章主要介绍了Springboot中的异步、定时、邮件任务,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-11-11
  • Java中toString函数的使用示例代码

    Java中toString函数的使用示例代码

    toString()函数用于将当前对象以字符串的形式返回,比如我定义了一个User类,创建了一个user对象,然后使用相应命令去打印user对象,本文结合示例代码介绍了toString函数的使用,需要的朋友可以参考下
    2024-02-02
  • java实现浮点数转人民币的小例子

    java实现浮点数转人民币的小例子

    java实现浮点数转人民币的小例子,需要的朋友可以参考一下
    2013-03-03
  • 学习SpringMVC——如何获取请求参数详解

    学习SpringMVC——如何获取请求参数详解

    本篇文章主要介绍了SpringMVC——如何获取请求参数详解,详细的介绍了每种参数注解的用法。具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2016-12-12
  • Java版学生管理系统

    Java版学生管理系统

    这篇文章主要为大家详细介绍了Java版学生管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • Java实现贪吃蛇大作战小游戏的示例代码

    Java实现贪吃蛇大作战小游戏的示例代码

    本文主要介绍了Java实现贪吃蛇大作战小游戏的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 3分钟纯 Java 注解搭个管理系统的示例代码

    3分钟纯 Java 注解搭个管理系统的示例代码

    这篇文章主要介绍了3分钟纯 Java 注解搭个管理系统的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 基于SpringBoot实现自动装配返回属性的设计思路

    基于SpringBoot实现自动装配返回属性的设计思路

    这篇文章主要介绍了基于SpringBoot实现自动装配返回属性,这里涉及到的技术知识点有注解解析器,为什么用ResponseBodyAdvice这里解析?不在Filter,Interceptors,本文结合示例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-03-03
  • java数据类型转换陷阱包括列表陷阱

    java数据类型转换陷阱包括列表陷阱

    这篇文章主要介绍了java数据类型转换的一些陷阱,包括基本数据类型转换列表陷阱,基本上这一篇就把常见的问题就给大家分享一下
    2020-10-10
  • Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例

    Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例

    今天小编就为大家分享一篇关于Java 8跳过本次循环,继续执行以及跳出循环,终止循环的代码实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-10-10

最新评论