Mybatis源碼第二篇:SqlSession操作源碼分析

Mybatis自帶是使用SqlSession進(jìn)行數(shù)據(jù)的增刪該查操作愉适,支持一級(jí)緩存啸盏,存儲(chǔ)作用域?yàn)橥粋€(gè)SqlSession什湘,當(dāng)Session flush或close之后,該Session中的所有Cache就將清空赎离。二級(jí)緩存建議采用業(yè)務(wù)處理逛犹。因?yàn)閙ybatis支持的二級(jí)緩存是存儲(chǔ)作用域?yàn)镸apper(Namespace),可自定義存儲(chǔ)源,如Ehcache虽画,Redis掠手。

mybatis一級(jí)緩存和二級(jí)緩存.

1. SqlSession入口

sqlSessionFactory在上一篇仔細(xì)分析了:Mybatis源碼第一篇:sqlSessionFactory創(chuàng)建流程. 下面以查詢語句分析sqlsession

SqlSession sqlSession = sqlSessionFactory.openSession();
User user = sqlSession.selectOne("user.selectOne",account);

SqlSession是執(zhí)行增刪改查的接口,默認(rèn)實(shí)現(xiàn)類是:DefaultSqlSession,主要這個(gè)類是非線程安全狸捕。非線程安全是指多線程操作同一個(gè)對(duì)象可能會(huì)出現(xiàn)問題,如果是每個(gè)線程中new SqlSession一個(gè)众雷,而這個(gè)SqlSession只在這一個(gè)線程中使用灸拍,那么肯定是沒問題的嚷那。

 /**
   * 
   * @param execType 執(zhí)行器類型
   * @param level 事務(wù)隔離級(jí)別
   * @param autoCommit 是否自動(dòng)提交
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 獲取數(shù)據(jù)庫配置環(huán)境
      final Environment environment = configuration.getEnvironment();
      // 獲取事務(wù)工廠
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 創(chuàng)建事務(wù)
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 生成executor執(zhí)行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 生成默認(rèn)SqlSession,這個(gè)是非線程安全击碗,所以每次需要開啟一個(gè)新的。
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

1.1 SqlSession的接口和實(shí)現(xiàn)類

下面看下SqlSession接口定義的方法


sqlSession接口定義

這里插下Java泛型的定義戈锻。T 單個(gè)Object编兄,E 集合的element轩性,K和V是指Map的key,value

// @param <T> the returned object type
<T> T selectOne(String statement, Object parameter);

// @param <E> the returned list element type
<E> List<E> selectList(String statement, Object parameter);

//  * @param <K> the returned Map keys type
//  * @param <V> the returned Map values type
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

DefaultSqlSessioin的構(gòu)造方法:需要configuration狠鸳,executor揣苏,autoCommit

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

2.executor接口

構(gòu)建DefaultSqlSession,需要參數(shù)executor接口的實(shí)現(xiàn)類件舵。默認(rèn)采用SimpleExcutor
不過還有一個(gè)CachingExecutor不能忽視P恫臁!铅祸!因?yàn)槭悄J(rèn)的坑质。

Executor接口實(shí)現(xiàn)類

創(chuàng)建的代碼如下:

/**
   * 創(chuàng)建一個(gè)執(zhí)行器,默認(rèn)是SIMPLE
   * 
   * @param transaction
   * @param executorType
   * @return
   */
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

有個(gè)ExecutorType的枚舉類临梗,不同的執(zhí)行器有不同的用途涡扼。

/**
 * 執(zhí)行器類型
 * 
 * SIMPLE:普通的執(zhí)行器
 * REUSE:會(huì)重用預(yù)處理語句(prepared statements)
 * BATCH:執(zhí)行器將重用語句并執(zhí)行批量更新
 * 
 * @author Clinton Begin
 */
public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

3. DefaultSqlSession實(shí)現(xiàn)類分析

查詢數(shù)據(jù)邏輯,查詢單個(gè)也是使用查詢list結(jié)果。

  @Override
  public <T> T selectOne(String statement) {
    return this.<T>selectOne(statement, null);
  }

  @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;
    }
  }
  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @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();
    }
  }

重點(diǎn)語句是盟庞,通過statement獲取MappedStatement吃沪,然后包裝參數(shù),執(zhí)行查詢什猖。
這里的statement是指mybatis的namespace+select語句的id巷波,組成的。通過configuration里面預(yù)先讀取的Map集合卸伞,找到具體的MappedStatement.


MappedStatement

3.1 Configuration的StrictMap引發(fā)的思考

看到上圖中的mappedStatements大家可能會(huì)奇怪抹镊,命名MappedStatement的id只有連個(gè),為啥放進(jìn)去的確有4個(gè)荤傲。核心原因是 StrictMap類:存儲(chǔ)數(shù)據(jù)分方式垮耳。


MappedStatement

目前Configuration有6個(gè)屬性是StrictMap,都會(huì)遇到id帶有.,存儲(chǔ)兩分?jǐn)?shù)據(jù)問題,不同的mapper.xml重復(fù)key终佛,在必須加上namespace才能獲取唯一俊嗽。如果存在重復(fù)的key,那么直接報(bào)錯(cuò)铃彰,說明下面的六個(gè)屬性key唯一I芑怼!牙捉!
mappedStatements竹揍,cachesresultMaps邪铲,parameterMaps芬位,keyGenerators,
sqlFragments。必須全部的mapper里面唯一带到。

StrictMap

上面的是題外話昧碉,繼續(xù)回到selectList查詢

3.2 執(zhí)行查詢的具體操作

雖然配置了CachingExecutor,但是沒有配置二級(jí)緩存最終是會(huì)調(diào)用SimpleExecutor的query方法揽惹。實(shí)際是BaseExecutor的方法被饿。先查詢一級(jí)緩存,可以在select語句中配置
flushCache="true",實(shí)現(xiàn)先清除本地緩存搪搏。

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.");
    }
    // 是否清除一級(jí)緩存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {//一級(jí)緩存不為空
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {//查詢數(shù)據(jù)庫
        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;
  }

上面的代碼就已經(jīng)得知了整個(gè)查詢數(shù)據(jù)庫的業(yè)務(wù)邏輯了锹漱。

  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;
  }

4.SimpleExecutor的doQuery方法詳解

  @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);
    }
  }

重點(diǎn)是StatementHandler接口,有點(diǎn)類似executor接口的實(shí)現(xiàn)慕嚷,這次是路由方式哥牍!

StatementHandler接口

這里的創(chuàng)建又回到了Configuration類,不虧是核心類喝检。

第一步:重點(diǎn)有個(gè)interceptorChain.pluginAll嗅辣,后續(xù)的分頁插件靠這個(gè)啦。

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

// 路由Statement處理器
  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

// 父類的構(gòu)造方法
  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

第二步:采用BaseStatementHandler的構(gòu)造方法

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();
    // BoundSql 占位符? 動(dòng)態(tài)其他參數(shù)
    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
    // 重點(diǎn)方法挠说,參數(shù)處理器和結(jié)果集處理器澡谭。
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

// Configuration的創(chuàng)建參數(shù)處理器:DefaultParameterHandler
 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

// Configuration的創(chuàng)建結(jié)構(gòu)集處理器:DefaultResultSetHandler
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

又有Configuration對(duì)象,這個(gè)Configuration對(duì)象完全是貫穿整個(gè)Mybatis核心業(yè)務(wù)损俭。
執(zhí)行完方法后蛙奖,生成StatementHandler實(shí)現(xiàn)類

第三步:SimpleExecutor的prepareStatement方法

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
 // 執(zhí)行查詢
handler.<E>queryCursor(stmt);

// 執(zhí)行數(shù)據(jù)庫語句
@Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleCursorResultSets(ps);
  }
image.png

這里才回顧下jdbc基本操作


jdbc查詢數(shù)據(jù)

mybatis采用的是動(dòng)態(tài)代理:PreparedStatementLogger


PreparedStatementLogger動(dòng)態(tài)代理

引出:ResultSetLogger代理類得到ResultSet結(jié)果集。


