用了挺久的mybatis,但一直停留在用的層面上,以爲不行的呀,得走出溫馨區。
因此想本身看看mybatis的實現,而後模仿着寫一個,哈哈,固然一開始不會要求完成度很高。
這一篇就先看下mybatis奧祕。這裏參考的mybatis源碼版本是3.4.5。sql
首先,先寫一個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的步驟就是apache
整個過程當中,玩家就只參與了配置參數,還有提供SQL這兩步。因此這兩步就是看mybatis怎麼操做的入口,是進入mybatis地下城的大門。
配置參數這部分,使用框架時基本都有這個操做,比較常見。因此算是個分支劇情,而提供SQL算是mybatis的主線劇情,這裏先通關主線劇情。緩存
IUserMapper mapper = session.getMapper(IUserMapper.class); User user = mapper.findById(1);
能夠看到,在使用時,咱們獲取到了咱們的接口的一個實現類實例,
燃鵝,咱們沒有寫這個接口的實現的呀。因此我以爲是魔法的緣由,在這裏要打個斷點。session
在getMapper的方法上斷點,咱們進入了DefaultSqlSession.getMapper(Class<T>),
因此默認咱們從SqlSessionFactory拿到的是一個DefaultSqlSession的實例。mybatis
/* 經過configuration的getMapper方法,傳入咱們的接口類型以及SqlSession實例,返 回一個泛型。這裏也就是咱們的IUserMapper接口的實現類的實例。*/ @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
再進去是Configuration.getMapper(Class<T>, SqlSession)。app
/* 這裏又從mapperRegistry裏拿到對象, mapperRegistry是Configuration類的一個屬性*/ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
看一下MapperRegistry的getMapper裏邊是什麼。
這裏看到了使人激動的字眼,就是Proxy,
猜想咱們最終拿到的IUserMapper的實例是個代理對象框架
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 看了下,knownMappers是個Map對象,Map<Class<?>, MapperProxyFactory<?>> 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); } }
MapperProxyFactory的newInstance 一探究竟
其實名字叫xxFactory的確定是生產xx的,能夠猜到返回的是個MapperProxyide
public T newInstance(SqlSession sqlSession) { /*這裏new了一個MapperProxy,而後調用newInstance*/ final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
MapperProxy是個啥ui
/* 這個類實現了InvocationHandler,動態代理的接口。*/ public class MapperProxy<T> implements InvocationHandler, Serializable
看看newInstance(mapperProxy)作了啥。
使用Proxy構造實現咱們IUserMapper接口的代理類的實例!
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { 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
通常使用動態代理,實現了InvocationHandler接口的類中都會持有被代理類的引用,這裏也就是MapperProxy。而後在invoke方法裏邊先執行額外的操做,再調用被代理類的方法。在MapperProxy這個類裏卻沒找到被代理類的引用。
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> 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);
接着咱們看看怎麼經過method參數拿到MapperMethod
這裏就很簡單了,Map裏邊有就直接返回,沒有就新建。接口的一個方法就對應一個MapperMethod。
so easy ~
//cachedMapperMethod private MapperMethod cachedMapperMethod(Method method) { // methodCache 是個Map<Method, MapperMethod> MapperMethod 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); }
咱們調用 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 public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>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 public <E> List<E> 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以後調用 executor的query方法,這個方法是CachingExecutor提供的。
能夠看到,這裏經過MappedStatement獲取了咱們的SQL,而後生成一個緩存key,想起我記憶深處的mybatis一級二級緩存。
以後返回調用query方法的結果。
@Override public <E> List<E> 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 public <E> List<E> 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<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
繼續看SimpleExecutor裏邊的query,看到SimpleExecutor裏邊並無query方法,
而是SimpleExecutor繼承了BaseExecutor,query是BaseExecutor類提供的。
第一句斷點進去以後,看到的是存起來的"executing a query",這是出異常時的堆棧信息。
emm..而後就是不少是否存在緩存是否使用緩存的代碼。
咱直接看queryFromDatabase(),這個命名明顯告訴玩家,BOSS就在前面了。
@SuppressWarnings("unchecked") @Override public <E> List<E> 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<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) 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看實現。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> 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 public <E> List<E> 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.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
先是進入到了RoutingStatementHandler,而後RoutingStatementHandler委託給了PreparedStatementHandler,下邊是PreparedStatementHandler的query。
看到想看的東西了,ps.execute()
以後將結果交給resultSetHandler處理。
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
答:使用的是咱們的接口的代理類的實例。
在構造代理類的實例時,
咱們傳入了實現了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...感受沒這麼簡單。