摘要:通過工廠獲得對象。咱直接看,這個命名明顯告訴玩家,就在前面了。是個抽象方法,我們得去看實現。看到想看的東西了,之后將結果交給處理。執行完操作之后,將結果交給。
用了挺久的mybatis,但一直停留在用的層面上,覺得不行的呀,得走出舒適區。
所以想自己看看mybatis的實現,然后模仿著寫一個,哈哈,當然一開始不會要求完成度很高。
這一篇就先看下mybatis奧秘。這里參考的mybatis源碼版本是3.4.5。
首先,先寫一個mybatis簡單使用的例子。
// 使用 public static void main(String[] args) throws IOException { //根據配置文件創建一個SqlSessionFactory對象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 獲取sqlSession對象 SqlSession session = sqlSessionFactory.openSession(); try{ // 獲取接口的實現類實例 IUserMapper mapper = session.getMapper(IUserMapper.class); // 調用方法 User user = mapper.findById(1); System.out.println(user.getName()); }finally{ session.close(); } }
回憶一下,使用Mybatis的步驟就是
寫配置文件,配置連接數據庫的參數,mybatis的參數。
定義接口,并且通過注解或者xml文件的形式提供SQL語句。之后要在配置文件中注冊這個接口。
創建SqlSessionFactory,傳入配置文件。通過工廠獲得SqlSession對象。
通過SqlSession對象獲取自定義的接口的實例,然后就是調用接口的方法。
整個過程中,玩家就只參與了配置參數,還有提供SQL這兩步。所以這兩步就是看mybatis怎么操作的入口,是進入mybatis地下城的大門。
配置參數這部分,使用框架時基本都有這個操作,比較常見。所以算是個分支劇情,而提供SQL算是mybatis的主線劇情,這里先通關主線劇情。
IUserMapper mapper = session.getMapper(IUserMapper.class); User user = mapper.findById(1);
可以看到,在使用時,我們獲取到了我們的接口的一個實現類實例,
燃鵝,我們沒有寫這個接口的實現的呀。所以我覺得是魔法的原因,在這里要打個斷點。
在getMapper的方法上斷點,我們進入了DefaultSqlSession.getMapper(Class
所以默認我們從SqlSessionFactory拿到的是一個DefaultSqlSession的實例。
/* 通過configuration的getMapper方法,傳入我們的接口類型以及SqlSession實例,返 回一個泛型。這里也就是我們的IUserMapper接口的實現類的實例。*/ @Override publicT getMapper(Class type) { return configuration. getMapper(type, this); }
再進去是Configuration.getMapper(Class
/* 這里又從mapperRegistry里拿到對象, mapperRegistry是Configuration類的一個屬性*/ publicT getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
看一下MapperRegistry的getMapper里邊是什么。
這里看到了令人激動的字眼,就是Proxy,
猜測我們最終拿到的IUserMapper的實例是個代理對象
@SuppressWarnings("unchecked") publicT getMapper(Class type, SqlSession sqlSession) { // 看了下,knownMappers是個Map對象,Map , MapperProxyFactory>> final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) 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); } }
MapperProxyFactory的newInstance 一探究竟
其實名字叫xxFactory的肯定是生產xx的,可以猜到返回的是個MapperProxy
public T newInstance(SqlSession sqlSession) { /*這里new了一個MapperProxy,然后調用newInstance*/ final MapperProxymapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
MapperProxy是個啥
/* 這個類實現了InvocationHandler,動態代理的接口。*/ public class MapperProxyimplements InvocationHandler, Serializable
看看newInstance(mapperProxy)做了啥。
使用Proxy構造實現我們IUserMapper接口的代理類的實例!
@SuppressWarnings("unchecked") protected T newInstance(MapperProxymapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
消化一下,開始的疑問是我們沒有提供IUserMapper的實現,但是通過SqlSession的getMapper方法能拿到一個IUserMapper的實現類的對象。
謎底就是最終返回了我們接口的一個代理類的實例。
而MapperProxy實現了InvocationHandler接口,在我們構造代理對象時傳入了MapperProxy對象,
因此在調IUserMapper的所有方法時,都會進入到MapperProxy類的invoke方法。
其實不像上邊那樣操作,通過直接打印這個對象也可以看出來..
System.out.println(mapper); System.out.println(Proxy.isProxyClass(mapper.getClass())); // 打印結果,貼圖片太丑了,就不貼結果圖了。 org.apache.ibatis.binding.MapperProxy@e580929 true劇情2 之 MapperProxy你干了啥
一般使用動態代理,實現了InvocationHandler接口的類中都會持有被代理類的引用,這里也就是MapperProxy。然后在invoke方法里邊先執行額外的操作,再調用被代理類的方法。在MapperProxy這個類里卻沒找到被代理類的引用。
public class MapperProxyimplements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; 。。。 }
所以穿山甲說了什么?
所以當我們調用 IUserMapper 的 findById 時發生了什么?
這里就要看下MapperProxy的invoke方法了。
@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); }
首先進行一個 if 判斷,邏輯是 如果調的這個方法的提供類是Object類,那個就直接執行這個方法。
這里容易想偏,哪個類不是Object的子類呀..
其實應該是 如果是Object中的方法,那就直接執行。
Object有哪些方法呢?toString這些。調mapper.toString()時,就直接被執行,不走下邊的邏輯了。
if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); }
之后是第二個 if,邏輯是,如果這個方法的權限修飾符是public并且是由接口提供的,則執行invokeDefaultMethod方法。
比如在IUserMapper寫了一個默認方法,執行這個方法isDefaultMethod就會返回true了。
這里我們的方法的提供方是代理類,不是接口,所以返回了false。
else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } // isDefaultMethod private boolean isDefaultMethod(Method method) { return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) && method.getDeclaringClass().isInterface(); }
前兩步都是過濾作用,下邊的才是重點。
可以看到通過 cachedMapperMethod方法 拿到了一個 MapperMethod 對象。
看名字是從緩存里拿。然后就執行MapperMethod的execute方法。
final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
緩一緩,小結一下。開始的疑問是,MapperProxy類里邊竟然沒有被代理類對象的引用。
那它想干什么。在invoke方法中我們找到答案。
通過invoke方法的method參數,拿到了一個MapperMethod 對象,
然后執行了這個對象的execute方法,就沒了。中間一些常規的方法就直接執行。
所以純粹就是為了進入invoke方法,拿到MapperMethod ,至始至終都不存在被代理類。
哇,代理的神奇用法,小本本記起來。
接著我們看看怎么通過method參數拿到MapperMethod
這里就很簡單了,Map里邊有就直接返回,沒有就新建。接口的一個方法就對應一個MapperMethod。
so easy ~
//cachedMapperMethod private MapperMethod cachedMapperMethod(Method method) { // methodCache 是個MapMapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
看下MapperMethod的構造過程,發現傳入了接口信息,方法信息,還有配置信息。
主要工作是初始化 command 還有 method 字段。
command里邊就保存方法的名稱(com.mapper.IUserMapper.findById),還有對應的SQL類型(SELECT)。
method里邊保存了方法的返回類型,是否是集合,是否是游標等信息。
看到這里,其實我一直在忽略Configuration這個類里邊是什么東西,等要模仿再去看。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }劇情3 之 MapperMethod你跟著干了啥
我們調用 mapper.findByid, 最終是通過MapperMethod執行execute得到結果。
所以接下來要看看execute方法中隱藏了什么秘密。
下邊是execute方法的內容
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; }
可以看到 INSERT UPDATE 這些熟悉的字眼的了。
通過MapperMethod里的command的屬性,進入不同分支。
這里調用的是findById,進入了SELECT分支,最終執行了下邊的語句,第一句是裝配參數,第二句是執行查詢。
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param);
看下 sqlSession.selectOne(),里邊調用了selectList的方法,然后將結果返回。
@Override publicT selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List list = this. selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }
進入到selectList瞧瞧,感覺流程要走完了,都已經開始select了。
這里又看到了令人激動的字眼,statement。感覺已經在靠近JDBC啦。
有個MappedStatement的對象需要關注一下。
@Override publicList selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
回憶下基本JDBC是怎么用的。
/* 1 加載對應數據庫驅動 Class.forName(driverClass); 2 獲取數據庫連接 Connection con = DriverManager.getConnection(jdbcUrl, user, password); 3 準備SQL語句 String sql = " ... "; 4 執行操作 Statement statement = con.createStatement(); statement.executeUpdate(sql); 5 釋放資源 statement.close(); con.close();*/
MappedStatement是個什么東西呢,它對應著我們的一條SQL語句。
是通過MapperMethod的command對象的name屬性,從configuration里邊拿到的。
拿到MappedStatement之后調用 executor的query方法,這個方法是CachingExecutor提供的。
可以看到,這里通過MappedStatement獲取了我們的SQL,然后生成一個緩存key,想起我記憶深處的mybatis一級二級緩存。
之后返回調用query方法的結果。
@Override publicList query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
還是進入CachingExecutor的query方法。看來Executor這樣的類就是真正執行數據庫操作的類了。
看到先是從MappedStatement里邊拿緩存,如果是空的,就調用delegate.query,
delegate是SimpleExecutor類型,顧名思義CachingExecutor委派了SimpleExecutor來進行數據庫操作。
@Override publicList query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List list = (List ) tcm.getObject(cache, key); if (list == null) { list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
繼續看SimpleExecutor里邊的query,看到SimpleExecutor里邊并沒有query方法,
而是SimpleExecutor繼承了BaseExecutor,query是BaseExecutor類提供的。
第一句斷點進去之后,看到的是存起來的"executing a query",這是出異常時的堆棧信息。
emm..然后就是很多是否存在緩存是否使用緩存的代碼。
咱直接看queryFromDatabase(),這個命名明顯告訴玩家,BOSS就在前面了。
@SuppressWarnings("unchecked") @Override publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { queryStack++; list = resultHandler == null ? (List ) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
同樣是BaseExecutor 提供的 queryFromDatabase()方法。
首先put進去了一個緩存,key是我們之前的緩存鍵,值是一個默認的值,感覺是占位的意思。
然后執行doQuery方法,看到do開頭的方法,就知道不簡單。doGet doPost
doQuery是個抽象方法,我們得去SimpleExecutor看實現。
privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
SimpleExecutor.doQuery
來啦! Statement!而且還有我們熟悉的prepareStatement字眼。哈哈 都是JDBC呀
最后看到是由一個Handler來執行的,看一看這個Handler。
@Override publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler. query(stmt, resultHandler); } finally { closeStatement(stmt); } }
先是進入到了RoutingStatementHandler,然后RoutingStatementHandler委托給了PreparedStatementHandler,下邊是PreparedStatementHandler的query。
看到想看的東西了,ps.execute()
之后將結果交給resultSetHandler處理。
@Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler. handleResultSets(ps); }
回顧一下,我們的問題是MapperMethod對象的execute方法做了什么,結論就是,
我們通過MapperMethod的command屬性和method屬性,知道了要執行的SQL的類型,
這里我們走的是SELECT路線。知道類型之后,交由SqlSession執行selectOne方法。
然后又調用了DefaultSqlSession的selectList方法,DefaultSqlSession表示不想干活,
就交給了勤勞的BaseExecutor,BaseExecutor的里邊有query方法,query方法做一些通用操作,
看一眼有沒有緩存呀這些。在沒有或不用緩存的情況下,再去調doQuery方法,doQuery方法有不同的實現。
在doQuery以及其要調用方法里邊使用的就是我們熟悉的JDBC。執行完操作之后,將結果交給resultSetHandler。
我們使用的是什么?
答:使用的是我們的接口的代理類的實例。
在構造代理類的實例時,
我們傳入了實現了InvocationHandler接口的MapperProxy實例,
當代理對象調用方法時,會進入MapperProxy的invoke方法。
在invoke方法中通過Method對象找MapperMethod,
然后執行MapperMethod對象的execute方法。
在這里,代理的作用是,讓我們知道哪個接口的哪個方法被使用了。MapperProxy 對應了我們的一個接口,
MapperMethod 對應接口里的一個方法,
MappedStatement 對應一條SQL
從上邊流程中,總結各個類的職責
MapperProxy: 定義代理對象調用方法時執行的動作。
即在invoke()里拿到調用的方法對應的MapperMethod,然后調用MapperMethod的execute。MapperMethod: 對應我們接口里的方法,持有SqlCommand(command)和MethodSignature(method),
可以知道方法的全名以及對應的SQL的類型。MappedStatement: 保存的SQL的信息。
SqlSession: 玩家獲取Mapper的地方。假裝執行SQL,實際交給了Executor。
Executor: 真正執行數據庫操作。
大致知道流程是什么樣的,接著就可以模仿著寫一寫了...
emm...感覺沒這么簡單。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76701.html
摘要:此文已由作者溫正湖授權網易云社區發布。歡迎訪問網易云社區,了解更多網易技術產品運營經驗。而嚴格的不會出現這個情況。最后安利下,網易蜂巢云服務已經重磅上線,蜂巢由業界著名的數據庫專家姜承堯親自把關架構設計,免費提供售前技術支持。 此文已由作者溫正湖授權網易云社區發布。 歡迎訪問網易云社區,了解更多網易技術產品運營經驗。 Primary(主)是MongoDB復制集中的最重要的角色,是能夠接...
摘要:相對于工廠模式,抽象工廠模式生產的對象更加具體,也更加豐富,但相對編碼也更加復雜。具體的抽象工廠模式的實現大家可以參考菜鳥教程。知道了工廠模式和抽象工廠模式的區別,請大家使用的時候應該根據具體的情況進行選擇。 大家好,今天給大家分享一些Spring的學習心得,在講Spring之前,先和大家分享Spring中核心的設計模式。 工廠模式 在聊概念之前我先問問大家:什么是工廠? 這個很簡單,...
摘要:前言嗨,小伙伴們,這篇博文將帶大家手寫,讓大家對的核心原理以及工作流程有更加深刻的理解。模塊顧名思義,就是框架配置類,用于解析配置文件加載相關環境。配置模塊這里的對框架的配置使用了簡單的,主要原因還是簡單易懂然后節省時間。 前言 (????)??嗨,小伙伴們,這篇博文將帶大家手寫mybatis,讓大家對mybaits的核心原理以及工作流程有更加深刻的理解。在上篇Spring-Mybat...
摘要:通過我們可以更輕松地入門,更簡單的使用的框架。團隊為了擺脫框架中各類繁復紛雜的配置,使用約定優于配置的思想,在基礎上整合了大量常用的第三方庫的開發框架。這里還要說的一點,的出現并不是單純的為了簡化開發,更是為做鋪墊。 說完了Spring 我們來聊聊Spring的進階版Spring Boot,如果你還不知道Spring Boot,那希望這篇文章能夠為你指明方向。 Spring Boot ...
閱讀 1695·2021-11-24 09:39
閱讀 2469·2021-11-18 10:07
閱讀 3657·2021-08-31 09:40
閱讀 3316·2019-08-30 15:44
閱讀 2627·2019-08-30 12:50
閱讀 3648·2019-08-26 17:04
閱讀 1429·2019-08-26 13:49
閱讀 1262·2019-08-23 18:05