<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                日常開發使用mybatis進行CURD操作十分簡便,我們只需要在Mapper接口定義好方法,然后在mapper.xml中寫好SQL語句,就能在業務代碼中使用了。這簡單到令人發指步驟,讓我們麻痹到以為這個過程是非常簡單的,時間久了腦子都笨了。今天,接著上一篇文章中的例子,結合源碼學習一下mybatis中一條SQL的執行過程,了解下背后發生的故事。 # 從Demo開始 還是之前的例子,代碼如下。 ## 示例代碼 ~~~xml <!--CompanyDAO.xml--> <select id="selectById" resultMap="baseResultMap" > select <include refid="BaseColumns"></include> from company where id= #{id} </select> 復制代碼 ~~~ CompanyDAO.xml,按照id查詢數據表company的記錄,id為主鍵,selectById為statementId。 ~~~java public interface CompanyDao { /** * 根據id查詢CompanyDO * * @param id 主鍵id * @return 查詢結果 */ CompanyDO selectById(@Param("id") Integer id); } 復制代碼 ~~~ CompanyDao.java,Mapper接口,僅包含一個方法selectById,按照mybatis要求,與mapper的xml配置文件一一對應。 ~~~java public class Program { public static void main(String[] args) throws IOException { // mybatis配置文件 String path = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(path); // 獲取 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 創建 SqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession()) { //獲取Mapper:CompanyDao CompanyDao dao = sqlSession.getMapper(CompanyDao.class); //調用selectById方法 CompanyDO companyDO = dao.selectById(1); System.out.println(companyDO); } } } 復制代碼 ~~~ Program.java業務代碼,調用邏輯比較簡單,再啰嗦一下: * 通過mybatis-config.xml創建SqlSessionFactory; * 使用SqlSessionFactory創建并打開SqlSession; * 通過sqlSession獲取CompanyDao的Mapper實例對象; * 調用selectById方法獲取查詢結果; ## 示例分析 由以上示例代碼可知,CompanyDao是一個接口,我們并沒有編寫它的實現類,那為啥可以創建它的對象呢?我們現在得到的到底是哪個類的實例? 打個斷點!下面調試界面截圖,我們發現dao是org.apache.ibatis.binding.MapperProxy的實例。 ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/63b05a91c0bb4218b0d76bb7be8bdc55~tplv-k3u1fbpfcp-zoom-1.image) 熟悉動態代理的同學,看一眼就知道MapperProxy是一個代理類。我們就從這里入手,順藤摸瓜。 # 流程分析 ## Mapper對象怎么來的? MapperProxy對象是通過SqlSession#getMapper方法創建的,這里的sqlSession是DefaultSqlSession的實例,跟著源碼走一下。 ~~~java //org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper @Override public <T> T getMapper(Class<T> type) { //這里調用了configuration的方法,繼續走 return configuration.<T>getMapper(type, this); } //org.apache.ibatis.session.Configuration#getMapper public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //所有的Mapper都注冊在Configuration#mapperRegistry(不過是存儲的MapperProxyFactory),所以是通過它來獲取Mapper對象 //下一步進入MapperRegistry#getMapper return mapperRegistry.getMapper(type, sqlSession); } //org.apache.ibatis.binding.MapperRegistry#getMapper public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //通過type從map中獲取MapperProxyFactory實例 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); //如果mapperProxyFactory,則拋出異常 if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //通過mapperProxyFactory的newInstance創建實例,繼續走 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } //org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession) public T newInstance(SqlSession sqlSession) { //先創建了MapperProxy對象 final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); //調用了newInstance方法 return newInstance(mapperProxy); } //org.apache.ibatis.binding.MapperProxyFactory#newInstance protected T newInstance(MapperProxy<T> mapperProxy) { //到這里就揭開了代理對象創建的面紗 //使用Proxy創建了代理對象 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } 復制代碼 ~~~ 為了方便梳理,上面把Mapper創建過程的代碼放在一起了,我們可以發現整個過程是通過Configuration串在一起的(Configuration是mybatis運行時的基礎)。簡單描述一下過程: * 入口在DefaultSqlSession#getMapper,整個方式只是一個中繼,需要通過Configuration#getMapper來創建; * 在解析mybatis配置時,所有的Mapper注冊信息由Configuration#mapperRegistry存儲,但是存儲的其實是一個與Mapper類型相關的MapperProxyFactory,既然是Factory,那就要生產點啥了。 * MapperProxyFactory創建了MapperProxy對象,然后通過Proxy創建了代理對象,由MapperProxy偽裝了一個Mapper實例。 所以,Mapper對象(示例中的dao)其實一個“披著Mapper皮的MapperProxy對象”。上述過程雖然跳轉的類比較多,一層一層走下來最主要的就是Java動態代理相關的內容。通過下面的時序圖再來看下Mapper對象的創建過程吧。 ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74bb6ee5cec841f6b20e4cdc220cca83~tplv-k3u1fbpfcp-zoom-1.image) ## 代理對象invoke方法執行 創建Mapper對象后,接下來調用Mapper方法,從下面的代碼開始: ~~~java CompanyDO companyDO = dao.selectById(1); 復制代碼 ~~~ dao為MapperProxy對象,當調用selectById時,會先調用org.apache.ibatis.binding.MapperProxy#invoke方法(這里可以看下Java動態代理相關的文章,保存出代理類的動態實現,有機會寫篇文章)。 ~~~java @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //判斷是否為Object類 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); } //走到這里說明是被代理接口的方法,獲取或設置MapperMethod緩存,得到MapperMethod對象 final MapperMethod mapperMethod = cachedMapperMethod(method); //方法執行 return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { //如果緩存中不存在method,就創建一個對象添加到緩存。 return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } 復制代碼 ~~~ 到這里,我們可以知道方法invoke最終執行的是org.apache.ibatis.binding.MapperMethod#execute,接著看MapperMethod這個類。 ~~~java public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { //sql命令,包含mapper接口、mapper方法和configuration this.command = new SqlCommand(config, mapperInterface, method); //方法簽名 this.method = new MethodSignature(config, mapperInterface, method); } } 復制代碼 ~~~ MapperMethod構造方法內步創建了SqlCommand和MethodSignature,這兩個類都是MapperMethod的內部類。等下看org.apache.ibatis.binding.MapperMethod#execute時會發現,它的執行與這兩個類有很大關系,我們先來看下這兩個做了什么。 ~~~java public static class SqlCommand { private final String name; //sql命令類型枚舉:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; private final SqlCommandType type; //構造方法:主要邏輯 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { //獲取方法名稱:示例中就是:selectById final String methodName = method.getName(); //方法所在類的全路徑限定名稱:com.raysonxin.dao.CompanyDao final Class<?> declaringClass = method.getDeclaringClass(); //解析獲取MappedStatement:看下面方法的注釋。 //這樣就可以拿到MappedStatement對象了 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); if (ms == null) { if(method.getAnnotation(Flush.class) != null){ name = null; type = SqlCommandType.FLUSH; } else { throw new BindingException("Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName); } } else { //獲取id name = ms.getId(); //獲取命令類型 type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } public String getName() { return name; } public SqlCommandType getType() { return type; } //解析獲取MappedStatement private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) { //拼接獲取statement,示例中是:com.raysonxin.dao.CompanyDao.selectById //這其實就是Configuration中MappedStatement注冊的key String statementId = mapperInterface.getName() + "." + methodName; //判斷configuration是否存在這個statementId if (configuration.hasStatement(statementId)) { //如果存在,獲取并返回 return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } //這里是從父接口中獲取的邏輯 for (Class<?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } } 復制代碼 ~~~ SqlCommand就是根據接口和方法的簽名信息,從Configuration中獲取對應的MappedStatement,MappedStatement是在解析mybatis-config.xml是添加的,大家可以翻閱Configuration解析代碼,或者查看上一篇文章《Mybatis源碼之Configuration》。SqlCommand創建后,我們就可以清楚知道了其對應的MappedStatement,也就知道了它是查詢、更新、刪除還是添加這樣的命令類型。 ~~~java public static class MethodSignature { //返回結果是否為多個值,即列表 private final boolean returnsMany; //返回結果是否為Map類型 private final boolean returnsMap; //返回結果是否為void private final boolean returnsVoid; //返回結果是否為游標 private final boolean returnsCursor; //返回結果是否為Optioanl類型 private final boolean returnsOptional; //返回值類型 private final Class<?> returnType; private final String mapKey; // private final Integer resultHandlerIndex; //查詢數據范圍參數索引 private final Integer rowBoundsIndex; private final ParamNameResolver paramNameResolver; public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); this.returnsOptional = Optional.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); } } 復制代碼 ~~~ MethodSignature是通過反射獲取其方法描述信息,也就是簽名,代碼太多,僅通過其字段和構造方法看下吧。MethodSignature主要的邏輯是獲取其返回值類型,比如單個、多個、Map等。 對SqlCommand和MethodSignature兩個內部類有一定的認識后,我們繼續看下MapperMethod#execute方法。execute方法會根據SqlCommandType處理不同的命令: * INSERT、UPDATE、DELETE的處理方式應該說是基本相同,參數轉換后調用對應的SqlSession方法去執行; * 對于SELECT命令,需要根據返回值特點,轉換參數、處理查詢范圍等,然后調用SqlSession不同的查詢方法; ~~~java public class MapperMethod { //... //方法執行邏輯 public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判斷命令類型:間接從MappedStatement獲取的 switch (command.getType()) { //insert數據 //INSERT、UPDATE、DELETE的處理方式一毛一樣的; //1、參數處理:把參數列表轉為Map結構,同時會復制一份參數按照param0、param1這樣put到Map //2、調用SqlSession對應的方法執行命令,command.getName()就是我們示例中的com.raysonxin.dao.CompanyDao.selectById 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; } //查詢命令:會根據返回值的類型調用不同的處理方法,進而路由到SqlSession不同的查詢方法,在此之前需要做一些準備工作: //selectOne、selectList、selectCursor等 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); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } 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#execute最終調用了SqlSession接口的方法,在我們的示例中會調用SqlSession#selectOne。 ![image.png](data:image/svg+xml;utf8,) 走到這里是不是有點懵了,怎么調來調去,繞了一圈又回到SqlSession了?為啥不直接調用SqlSession接口呢?這里插一句:*直接使用SqlSession確實是可以,而且也是早起Mybatis所支持的方式;但是它不符合面向對象語言的概念和面向接口編程的編程習慣。由于面向接口的編程是面向對象的大趨勢,MyBatis?為了適應這一趨勢,增加了第二種使用MyBatis?支持接口(Interface)調用方式,即Mapper接口。* * 這一段過程的主要工作就是:**采用動態代理方法,根據我們所調用的接口和方法,結合Configuration所加載的配置信息,動態的把請求路由到SqlSession對應的接口上來。** *![image.png](data:image/svg+xml;utf8,)\_ ## Sql命令執行 繞了一圈回到SqlSession后,才算是真正開始sql命令語句的執行。這個過程也是很復雜的,需要依次經過SqlSession、Executor、StatementHandler、ParamterHandler、ResultSetHandler等,流程較長,不能面面俱到,我們隨著代碼走一遍流程,然后通過后續的文章逐個補齊細節。先看個大體的流程圖: ![image.png](data:image/svg+xml;utf8,) ### SqlSession 按照示例,DefaultSqlSession為具體實現,從DefaultSqlSession#selectOne()開始。 ~~~java @Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. //調用selectList方法,獲取返回結果list List<T> list = this.selectList(statement, parameter); //如果結果剛好是1個,返回;若為多個,拋出異常;若為空,返回null; 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; } } @Override public <E> List<E> selectList(String statement, Object parameter) { //中轉一下,RowBounds給了默認值,不設限。 return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //從configuration獲取MappedStatement, //示例中statement=com.raysonxin.dao.CompanyDao.selectById MappedStatement ms = configuration.getMappedStatement(statement); //通過執行器Executor執行查詢 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(); } } 復制代碼 ~~~ 根據DefaultSqlSession代碼走下來,從selectOne走到selectList,在selectList中調用了Executor#query方法。根據Executor的原理,當前executor為CachingExecutor實例,并且SimpleExecutor為其代理執行器(如下圖),所以接下來進入Executor執行器的流程。 ![image.png](data:image/svg+xml;utf8,) ### Executor CachingExecutor是Executor的緩存設計,若緩存中存在所要查詢的內容會直接返回,否則會交給其代理執行器通過數據庫進行查詢。從CachingExecutor#query(MappedStatement, Object, RowBounds, ResultHandler)開始: ~~~java //org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler) @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //獲取BoundSql BoundSql boundSql = ms.getBoundSql(parameterObject); //獲取緩存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); //調用重載方法 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } //org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql) @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, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); //數據為空還是要調用委托執行器的query if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //緩存不存在時,調用委托執行器的query return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 復制代碼 ~~~ 我們看到如果未啟用或者未命中緩存的情況,CachingExecutor會通過其委托執行器繼續查詢,也就是SimpleExecutor#query。SimpleExecutor繼承自BaseExecutor,在BaseExecutor也同樣具有緩存的處理邏輯,我們暫時跳過,僅貼出主要代碼: ~~~java public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //省略。。。 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--; } //.... } 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 { //執行doQuery,這個是抽象方法,具體實現在SimpleExecutor 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; } @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //獲取configuration Configuration configuration = ms.getConfiguration(); //創建StatementHandler,默認為PreparedStatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //創建并初始化statement stmt = prepareStatement(handler, ms.getStatementLog()); //由StatementHandler執行查詢,使用ResultSetHandler處理查詢結果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); //創建Statement,BaseStatementHandler#prepare stmt = handler.prepare(connection, transaction.getTimeout()); //設置參數,使用ParameterHandler handler.parameterize(stmt); return stmt; } 復制代碼 ~~~ Executor過程的執行就到這里了,CachingExecutor->BaseExecutor->SimpleExecutor,并且完成了StatementHandler執行的準備工作。CachingExecutor充當了緩存一層,當未命中緩存時會執行數據庫查詢,此時交給BaseExecutor及其子類執行。我們通過doQuery方法看到了StatementHandler創建、參數設置、執行與關閉整個生命周期,可以說Executor是StatementHandler的調度者。 從SimpleExecutor#doQuery、SimpleExecutor#prepareStatement方法看,可以把StatementHandler的工作分為幾個過程:創建Statement對象、設置Statement參數、執行Statement查詢并返回結果、關閉Statement對象。其中“設置Statement參數”需要使用ParameterHandler進行參數設置、“執行Statement查詢并返回結果”需要使用ResultSetHandler處理查詢結果。接下來重點看下參數設置、查詢、結果處理三個過程。 ### ParameterHandler prepareStatement方法中,調用了StatementHandler#parameterize進行參數設置,我們看下PreparedStatementHandler的實現。 ~~~java @Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } 復制代碼 ~~~ 這里的parameterHandler是ParameterHandler實例。在mybatis中,ParameterHandler僅有一個默認實現,即:DefaultParameterHandler。所以,直接看DefaultParameterHandler#setParameters方法: ~~~java @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); //獲取參數映射列表,它來自SqlSource的解析過程,目前了解即可。 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { //遍歷參數映射 for (int i = 0; i < parameterMappings.size(); i++) { //獲取當前參數映射對象 ParameterMapping parameterMapping = parameterMappings.get(i); //如果參數模式不是OUT,即IN、INOUT時,繼續執行 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; //獲取參數名稱:示例中為:id String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } //參數值為空, else if (parameterObject == null) { value = null; } //類型處理器是否存在當前參數類型。如果只有一個參數,這里一般會命中。 //我們編寫sql語句時,沒有標明入參類型,mybatis會把參數類型解析為Object else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } //多個參數時,會走到這里。 else { //轉為MetaObject類似于一個map MetaObject metaObject = configuration.newMetaObject(parameterObject); //獲取當前參數對應的值 value = metaObject.getValue(propertyName); } //獲取類型處理器 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { //通過類型處理器設置Statement參數。 typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } 復制代碼 ~~~ setParameters設置參數的過程比較簡單:獲取參數映射列表后,依次遍歷參數映射對象,逐個參數類型獲取類型處理器,然后使用類型處理器設置Statement參數。當然,這里面會設置到類型處理器的查詢中轉操作(UnKnown->Integer),大家可以調試看下過程。 ### StatementHandler 參數設置完成后,就到了執行操作了。示例中將會走到PreparedStatementHandler#query,如下: ~~~java @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { //轉為PreparedStatement類型 PreparedStatement ps = (PreparedStatement) statement; //執行 ps.execute(); //處理查詢結果 return resultSetHandler.handleResultSets(ps); } 復制代碼 ~~~ 呃,這個太簡潔了。把statement類型轉為PreparedStatement,執行execute,處理查詢結果并返回。Statement相關的內容屬于傳統JDBC范疇了,大家比較熟悉,不再贅述。 ### ResultSetHandler 這應該是查詢過程中的最后一步了!由PreparedStatementHandler#query方法可知,執行java.sql.PreparedStatement#execute后,mybatis使用ResultSetHandler#handleResultSets處理查詢結果。mybatis對ResultSetHandler也僅有一個默認實現DefaultResultSetHandler,直接看代碼吧: ~~~java @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; //獲取第一個結果集 ResultSetWrapper rsw = getFirstResultSet(stmt); //獲取結果映射 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); //如果rsw為空,或者resultMapCount<1,會拋出異常 validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { //獲取結果集映射對象 ResultMap resultMap = resultMaps.get(resultSetCount); //獲取結果,完成查詢結果映射 handleResultSet(rsw, resultMap, multipleResults, null); //處理下一個,多個結果時有用 rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } //獲取配置的結果集信息,我們沒有使用,不用管了。 String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); } 復制代碼 ~~~ 總結一下這個階段的處理過程,看圖吧,一圖勝千言! ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19e503e00a0e45708a6d576cb40ac4c2~tplv-k3u1fbpfcp-zoom-1.image) # 總結 本文從一個簡答的Mapper查詢方法開始,詳細分析了其執行過程。按照我的個人理解,把執行過程分為了三個階段: * Mapper接口實例的動態實現階段:Mybatis基于動態代理,動態實現Mapper接口,其實一個“披著Mapper皮的MapperProxy對象”; * Mapper接口命令解析階段:這個過程中基于接口、方法簽名,結合反射技術、Configuration配置信息,確認了對應的SqlSession方法入口。 * SQL命令執行階段:從SqlSession入口開始,依次經過Executor、StatementHandler、ParameterHandler、ResultSetHandler四大組件的層層處理,最終完成查詢過程。 作者:碼路印記 鏈接:https://juejin.cn/post/6879710920534884360 來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看