在閱讀這篇文章以前,建議先閱讀一下我以前寫的兩篇文章,對理解這篇文章頗有幫助,特別是Mybatis新手:git
mybatis底層原理學習(一):SqlSessionFactory和SqlSession的建立過程數據庫
若是你想得到更好的閱讀體驗,能夠點擊這裏:Mybatis底層原理學習(二):從源碼角度分析一次查詢操做過程緩存
(1)在使用Mybatis操做數據庫的時候,每一次的CRUD操做都會去獲取一次映射配置文件(mapper xml文件)對應的sql映射。每個sql映射在內存緩存中(建立SqlSessionFactory以前就緩存在內存中了)都會有惟一ID,就是sql映射所在xml文件的命名空間加上sql映射配置節點的id值。<br/> (2)Mapper xml文件的命名空間使用的是類的全路徑名,這樣作的好處是能夠全局惟一,又能夠經過反射獲取對應的Mapper類。能夠理解成每個mapper xml文件對應一個Mapper類。<br/>(3)mapper xml文件每個sql映射節點的id屬性值對應類的一個方法。咱們在配置sql映射的時候也必須這樣作,由於Mybatis的底層就是使用反射機制來獲取執行方法的全路徑做爲ID來獲取sql的映射配置的。<br/> (4)每個和mapper xml文件關聯的類,都是Mapper類,在執行過程,經過動態代理,執行對應的方法。Mybatis是如何判斷哪些類是Mapper類的呢?其實只有在運行時纔會知道。在加載Mybatis配置文件中,經過解析mapper xml文件緩存了全部的sql映射配置,在調用SqlSession的getMapper方法獲取Mapper類的時候纔會生成代理類。微信
如今,咱們來從源碼角度分析Mapper代理類的建立過程,demo源碼在後面給出 demo示例:mybatis
public class Main { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { SqlSession sqlSession = MyBatisUtil.getSqlSession(); ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class); Article article = mapper.selectOne(1); LOGGER.info("title:" + article.getTitle() + " " + "content:" + article.getContent()); } }
咱們在這行代碼處搭上斷點:app
ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);
Debug進去,執行下面代碼:源碼分析
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
configuration持有Mybatis的基本配置信息,繼續看看getMapper方法的執行:學習
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
mapperRegistry緩存了全部的SQL映射配置信息,在加載解析Mybatis配置文件(例子是mybatis)和mapper xml文件的時候完成緩存的,繼續看getMapper的執行:ui
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 這裏首先會獲取Mapper代理類工廠,拿到代理工廠就建立代理類 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 建立Mapper代理類 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
經過動態代理機制建立Mapper代理類
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
到這裏,動態代理類建立完成。 經過分析了源碼執行過程,Mapper代理類的建立過程弄清楚了,大致就是經過從緩存中獲取sql映射配置的id(類全路徑名+方法名)來經過動態代理機制建立代理類,實際執行的CRUD是執行動態代理類的方法。 執行CRUD操做的時候,咱們都會執行到動態代理類的invoke方法:
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); }
最後找到映射的方法,執行mapperMethod.execute(sqlSession, args)
。 經過代碼咱們能夠看到,會根據執行方法的操做類型(CRUD)執行不一樣的邏輯處理。
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; }
咱們分析一下查詢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); }
首先根據方法返回類型的不一樣執行不一樣的邏輯,最終會調用SqlSession的selectXXX方法,
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; } }
List<T> list = this.<T>selectList(statement, parameter);
這行代碼邏輯處理:
public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
繼續進去:
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(); } }
到這一步,是調用執行器Executor的query方法:
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); }
進去query方法:
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); }
繼續進去query方法:
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; }
真正訪問數據庫的是這行代碼:list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
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; }
查詢操做由doQuery方法處理,這段代碼就接近原生JDBC操做了,首先會獲取語句處理器,而後開始執行語句,執行完,還會對結果進行結果集處理,返回處理的結果集,這裏就很少分析了
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); } }
咱們在使用Mybatis進行CRUD操做的時候,大致過程是這樣:
固然,這個過程Mybatis還作了不少事情,Sql的解析,結果集的處理……等操做咱們在這篇文章不分析,後面會有文章分析。這篇文章目的是分析Mapper代理類的建立過程和簡單分析一個查詢操做的過程。
學習更多源碼分析文章,歡迎關注微信公衆號:深夜程猿 <br/>【福利】關注公衆號回覆關鍵字,還可得到視頻學習資源,求職簡歷模板