Mybatis源碼解析之mapper接口的代理模式詳解
一、簡介
在mybatis中執(zhí)行sql時有兩種方式,一種是基于statementId,也就是直接調(diào)用SqlSession的方法,如sqlSession.update(“statementId”); 還有一種方法是基于java接口,也是日常開發(fā)中最常用的方式。 mapper接口中的每個方法都可以喝mapper xml中的一條sql語句對應(yīng),我們可以直接通過調(diào)用接口方法的方式進行sql執(zhí)行。因為mybatis會為mapper接口通過jdk動態(tài)代理的方法生成接口的實現(xiàn)類,本篇文章將針對mapper接口的代理展開分析。 以下是mapper接口的代理模式的核心組件的類圖。
二、MapperRegistry
MapperRegistry通過Map結(jié)構(gòu)的屬性knownMappers中維護著mybatis中所有的mapper接口。
1. MapperRegistry#addMapper(class)
當開發(fā)者在配置文件中配置了通過mappers節(jié)點的子節(jié)點mapper配置了mapper接口時,會調(diào)用configuation#addMapper(Class)記錄mapper接口,而configuration又委托了MapperRegistry#addMapper(class)處理邏輯。
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)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
可以看到,MapperRegistry以mapper接口的類型為key值,將接口類型封裝成MapperProxyFactory作為value值放入knownMappers。
2. MapperRegistry#getMapper(Class, SqlSession)
該方法基于SqlSession參數(shù)向外提供了對應(yīng)類型的mapper接口的對象。
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); } }
根據(jù)mapper接口類型找到對應(yīng)的MapperProxyFactory時,調(diào)用其newInstance方法得到對應(yīng)的對象返回。
三、MapperProxyFactory
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final 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); } }
如果對jdk動態(tài)代理有一定了解,很容易就能看出來MapperProxyFactory的newInstance方法是很典型的生成代理對象的方式。
四、MapperProxy
MapperProxy作為InvocationHandler的實現(xiàn)類,是jdk動態(tài)代理模式的核心。
1. MapperProxy#invoke(Object, Method, Object[])
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, 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; }
對于Object方法和default方法,執(zhí)行原方法邏輯即可。 對于對于sql語句的方法,交給MapperMethod 處理。
五、MapperMethod
Mapper類負責去處理mapper接口中的sql方法。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
MapperMethod還有一個靜態(tài)內(nèi)部類MethodSignature用作mapper方法的標簽,SqlCommand用作sql命令。 可以看到,根據(jù)sql命令的類型(insert|update|delete|select|flush)和返回類型分別調(diào)用SqlSession的不同方法,然后對insert|update|delete方法的返回值做適配。
MapperMethod#rowCountResult(int)
private Object rowCountResult(int rowCount) { final Object result; if (method.returnsVoid()) { result = null; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long)rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = rowCount > 0; } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; }
insert|update|delete方法方法的返回值就是sql命令的匹配行數(shù),而在mapper方法中支持Integer、Long、Boolean和void類型的返回,因此做簡單適配。
到此這篇關(guān)于Mybatis源碼解析之mapper接口的代理模式詳解的文章就介紹到這了,更多相關(guān)mapper接口的代理模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java數(shù)據(jù)結(jié)構(gòu)基礎(chǔ):緒論
這篇文章主要介紹了Java的數(shù)據(jù)解構(gòu)基礎(chǔ),希望對廣大的程序愛好者有所幫助,同時祝大家有一個好成績,需要的朋友可以參考下,希望能給你帶來幫助2021-07-07Java的JSON格式轉(zhuǎn)換庫GSON的初步使用筆記
GSON是Google開發(fā)并在在GitHub上開源的Java對象與JSON互轉(zhuǎn)功能類庫,在Android開發(fā)者中也大受歡迎,這里我們就來看一下Java的JSON格式轉(zhuǎn)換庫GSON的初步使用筆記:2016-06-06Mybatis配置之<properties>屬性配置元素解析
這篇文章主要介紹了Mybatis配置之<properties>屬性配置元素解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07如何使用IDEA的groovy腳本文件生成帶JPA注解的實體類(圖文詳解)
這篇文章主要介紹了如何使用IDEA的groovy腳本文件生成帶JPA注解的實體類,本文通過圖文并茂實例相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07SpringBoot實現(xiàn)對Http接口進行監(jiān)控的代碼
Spring Boot Actuator是Spring Boot提供的一個模塊,用于監(jiān)控和管理Spring Boot應(yīng)用程序的運行時信息,本文將介紹一下Spring Boot Actuator以及代碼示例,以及如何進行接口請求監(jiān)控,需要的朋友可以參考下2024-07-07