mybatis如何通過(guò)接口查找對(duì)應(yīng)的mapper.xml及方法執(zhí)行詳解
本文主要介紹的是關(guān)于mybatis通過(guò)接口查找對(duì)應(yīng)mapper.xml及方法執(zhí)行的相關(guān)內(nèi)容,下面話不多說(shuō),來(lá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)來(lái)了,在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ù)分兩部分來(lái)看,首先是從map集合中獲取接口代理,map集合的來(lái)源,第二部分獲取代理后實(shí)例化,獲取接口的方法,執(zhí)行sql。
對(duì)于第一部分:集合的來(lái)源。
這個(gè)MapperRegistry.java中有個(gè)方法是addMappers();共有兩個(gè)重載。
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
//通過(guò)包名,查找該包下所有的接口進(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);
}
}
}
}
這是通過(guò)接口的全路徑來(lái)查找對(duì)應(yīng)的xml。這里有兩種方式解析,也就是我們平常xml文件放置位置的兩種寫(xiě)法。
第一種是不加namespace,把xml文件放在和接口相同的路徑下,同時(shí)xml的名字與接口名字相同,如接口名為Student.java,xml文件為Student.xml。在相同的包下。這種當(dāng)時(shí)可以不加namespace.
第二種是加namespace,通過(guò)namespace來(lái)查找對(duì)應(yīng)的xml.
到這就是接口名和xml的全部注冊(cè)流程。
下面再說(shuō)下第二部分就是通過(guò)動(dòng)態(tài)代理獲取接口名字來(lá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的加載,以及通過(guò)動(dòng)態(tài)代理來(lái)進(jìn)行接口的執(zhí)行的過(guò)程。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
MyBatis中#{}占位符與${}拼接符的用法說(shuō)明
這篇文章主要介紹了MyBatis中#{}占位符與${}拼接符的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別
這篇文章主要為大家詳細(xì)介紹了OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
淺談Java自定義類加載器及JVM自帶的類加載器之間的交互關(guān)系
這篇文章主要介紹了淺談Java自定義類加載器及JVM自帶的類加載器之間的交互關(guān)系,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02
Spring TaskScheduler使用實(shí)例解析
這篇文章主要介紹了Spring TaskScheduler使用實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
javacv開(kāi)發(fā)詳解之調(diào)用本機(jī)攝像頭視頻
這篇文章主要介紹了javacv開(kāi)發(fā)詳解之調(diào)用本機(jī)攝像頭視頻,對(duì)javacv感興趣的同學(xué),可以參考下2021-04-04
Java使用OpenFeign管理多個(gè)第三方服務(wù)調(diào)用
最近開(kāi)發(fā)了一個(gè)統(tǒng)一調(diào)度類的項(xiàng)目,需要依賴多個(gè)第三方服務(wù),這些服務(wù)都提供了HTTP接口供我調(diào)用。感興趣的可以了解一下2021-06-06
升級(jí)dubbo2.7.4.1版本平滑遷移到注冊(cè)中心nacos
這篇文章主要為大家介紹了2.7.4.1的dubbo平滑遷移到注冊(cè)中心nacos的兩種版本升級(jí)方案,以及為什要升級(jí),有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02

