欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-18 18:49:16
本文主要介绍的是关于mybatis通过接口查找对应mapper.xml及方法执行的相关内容,下面话不多说,来看看详细的介绍: 在使用mybatis的时候,有一种方式是...

本文主要介绍的是关于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的加载,以及通过动态代理来进行接口的执行的过程。

总结

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

上一篇:

下一篇: