1. 程式人生 > >mybatis一個物件查詢流程簡單分析(整合spring boot)

mybatis一個物件查詢流程簡單分析(整合spring boot)

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.1.1</version>
</dependency>
首先寫一個單測,然後跟隨斷點看
    @Test
    public void getMaxUpdateTimeByPositions(){
        assert  appBlockDao.getMaxUpdateTimeByPositions("mine")!=null;
    }

跟著斷點進來,發現進入了org.apache.ibatis.binding.MapperProxy動態代理類,執行invoke方法

//獲取一個MapperMethod
 MapperMethod mapperMethod = this.cachedMapperMethod(method);
//接著執行execute方法,傳入一個sqlSession,與被代理方法的入參
 return mapperMethod.execute(this.sqlSession, args);
/**
     * cachedMapperMethod會從methodCache中去獲取MapperMethod,獲取不到則new一個
     * 1、入參為當前被呼叫介面 private final Class<T> mapperInterface;
     * 2、method
     * 3、呼叫sqlSession.getConfiguration()介面,獲取sqlSession中的的Configuration物件,由於 
     * sqlSession都是從sqlSessionFactory中獲取的,所以實際上呼叫的是 
     * org.apache.ibatis.session.defaults.DefaultSqlSessionFactory的Configuration物件
     * 
 */
 private MapperMethod cachedMapperMethod(Method method) {
    //private final Map<Method, MapperMethod> methodCache;
    MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
    if (mapperMethod == null) {
         mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
          this.methodCache.put(method, mapperMethod);
      }
      return mapperMethod;
    }

初始化MapperMethod =>org.apache.ibatis.binding.MapperMethod下的SqlCommand

private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
//分別會初始化內部類SqlCommand、MethodSignature
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
   this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
   this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
//SqlCommand的例項化,篇幅有限,這裡只暫時本次被代理介面執行的重要程式碼
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    String statementName = mapperInterface.getName() + "." + method.getName();
    MappedStatement ms = null;
    //hasStatement方法主要是呼叫protected final Map<String, MappedStatement> 
    //mappedStatements,判斷是否存在statementName,mappedStatements主要維護的是statementName與 
    //xml配置檔案sql塊的關係
    if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
    }
    //獲取獲取不到ms,會報這個熟悉的Invalid bound statement異常
    if(ms==null){
       if (method.getAnnotation(Flush.class) == null) {
          throw new BindingException("Invalid bound statement (not found): "                 statementName);
     }
    } 
...
     //獲取到ms後,給name複製未ms的id,這裡的id=我們dao介面的全類名.方法名
     this.name = ms.getId();
     //SqlCommandType(type=SELECT)
     this.type = ms.getSqlCommandType();
   }

初始化MapperMethod =>org.apache.ibatis.binding.MapperMethod下的MethodSignature,主要是為MapperMethod中的一些引數賦值,為後續的流程使用,如returnType、入參params,判斷是selectOne/List等

================================================================================================

到了這裡,MapperMethod也就成功獲取到了,開始執行execute部分,select請求會進入SqlCommandType.SELECT == this.command.getType()部分

 mapperMethod.execute(this.sqlSession, args);
==================================================================================
 public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        if (SqlCommandType.INSERT == this.command.getType()) {
           ...
        } else if (SqlCommandType.UPDATE == this.command.getType()) {
           ...
        } else if (SqlCommandType.DELETE == this.command.getType()) {
          ...
        //獲取剛才為SqlCommand中SqlCommandType賦值的type=SELECT
        } else if (SqlCommandType.SELECT == this.command.getType()) {
            if .//這裡獲取list、map等方法..{
           else {
                //param是一個map,存放了引數名與value
                param = this.method.convertArgsToSqlCommandParam(args);
               ////由於我們的介面是返回Date型別,所以會執行SelectOne方法
                result = sqlSession.selectOne(this.command.getName(), param);
            }
        } else {
            ...
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

sqlSession.selectOne首先經過=>

  org.mybatis.spring.SqlSessionTemplate的內部類SqlSessionInterceptor的代理

  通過SqlSessionUtils.getSqlSession獲取SqlSession,獲取過程=>

  1、從Spring中TransactionSynchronizationManager中獲取一個sqlSessionHolder

  2、接著從sqlSessionHolder獲取sqlSession,如果獲取不到,那麼就從SqlSessionFactory#openSession獲取一個sqlSession,再呼叫SqlSessionUtils#registerSessionHolder註冊到TransactionSynchronizationManager中

然後進入SqlSession的實際實現類:org.apache.ibatis.session.defaults.DefaultSqlSession,呼叫實際的selectList方法

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
           //以statement為key,獲取MappedStatement,與前面講過的過程一致
            MappedStatement ms = this.configuration.getMappedStatement(statement);
           //執行CachingExecutor的query方法 
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
}
========================================================================================
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       //dao的sql語句資訊,包括sql,parameterObject、metaParameters
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        //獲取一級快取用的CacheKey,在BaseExecutor中建立、獲取
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

然後會執行自身的另外一個query方法,首先判斷MappedStatement中是否存在快取Cache

如果不存在,那麼會執行org.apache.ibatis.executor.BaseExecutor的query方法

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//內部使用一個ThreadLocal<ErrorContext>,設定resource物件等等
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
               //判斷是否從localCache中獲取resultList
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    //判斷到為空後,就開始從資料庫的查詢
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator i$ = this.deferredLoads.iterator();

                while(i$.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

queryFromDatabase,會呼叫BaseExecutor子類org.apache.ibatis.executor.SimpleExecutor中的doQuery方法

1、從Configuration中獲取StatementHandler,呼叫Configuration#newStatementHandler方法,初始化org.apache.ibatis.executor.statement.RoutingStatementHandler,根據mapperStatement中的statementType判斷需要返回SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler物件

2、接著執行org.apache.ibatis.executor.SimpleExecutor中的prepareStatement方法,在這裡獲取jdbc ConnectionStatement等,從而建立資料庫連結,然後呼叫StatementHandler的prepare方法,獲取Statement,呼叫org.apache.ibatis.executor.statement.BaseStatementHandler#instantiateStatement()方法,呼叫Connection.prepareStatement方法,

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Connection connection = this.getConnection(statementLog);
        Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
        handler.parameterize(stmt);
        return stmt;
}

3、然後呼叫org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize()方法,設定佔位符引數PreparedStatement

4、呼叫ResultSetHandler#handleResultSets方法,然後結果集合ResultSet

5org.apache.ibatis.executor.SimpleExecutor#closeStatement()方法,關閉org.apache.ibatis.executor.BaseExecutor#state方法,接著移除localCache,再把查詢到的結果list和key再放入localCache中

動態代理=》SqlSession=>MapperStatement=》Executor=>Handler