mybatis如何通過接口查找對(duì)應(yīng)的mapper.xml及方法執(zhí)行詳解
本文主要介紹的是關(guān)于mybatis通過接口查找對(duì)應(yīng)mapper.xml及方法執(zhí)行的相關(guān)內(nèi)容,下面話不多說,來看看詳細(xì)的介紹:
在使用mybatis的時(shí)候,有一種方式是
BookMapper bookMapper = SqlSession().getMapper(BookMapper.class)
獲取接口,然后調(diào)用接口的方法。只要方法名和對(duì)應(yīng)的mapper.xml中的id名字相同,就可以執(zhí)行sql。
那么接口是如何與mapper.xml對(duì)應(yīng)的呢?
首先看下,在getMapper()
方法是如何操作的。
在DefaultSqlSession.Java中調(diào)用了configuration.getMapper()
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
在Configuration.java中調(diào)用了mapperRegistry.getMapper(type, sqlSession);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
下面重點(diǎn)來了,在MapperRegistry.java中實(shí)現(xiàn)了動(dòng)態(tài)代理
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); } }
這個(gè)函數(shù)分兩部分來看,首先是從map集合中獲取接口代理,map集合的來源,第二部分獲取代理后實(shí)例化,獲取接口的方法,執(zhí)行sql。
對(duì)于第一部分:集合的來源。
這個(gè)MapperRegistry.java中有個(gè)方法是addMappers();
共有兩個(gè)重載。
public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //通過包名,查找該包下所有的接口進(jìn)行遍歷,放入集合中 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); }
往上追溯該方法的調(diào)用是在SqlSessionFactory.build();
時(shí)對(duì)配置文件的解析,其中對(duì)節(jié)點(diǎn)mappers的解析,這里先不贅述,
mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { //使用package節(jié)點(diǎn)進(jìn)行解析配置 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); //注冊(cè)包下的接口 configuration.addMappers(mapperPackage); } else { //使用mapper節(jié)點(diǎn) 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."); } } } } }
這是調(diào)用addMapper()
的順序。
同時(shí)在改方法中還有一個(gè)方法很重要
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)); //根據(jù)接口名尋找同包下同名的xml或者mapper的namespace是該接口的xml //找到對(duì)用的xml后進(jìn)行解析mapper節(jié)點(diǎn)里面的節(jié)點(diǎn) MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
這是通過接口的全路徑來查找對(duì)應(yīng)的xml。這里有兩種方式解析,也就是我們平常xml文件放置位置的兩種寫法。
第一種是不加namespace,把xml文件放在和接口相同的路徑下,同時(shí)xml的名字與接口名字相同,如接口名為Student.java,xml文件為Student.xml。在相同的包下。這種當(dāng)時(shí)可以不加namespace.
第二種是加namespace,通過namespace來查找對(duì)應(yīng)的xml.
到這就是接口名和xml的全部注冊(cè)流程。
下面再說下第二部分就是通過動(dòng)態(tài)代理獲取接口名字來對(duì)應(yīng)xml中的id。
主要有兩個(gè)類MapperProxyFactory.java和MapperProxy.java
對(duì)于MapperProxyFactory.java
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); //構(gòu)造函數(shù),獲取接口類 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); } //供外部調(diào)用 public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
在MapperProxy.java中進(jìn)行方法的執(zhí)行
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); //方法的執(zhí)行 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的加載,以及通過動(dòng)態(tài)代理來進(jìn)行接口的執(zhí)行的過程。
總結(jié)
以上就是這篇文章的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java聊天室之實(shí)現(xiàn)獲取Socket功能
這篇文章主要為大家詳細(xì)介紹了Java簡易聊天室之實(shí)現(xiàn)獲取遠(yuǎn)程服務(wù)器和客戶機(jī)的IP地址和端口號(hào)功能,文中的示例代碼講解詳細(xì),需要的可以了解一下2022-10-10Springboot如何通過filter修改Header的值
這篇文章主要介紹了Springboot如何通過filter修改Header的值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07打開.properties中文顯示unicode編碼問題以及解決
這篇文章主要介紹了打開.properties中文顯示unicode編碼問題以及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01java遞歸菜單樹轉(zhuǎn)換成pojo對(duì)象
這篇文章介紹了java遞歸菜單樹轉(zhuǎn)換成pojo對(duì)象的具體實(shí)現(xiàn),有需要的朋友可以參考一下2013-08-08java實(shí)現(xiàn)字符串反轉(zhuǎn)案例
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)字符串反轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09解決resultMap映射數(shù)據(jù)錯(cuò)誤的問題
這篇文章主要介紹了解決resultMap映射數(shù)據(jù)錯(cuò)誤的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08spring boot使用properties定義短信模板的方法教程
這篇文章主要給大家介紹了關(guān)于spring boot使用properties定義短信模板的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01Spring AOP面向切面編程實(shí)現(xiàn)原理方法詳解
這篇文章主要介紹了Spring AOP面向切面編程實(shí)現(xiàn)原理方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08