mybatis如何通过接口查找对应的mapper.xml及方法执行详解
本文主要介绍的是关于mybatis通过接口查找对应mapper.xml及方法执行的相关内容,下面话不多说,来看看详细的介绍:
在使用mybatis的时候,有一种方式是
BookMapperbookMapper=SqlSession().getMapper(BookMapper.class)
获取接口,然后调用接口的方法。只要方法名和对应的mapper.xml中的id名字相同,就可以执行sql。
那么接口是如何与mapper.xml对应的呢?
首先看下,在getMapper()方法是如何操作的。
在DefaultSqlSession.Java中调用了configuration.getMapper()
publicTgetMapper(Class type){ returnconfiguration. getMapper(type,this); }
在Configuration.java中调用了mapperRegistry.getMapper(type,sqlSession);
publicTgetMapper(Class type,SqlSessionsqlSession){ returnmapperRegistry.getMapper(type,sqlSession); }
下面重点来了,在MapperRegistry.java中实现了动态代理
publicTgetMapper(Class type,SqlSessionsqlSession){ finalMapperProxyFactory mapperProxyFactory=(MapperProxyFactory )knownMappers.get(type); if(mapperProxyFactory==null) thrownewBindingException("Type"+type+"isnotknowntotheMapperRegistry."); try{ returnmapperProxyFactory.newInstance(sqlSession); }catch(Exceptione){ thrownewBindingException("Errorgettingmapperinstance.Cause:"+e,e); } }
这个函数分两部分来看,首先是从map集合中获取接口代理,map集合的来源,第二部分获取代理后实例化,获取接口的方法,执行sql。
对于第一部分:集合的来源。
这个MapperRegistry.java中有个方法是addMappers();共有两个重载。
publicvoidaddMappers(StringpackageName,Class>superType){
ResolverUtil>resolverUtil=newResolverUtil>();
//通过包名,查找该包下所有的接口进行遍历,放入集合中
resolverUtil.find(newResolverUtil.IsA(superType),packageName);
Set>>mapperSet=resolverUtil.getClasses();
for(Class>mapperClass:mapperSet){
addMapper(mapperClass);
}
}
//解析包名下的接口
publicvoidaddMappers(StringpackageName){
addMappers(packageName,Object.class);
}
往上追溯该方法的调用是在SqlSessionFactory.build();时对配置文件的解析,其中对节点mappers的解析,这里先不赘述,
mapperElement(root.evalNode("mappers"));
privatevoidmapperElement(XNodeparent)throwsException{
if(parent!=null){
for(XNodechild:parent.getChildren()){
//使用package节点进行解析配置
if("package".equals(child.getName())){
StringmapperPackage=child.getStringAttribute("name");
//注册包下的接口
configuration.addMappers(mapperPackage);
}else{
//使用mapper节点
Stringresource=child.getStringAttribute("resource");
Stringurl=child.getStringAttribute("url");
StringmapperClass=child.getStringAttribute("class");
if(resource!=null&&url==null&&mapperClass==null){
ErrorContext.instance().resource(resource);
InputStreaminputStream=Resources.getResourceAsStream(resource);
XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());
mapperParser.parse();
}elseif(resource==null&&url!=null&&mapperClass==null){
ErrorContext.instance().resource(url);
InputStreaminputStream=Resources.getUrlAsStream(url);
XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,url,configuration.getSqlFragments());
mapperParser.parse();
}elseif(resource==null&&url==null&&mapperClass!=null){
Class>mapperInterface=Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
}else{
thrownewBuilderException("Amapperelementmayonlyspecifyaurl,resourceorclass,butnotmorethanone.");
}
}
}
}
}
这是调用addMapper()的顺序。
同时在改方法中还有一个方法很重要
publicvoidaddMapper(Class type){ if(type.isInterface()){ if(hasMapper(type)){ thrownewBindingException("Type"+type+"isalreadyknowntotheMapperRegistry."); } booleanloadCompleted=false; try{ knownMappers.put(type,newMapperProxyFactory (type)); //根据接口名寻找同包下同名的xml或者mapper的namespace是该接口的xml //找到对用的xml后进行解析mapper节点里面的节点 MapperAnnotationBuilderparser=newMapperAnnotationBuilder(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
publicclassMapperProxyFactory{ privatefinalClass mapperInterface; privateMap methodCache=newConcurrentHashMap (); //构造函数,获取接口类 publicMapperProxyFactory(Class mapperInterface){ this.mapperInterface=mapperInterface; } publicClass getMapperInterface(){ returnmapperInterface; } publicMap getMethodCache(){ returnmethodCache; } @SuppressWarnings("unchecked") protectedTnewInstance(MapperProxy mapperProxy){ return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy); } //供外部调用 publicTnewInstance(SqlSessionsqlSession){ finalMapperProxy mapperProxy=newMapperProxy (sqlSession,mapperInterface,methodCache); returnnewInstance(mapperProxy); } }
在MapperProxy.java中进行方法的执行
publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{
if(Object.class.equals(method.getDeclaringClass())){
try{
returnmethod.invoke(this,args);
}catch(Throwablet){
throwExceptionUtil.unwrapThrowable(t);
}
}
finalMapperMethodmapperMethod=cachedMapperMethod(method);
//方法的执行
returnmapperMethod.execute(sqlSession,args);
}
privateMapperMethodcachedMapperMethod(Methodmethod){
MapperMethodmapperMethod=methodCache.get(method);
if(mapperMethod==null){
mapperMethod=newMapperMethod(mapperInterface,method,sqlSession.getConfiguration());
methodCache.put(method,mapperMethod);
}
returnmapperMethod;
}
至此,就是mybatis所有接口和xml的加载,以及通过动态代理来进行接口的执行的过程。
总结
以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。