DefaultResultSetHandler
ResultSetWrapper包裝結(jié)果集
//DefaultResultSetHandler處理結(jié)果集
 @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    // 獲取結(jié)果包裝類
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 獲取ResultMap映射
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 校驗(yàn)判斷rsw不為空且resultMapCount不小于1
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 處理結(jié)果集
      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);
  }
validateResultMapsCount
handleResultSet

處理結(jié)果集

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else { //是有一個(gè)resultMap映射時(shí)
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    // 邏輯分頁
    skipRows(rsw.getResultSet(), rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      // 獲取結(jié)果--核心
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      // 存儲(chǔ)結(jié)果
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
// 邏輯分頁
 private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
      }
    }
  }

//處理resultMap杆兵,實(shí)際就是判斷Discriminator鑒別器雁仲,discriminator – 使用結(jié)果值來決定使用哪個(gè) resultMap
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
    Set<String> pastDiscriminators = new HashSet<String>();
    Discriminator discriminator = resultMap.getDiscriminator();
    while (discriminator != null) {
      final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
      final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
      if (configuration.hasResultMap(discriminatedMapId)) {
        resultMap = configuration.getResultMap(discriminatedMapId);
        Discriminator lastDiscriminator = discriminator;
        discriminator = resultMap.getDiscriminator();
        if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
          break;
        }
      } else {
        break;
      }
    }
    return resultMap;
  }

自動(dòng)映射類型

AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior()
Configuration默認(rèn)的是AutoMappingBehavior.PARTIAL。

有三種自動(dòng)映射等級(jí):
NONE - 禁用自動(dòng)映射琐脏。僅設(shè)置手動(dòng)映射屬性攒砖。
PARTIAL - 將自動(dòng)映射結(jié)果除了那些有內(nèi)部定義內(nèi)嵌結(jié)果映射的(joins).
FULL - 自動(dòng)映射所有缸兔。
默認(rèn)值是PARTIAL,這是有原因的吹艇。當(dāng)使用FULL時(shí)惰蜜,自動(dòng)映射會(huì)在處理join結(jié)果時(shí)執(zhí)行,并且join取得若干相同行的不同實(shí)體數(shù)據(jù)受神,因此這可能導(dǎo)致非預(yù)期的映射抛猖。

獲取行Value
 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        // 賦值,如果value屬性不一致鼻听,容易出現(xiàn)錯(cuò)誤   
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

// 核心
  private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) {
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERED;
    } else {
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }

BaseTypeHandler-->IntegerTypeHandler獲取具體的值類型


image.png
image.png
image.png
image.png

這里用到了Mybatis的org.apache.ibatis.reflection.Reflector

image.png

image.png

最后返回multipleResults得到查詢結(jié)構(gòu)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末财著,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子精算,更是在濱河造成了極大的恐慌,老刑警劉巖碎连,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灰羽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鱼辙,警方通過查閱死者的電腦和手機(jī)廉嚼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倒戏,“玉大人怠噪,你說我怎么就攤上這事《捧危” “怎么了傍念?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長葛闷。 經(jīng)常有香客問我憋槐,道長,這世上最難降的妖魔是什么淑趾? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任阳仔,我火速辦了婚禮,結(jié)果婚禮上扣泊,老公的妹妹穿的比我還像新娘近范。我一直安慰自己,他們只是感情好延蟹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布评矩。 她就那樣靜靜地躺著,像睡著了一般阱飘。 火紅的嫁衣襯著肌膚如雪稚照。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音果录,去河邊找鬼上枕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弱恒,可吹牛的內(nèi)容都是我干的辨萍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼返弹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼锈玉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起义起,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤拉背,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后默终,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椅棺,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年齐蔽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了两疚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡含滴,死狀恐怖诱渤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谈况,我是刑警寧澤勺美,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站碑韵,受9級(jí)特大地震影響励烦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泼诱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一坛掠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧治筒,春花似錦屉栓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堤框,卻和暖如春域滥,著一層夾襖步出監(jiān)牢的瞬間纵柿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國打工启绰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留昂儒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓委可,卻偏偏與公主長得像渊跋,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子着倾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容