女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

源碼學(xué)習(xí)之MyBatis的底層查詢?cè)?/h1>

導(dǎo)讀

本文通過(guò)MyBatis一個(gè)低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查詢流程,從配置文件的解析到一個(gè)查詢的完整執(zhí)行過(guò)程詳細(xì)解讀MyBatis的一次查詢流程,通過(guò)本文可以詳細(xì)了解MyBatis的一次查詢過(guò)程。在平時(shí)的代碼編寫(xiě)中,發(fā)現(xiàn)了MyBatis一個(gè)低版本的bug(3.4.5之前的版本),由于現(xiàn)在很多工程中的版本都是低于3.4.5的,因此在這里用一個(gè)簡(jiǎn)單的例子復(fù)現(xiàn)問(wèn)題,并且從源碼角度分析MyBatis一次查詢的流程,讓大家了解MyBatis的查詢?cè)?/p>

01問(wèn)題現(xiàn)象 在今年的敏捷團(tuán)隊(duì)建設(shè)中,我通過(guò)Suite執(zhí)行器實(shí)現(xiàn)了一鍵自動(dòng)化單元測(cè)試。Juint除了Suite執(zhí)行器還有哪些執(zhí)行器呢?由此我的Runner探索之旅開(kāi)始了!

1.1 場(chǎng)景問(wèn)題復(fù)現(xiàn)

如下圖所示,在示例Mapper中,下面提供了一個(gè)方法queryStudents,從student表中查詢出符合查詢條件的數(shù)據(jù),入?yún)⒖梢詾閟tudent_name或者student_name的集合,示例中參數(shù)只傳入的是studentName的List集合

 List studentNames = new LinkedList<>();
 studentNames.add("lct");
 studentNames.add("lct2");
 condition.setStudentNames(studentNames);
  


        select * from student
        
            
                AND student_name IN
                
                    #{studentName, jdbcType=VARCHAR}
                 foreach>
             if>


            
                AND student_name = #{studentName, jdbcType=VARCHAR}
             if>
         where>
     select>


 mapper>

2.示例代碼

public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //1.獲取SqlSessionFactory對(duì)象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.獲取對(duì)象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3.獲取接口的代理類對(duì)象
        StudentDao mapper = sqlSession.getMapper(StudentDao.class);
        StudentCondition condition = new StudentCondition();
        List studentNames = new LinkedList<>();
        studentNames.add("lct");
        studentNames.add("lct2");
        condition.setStudentNames(studentNames);
        //執(zhí)行方法
        List students = mapper.queryStudents(condition);
    }

2.2.3 查詢過(guò)程分析

1.SqlSessionFactory的構(gòu)建

先看SqlSessionFactory的對(duì)象的創(chuàng)建過(guò)程

//1.獲取SqlSessionFactory對(duì)象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

代碼中首先通過(guò)調(diào)用SqlSessionFactoryBuilder中的build方法來(lái)獲取對(duì)象,進(jìn)入build方法

 public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

調(diào)用自身的build方法

bb27fc9c-484c-11ed-a3b6-dac502259ad0.png

圖1 build方法自身調(diào)用調(diào)試圖例

在這個(gè)方法里會(huì)創(chuàng)建一個(gè)XMLConfigBuilder的對(duì)象,用來(lái)解析傳入的MyBatis的配置文件,然后調(diào)用parse方法進(jìn)行解析

bb586698-484c-11ed-a3b6-dac502259ad0.png

圖2 parse解析入?yún)⒄{(diào)試圖例

在這個(gè)方法中,會(huì)從MyBatis的配置文件的根目錄中獲取xml的內(nèi)容,其中parser這個(gè)對(duì)象是一個(gè)XPathParser的對(duì)象,這個(gè)是專門(mén)用來(lái)解析xml文件的,具體怎么從xml文件中獲取到各個(gè)節(jié)點(diǎn)這里不再進(jìn)行講解。這里可以看到解析配置文件是從configuration這個(gè)節(jié)點(diǎn)開(kāi)始的,在MyBatis的配置文件中這個(gè)節(jié)點(diǎn)也是根節(jié)點(diǎn)

 

        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">



    
           
     properties>
然后將解析好的xml文件傳入parseConfiguration方法中,在這個(gè)方法中會(huì)獲取在配置文件中的各個(gè)節(jié)點(diǎn)的配置

bb98ffe6-484c-11ed-a3b6-dac502259ad0.png

圖3 解析配置調(diào)試圖例

以獲取mappers節(jié)點(diǎn)的配置來(lái)看具體的解析過(guò)程

 
        
     mappers>
進(jìn)入mapperElement方法
mapperElement(root.evalNode("mappers"));

bbb688ae-484c-11ed-a3b6-dac502259ad0.png

圖4 mapperElement方法調(diào)試圖例

看到MyBatis還是通過(guò)創(chuàng)建一個(gè)XMLMapperBuilder對(duì)象來(lái)對(duì)mappers節(jié)點(diǎn)進(jìn)行解析,在parse方法中

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }


  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

通過(guò)調(diào)用configurationElement方法來(lái)解析配置的每一個(gè)mapper文件

private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
  }
}

以解析mapper中的增刪改查的標(biāo)簽來(lái)看看是如何解析一個(gè)mapper文件的

進(jìn)入buildStatementFromContext方法

private void buildStatementFromContext(List list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

可以看到MyBatis還是通過(guò)創(chuàng)建一個(gè)XMLStatementBuilder對(duì)象來(lái)對(duì)增刪改查節(jié)點(diǎn)進(jìn)行解析,通過(guò)調(diào)用這個(gè)對(duì)象的parseStatementNode方法,在這個(gè)方法里會(huì)獲取到配置在這個(gè)標(biāo)簽下的所有配置信息,然后進(jìn)行設(shè)置

bbf41318-484c-11ed-a3b6-dac502259ad0.png

圖5 parseStatementNode方法調(diào)試圖例

解析完成以后,通過(guò)方法addMappedStatement將所有的配置都添加到一個(gè)MappedStatement中去,然后再將mappedstatement添加到configuration中去

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    resultSetTypeEnum, flushCache, useCache, resultOrdered, 
    keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

bc14bf64-484c-11ed-a3b6-dac502259ad0.png

圖6 增加解析完成的mapper方法調(diào)試圖例

可以看到一個(gè)mappedstatement中包含了一個(gè)增刪改查標(biāo)簽的詳細(xì)信息

bc7ef898-484c-11ed-a3b6-dac502259ad0.png

圖7 mappedstatement對(duì)象方法調(diào)試圖例

而一個(gè)configuration就包含了所有的配置信息,其中mapperRegistertry和mappedStatements

bcbddaea-484c-11ed-a3b6-dac502259ad0.png

圖8 config對(duì)象方法調(diào)試圖例

具體的流程

bcebfc40-484c-11ed-a3b6-dac502259ad0.png

圖9 SqlSessionFactory對(duì)象的構(gòu)建過(guò)程

2.SqlSession的創(chuàng)建過(guò)程

SqlSessionFactory創(chuàng)建完成以后,接下來(lái)看看SqlSession的創(chuàng)建過(guò)程

SqlSession sqlSession = sqlSessionFactory.openSession();

首先會(huì)調(diào)用DefaultSqlSessionFactory的openSessionFromDataSource方法

@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

在這個(gè)方法中,首先會(huì)從configuration中獲取DataSource等屬性組成對(duì)象Environment,利用Environment內(nèi)的屬性構(gòu)建一個(gè)事務(wù)對(duì)象TransactionFactory

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    final Executor executor = configuration.newExecutor(tx, execType);
    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();
  }
}

事務(wù)創(chuàng)建完成以后開(kāi)始創(chuàng)建Executor對(duì)象,Executor對(duì)象的創(chuàng)建是根據(jù) executorType創(chuàng)建的,默認(rèn)是SIMPLE類型的,沒(méi)有配置的情況下創(chuàng)建了SimpleExecutor,如果開(kāi)啟二級(jí)緩存的話,則會(huì)創(chuàng)建CachingExecutor

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

創(chuàng)建executor以后,會(huì)執(zhí)行executor = (Executor) interceptorChain.pluginAll(executor)方法,這個(gè)方法對(duì)應(yīng)的含義是使用每一個(gè)攔截器包裝并返回executor,最后調(diào)用DefaultSqlSession方法創(chuàng)建SqlSession

bd01a34c-484c-11ed-a3b6-dac502259ad0.png

圖10 SqlSession對(duì)象的創(chuàng)建過(guò)程

3.Mapper的獲取過(guò)程

有了SqlSessionFactory和SqlSession以后,就需要獲取對(duì)應(yīng)的Mapper,并執(zhí)行mapper中的方法

StudentDao mapper = sqlSession.getMapper(StudentDao.class);

在第一步中知道所有的mapper都放在MapperRegistry這個(gè)對(duì)象中,因此通過(guò)調(diào)用org.apache.ibatis.binding.MapperRegistry#getMapper方法來(lái)獲取對(duì)應(yīng)的mapper

public  T getMapper(Class type, SqlSession sqlSession) {
  final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
  }
}

在MyBatis中,所有的mapper對(duì)應(yīng)的都是一個(gè)代理類,獲取到mapper對(duì)應(yīng)的代理類以后執(zhí)行newInstance方法,獲取到對(duì)應(yīng)的實(shí)例,這樣就可以通過(guò)這個(gè)實(shí)例進(jìn)行方法的調(diào)用

public class MapperProxyFactory {


  private final Class mapperInterface;
  private final Map methodCache = new ConcurrentHashMap();


  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }


  public Class getMapperInterface() {
    return mapperInterface;
  }


  public Map getMethodCache() {
    return methodCache;
  }


  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }


  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }


}

獲取mapper的流程為

bd1adefc-484c-11ed-a3b6-dac502259ad0.png

圖11 Mapper的獲取過(guò)程

4.查詢過(guò)程

獲取到mapper以后,就可以調(diào)用具體的方法

//執(zhí)行方法
List students = mapper.queryStudents(condition);

首先會(huì)調(diào)用org.apache.ibatis.binding.MapperProxy#invoke的方法,在這個(gè)方法中,會(huì)調(diào)用org.apache.ibatis.binding.MapperMethod#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;
}

首先根據(jù)SQL的類型增刪改查決定執(zhí)行哪個(gè)方法,在此執(zhí)行的是SELECT方法,在SELECT中根據(jù)方法的返回值類型決定執(zhí)行哪個(gè)方法,可以看到在select中沒(méi)有selectone單獨(dú)方法,都是通過(guò)selectList方法,通過(guò)調(diào)用org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)方法來(lái)獲取到數(shù)據(jù)

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

在selectList中,首先從configuration對(duì)象中獲取MappedStatement,在statement中包含了Mapper的相關(guān)信息,然后調(diào)用org.apache.ibatis.executor.CachingExecutor#query()方法

bd79d010-484c-11ed-a3b6-dac502259ad0.png

圖12 query()方法調(diào)試圖示

在這個(gè)方法中,首先對(duì)SQL進(jìn)行解析根據(jù)入?yún)⒑驮糞QL,對(duì)SQL進(jìn)行拼接

bdc8acf8-484c-11ed-a3b6-dac502259ad0.png

圖13 SQL拼接過(guò)程代碼圖示

調(diào)用MapperedStatement里的getBoundSql最終解析出來(lái)的SQL為

bde17670-484c-11ed-a3b6-dac502259ad0.png

圖14 SQL拼接過(guò)程結(jié)果圖示

接下來(lái)調(diào)用org.apache.ibatis.parsing.GenericTokenParser#parse對(duì)解析出來(lái)的SQL進(jìn)行解析

be180d5c-484c-11ed-a3b6-dac502259ad0.png

圖15 SQL解析過(guò)程圖示

最終解析的結(jié)果為

be404ad8-484c-11ed-a3b6-dac502259ad0.png

圖16 SQL解析結(jié)果圖示

最后會(huì)調(diào)用SimpleExecutor中的doQuery方法,在這個(gè)方法中,會(huì)獲取StatementHandler,然后調(diào)用org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize這個(gè)方法進(jìn)行參數(shù)和SQL的處理,最后調(diào)用statement的execute方法獲取到結(jié)果集,然后 利用resultHandler對(duì)結(jié)進(jìn)行處理

bef01c9c-484c-11ed-a3b6-dac502259ad0.png

圖17 SQL處理結(jié)果圖示

查詢的主要流程為

bf1a73a2-484c-11ed-a3b6-dac502259ad0.png

bf2f3a6c-484c-11ed-a3b6-dac502259ad0.png

圖18 查詢流程處理圖示

5.查詢流程總結(jié)

總結(jié)整個(gè)查詢流程如下

bf749d46-484c-11ed-a3b6-dac502259ad0.png

圖19 查詢流程抽象

2.3場(chǎng)景問(wèn)題原因及解決方案

2.3.1 個(gè)人排查

這個(gè)問(wèn)bug出現(xiàn)的地方在于綁定SQL參數(shù)的時(shí)候再源碼中位置為

 @Override
 public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameter);
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

由于所寫(xiě)的SQL是一個(gè)動(dòng)態(tài)綁定參數(shù)的SQL,因此最終會(huì)走到org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql這個(gè)方法中去

public BoundSql getBoundSql(Object parameterObject) {
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  List parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings == null || parameterMappings.isEmpty()) {
    boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
  }


  // check for nested result maps in parameter mappings (issue #30)
  for (ParameterMapping pm : boundSql.getParameterMappings()) {
    String rmId = pm.getResultMapId();
    if (rmId != null) {
      ResultMap rm = configuration.getResultMap(rmId);
      if (rm != null) {
        hasNestedResultMaps |= rm.hasNestedResultMaps();
      }
    }
  }


  return boundSql;
}

在這個(gè)方法中,會(huì)調(diào)用 rootSqlNode.apply(context)方法,由于這個(gè)標(biāo)簽是一個(gè)foreach標(biāo)簽,因此這個(gè)apply方法會(huì)調(diào)用到org.apache.ibatis.scripting.xmltags.ForEachSqlNode#apply這個(gè)方法中去

@Override
public boolean apply(DynamicContext context) {
  Map bindings = context.getBindings();
  final Iterable  iterable = evaluator.evaluateIterable(collectionExpression, bindings);
  if (!iterable.iterator().hasNext()) {
    return true;
  }
  boolean first = true;
  applyOpen(context);
  int i = 0;
  for (Object o : iterable) {
    DynamicContext oldContext = context;
    if (first) {
      context = new PrefixedContext(context, "");
    } else if (separator != null) {
      context = new PrefixedContext(context, separator);
    } else {
        context = new PrefixedContext(context, "");
    }
    int uniqueNumber = context.getUniqueNumber();
    // Issue #709 
    if (o instanceof Map.Entry) {
      @SuppressWarnings("unchecked") 
      Map.Entry mapEntry = (Map.Entry) o;
      applyIndex(context, mapEntry.getKey(), uniqueNumber);
      applyItem(context, mapEntry.getValue(), uniqueNumber);
    } else {
      applyIndex(context, i, uniqueNumber);
      applyItem(context, o, uniqueNumber);
    }
    contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
    if (first) {
      first = !((PrefixedContext) context).isPrefixApplied();
    }
    context = oldContext;
    i++;
  }
  applyClose(context);
  return true;
}

當(dāng)調(diào)用appItm方法的時(shí)候?qū)?shù)進(jìn)行綁定,參數(shù)的變量問(wèn)題都會(huì)存在bindings這個(gè)參數(shù)中區(qū)

private void applyItem(DynamicContext context, Object o, int i) {
  if (item != null) {
    context.bind(item, o);
    context.bind(itemizeItem(item, i), o);
  }
}

進(jìn)行綁定參數(shù)的時(shí)候,綁定完成foreach的方法的時(shí)候,可以看到bindings中不止綁定了foreach中的兩個(gè)參數(shù)還額外有一個(gè)參數(shù)名字studentName->lct2,也就是說(shuō)最后一個(gè)參數(shù)也是會(huì)出現(xiàn)在bindings這個(gè)參數(shù)中的,

private void applyItem(DynamicContext context, Object o, int i) {
  if (item != null) {
    context.bind(item, o);
    context.bind(itemizeItem(item, i), o);
  }
}

bf86da60-484c-11ed-a3b6-dac502259ad0.png

圖20 參數(shù)綁定過(guò)程

最后判定

org.apache.ibatis.scripting.xmltags.IfSqlNode#apply

@Override
public boolean apply(DynamicContext context) {
  if (evaluator.evaluateBoolean(test, context.getBindings())) {
    contents.apply(context);
    return true;
  }
  return false;
}

可以看到在調(diào)用evaluateBoolean方法的時(shí)候會(huì)把context.getBindings()就是前邊提到的bindings參數(shù)傳入進(jìn)去,因?yàn)楝F(xiàn)在這個(gè)參數(shù)中有一個(gè)studentName,因此在使用Ognl表達(dá)式的時(shí)候,判定為這個(gè)if標(biāo)簽是有值的因此將這個(gè)標(biāo)簽進(jìn)行了解析

bfb17dba-484c-11ed-a3b6-dac502259ad0.png

圖21 單個(gè)參數(shù)綁定過(guò)程

最終綁定的結(jié)果為

c015c9be-484c-11ed-a3b6-dac502259ad0.png

圖22 全部參數(shù)綁定過(guò)程

因此這個(gè)地方綁定參數(shù)的地方是有問(wèn)題的,至此找出了問(wèn)題的所在。

2.3.2 官方解釋

翻閱MyBatis官方文檔進(jìn)行求證,發(fā)現(xiàn)在3.4.5版本發(fā)行中bug fixes中有這樣一句

c05977d6-484c-11ed-a3b6-dac502259ad0.png

圖23 此問(wèn)題官方修復(fù)github記錄

修復(fù)了foreach版本中對(duì)于全局變量context的修改的bug

issue地址為https://github.com/mybatis/mybatis-3/pull/966

修復(fù)方案為https://github.com/mybatis/mybatis-3/pull/966/commits/84513f915a9dcb97fc1d602e0c06e11a1eef4d6a

可以看到官方給出的修改方案,重新定義了一個(gè)對(duì)象,分別存儲(chǔ)全局變量和局部變量,這樣就會(huì)解決foreach會(huì)改變?nèi)肿兞康膯?wèn)題。

c07f0e10-484c-11ed-a3b6-dac502259ad0.png

圖24 此問(wèn)題官方修復(fù)代碼示例

2.3.3 修復(fù)方案

升級(jí)MyBatis版本至3.4.5以上

如果保持版本不變的話,在foreach中定義的變量名不要和外部的一致

03源碼閱讀過(guò)程總結(jié) 理解,首先 MCube 會(huì)依據(jù)模板緩存狀態(tài)判斷是否需要網(wǎng)絡(luò)獲取最新模板,當(dāng)獲取到模板后進(jìn)行模板加載,加載階段會(huì)將產(chǎn)物轉(zhuǎn)換為視圖樹(shù)的結(jié)構(gòu),轉(zhuǎn)換完成后將通過(guò)表達(dá)式引擎解析表達(dá)式并取得正確的值,通過(guò)事件解析引擎解析用戶自定義事件并完成事件的綁定,完成解析賦值以及事件綁定后進(jìn)行視圖的渲染,最終將目標(biāo)頁(yè)面展示到屏幕。

MyBatis源代碼的目錄是比較清晰的,基本上每個(gè)相同功能的模塊都在一起,但是如果直接去閱讀源碼的話,可能還是有一定的難度,沒(méi)法理解它的運(yùn)行過(guò)程,本次通過(guò)一個(gè)簡(jiǎn)單的查詢流程從頭到尾跟下來(lái),可以看到MyBatis的設(shè)計(jì)以及處理流程,例如其中用到的設(shè)計(jì)模式:

c0bbc3fa-484c-11ed-a3b6-dac502259ad0.png

圖25 MyBatis代碼結(jié)構(gòu)圖

組合模式:如ChooseSqlNode,IfSqlNode等

模板方法模式:例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler

Builder模式:例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder

工廠模式:例如SqlSessionFactory、ObjectFactory、MapperProxyFactory

代理模式:MyBatis實(shí)現(xiàn)的核心,比如MapperProxy、ConnectionLogger

04文檔參考

https://mybatis.org/mybatis-3/zh/index.html

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    667

    瀏覽量

    30134
  • BUG
    BUG
    +關(guān)注

    關(guān)注

    0

    文章

    156

    瀏覽量

    15952
  • Suite
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    8207
  • mybatis
    +關(guān)注

    關(guān)注

    0

    文章

    63

    瀏覽量

    6867
收藏 人收藏

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    京東中臺(tái)化底層支撐框架技術(shù)分析及隨想

    架構(gòu)涉及的變化和影響,只是從中臺(tái)化演進(jìn)的思路,及使用的底層支撐技術(shù)框架進(jìn)行分析探討,重點(diǎn)對(duì)中臺(tái)及前臺(tái)協(xié)作涉及到的擴(kuò)展點(diǎn)及熱部署包的底層技術(shù)細(xì)節(jié),結(jié)合京東實(shí)際落地情況,對(duì)涉及的核心技術(shù)框架進(jìn)行源碼初探分析,探討技術(shù)框架的考慮
    的頭像 發(fā)表于 04-08 11:29 ?218次閱讀
    京東中臺(tái)化<b class='flag-5'>底層</b>支撐框架技術(shù)分析及隨想

    底層開(kāi)發(fā)與應(yīng)用開(kāi)發(fā)到底怎么選?

    理解計(jì)算機(jī)系統(tǒng)的底層原理,掌握核心技術(shù)。 穩(wěn)定性與稀缺性:底層開(kāi)發(fā)人才相對(duì)稀缺,市場(chǎng)需求穩(wěn)定,薪資待遇通常較高。 行業(yè)基礎(chǔ):底層開(kāi)發(fā)是整個(gè)計(jì)算機(jī)技術(shù)的基石,對(duì)個(gè)人技術(shù)成長(zhǎng)有極大幫助。 5. 挑戰(zhàn)
    發(fā)表于 03-06 10:10

    IP地址查詢技術(shù)

    IP查詢****的價(jià)值 根據(jù)2023年國(guó)際互聯(lián)網(wǎng)數(shù)據(jù)中心統(tǒng)計(jì),全球每天產(chǎn)生的IP查詢請(qǐng)求超過(guò)50億次,這一數(shù)字就能夠清晰的看出廣大群眾對(duì)于IP查詢技術(shù)的需求以及它的價(jià)值。 而傳統(tǒng)IP查詢
    的頭像 發(fā)表于 02-12 11:13 ?328次閱讀
    IP地址<b class='flag-5'>查詢</b>技術(shù)

    什么是AI查詢引擎

    AI 查詢引擎可高效處理、存儲(chǔ)和檢索大量數(shù)據(jù),以增強(qiáng)生成式 AI 模型的輸入。
    的頭像 發(fā)表于 01-10 10:00 ?1116次閱讀

    超級(jí)大全貼片元件代碼查詢

    如題:超級(jí)大全貼片元件代碼查詢
    發(fā)表于 01-08 13:49 ?34次下載

    Mybatis 源碼暢談軟件設(shè)計(jì)(九):“能用就行” 其實(shí)遠(yuǎn)遠(yuǎn)不夠

    作者:京東保險(xiǎn) 王奕龍 到本節(jié) Mybatis 源碼中核心邏輯基本已經(jīng)介紹完了,在這里我想借助 Mybatis 其他部分源碼來(lái)介紹一些我認(rèn)為在編程中能 最快提高編碼質(zhì)量的小方法 ,它們
    的頭像 發(fā)表于 01-03 10:39 ?366次閱讀

    SSM框架的源碼解析與理解

    SSM框架(Spring + Spring MVC + MyBatis)是一種在Java開(kāi)發(fā)中常用的輕量級(jí)企業(yè)級(jí)應(yīng)用框架。它通過(guò)整合Spring、Spring MVC和MyBatis三個(gè)框架,實(shí)現(xiàn)了
    的頭像 發(fā)表于 12-17 09:20 ?822次閱讀

    源碼開(kāi)放 智能監(jiān)測(cè)電源管理教程寶典!

    源碼開(kāi)放,今天我們學(xué)習(xí)的是電源管理系統(tǒng)的核心功能模塊,手把手教你如何通過(guò)不同的技術(shù)手段實(shí)現(xiàn)有效的電源管理。
    的頭像 發(fā)表于 12-11 09:26 ?571次閱讀
    <b class='flag-5'>源碼</b>開(kāi)放  智能監(jiān)測(cè)電源管理教程寶典!

    根據(jù)ip地址查網(wǎng)頁(yè)怎么查詢

    一、通過(guò)命令提示符查詢查網(wǎng)頁(yè)(Windows系統(tǒng)) ①按“Win+R”鍵,打開(kāi)運(yùn)營(yíng)窗口。 根據(jù)ip地址查網(wǎng)頁(yè)怎么查詢? ②輸入“cmd”+“回車(chē)”,打開(kāi)命令提示符窗口。 ③輸入“nslookup
    的頭像 發(fā)表于 09-29 10:56 ?2005次閱讀
    根據(jù)ip地址查網(wǎng)頁(yè)怎么<b class='flag-5'>查詢</b>?

    【免費(fèi)領(lǐng)取】AI人工智能學(xué)習(xí)資料(學(xué)習(xí)路線圖+100余講課程+虛擬仿真平臺(tái)體驗(yàn)+項(xiàng)目源碼+AI論文)

    想要深入學(xué)習(xí)AI人工智能嗎?現(xiàn)在機(jī)會(huì)來(lái)了!我們?yōu)槌鯇W(xué)者們準(zhǔn)備了一份全面的資料包,包括學(xué)習(xí)路線、100余講視頻課程、AI在線實(shí)驗(yàn)平合體驗(yàn)、項(xiàng)目源碼、AI論文等,所有資料全部免費(fèi)領(lǐng)取。01完整學(xué)習(xí)
    的頭像 發(fā)表于 09-27 15:50 ?695次閱讀
    【免費(fèi)領(lǐng)取】AI人工智能<b class='flag-5'>學(xué)習(xí)</b>資料(<b class='flag-5'>學(xué)習(xí)</b>路線圖+100余講課程+虛擬仿真平臺(tái)體驗(yàn)+項(xiàng)目<b class='flag-5'>源碼</b>+AI論文)

    【免費(fèi)分享】嵌入式Linux開(kāi)發(fā)板【入門(mén)+項(xiàng)目,應(yīng)用+底層】資料包一網(wǎng)打盡,附教程/視頻/源碼...

    ?想要深入學(xué)習(xí)嵌入式Linux開(kāi)發(fā)嗎?現(xiàn)在機(jī)會(huì)來(lái)了!我們?yōu)槌鯇W(xué)者們準(zhǔn)備了一份全面的資料包,包括原理圖、教程、課件、視頻、項(xiàng)目、源碼等,所有資料全部免費(fèi)領(lǐng)取,課程視頻可試看(購(gòu)買(mǎi)后看完整版),讓你
    的頭像 發(fā)表于 09-05 10:45 ?552次閱讀
    【免費(fèi)分享】嵌入式Linux開(kāi)發(fā)板【入門(mén)+項(xiàng)目,應(yīng)用+<b class='flag-5'>底層</b>】資料包一網(wǎng)打盡,附教程/視頻/<b class='flag-5'>源碼</b>...

    北京迅為RK3568開(kāi)發(fā)板嵌入式學(xué)習(xí)Linux驅(qū)動(dòng)全新更新-CAN+

    北京迅為RK3568開(kāi)發(fā)板嵌入式學(xué)習(xí)Linux驅(qū)動(dòng)全新更新-CAN+
    的頭像 發(fā)表于 09-04 15:29 ?900次閱讀
    北京迅為RK3568開(kāi)發(fā)板嵌入式<b class='flag-5'>學(xué)習(xí)</b><b class='flag-5'>之</b>Linux驅(qū)動(dòng)全新更新-CAN+

    使用mybatis切片實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制

    一、使用方式 數(shù)據(jù)權(quán)限控制需要對(duì)查詢出的數(shù)據(jù)進(jìn)行篩選,對(duì)業(yè)務(wù)入侵最少的方式就是利用mybatis或者數(shù)據(jù)庫(kù)連接池的切片對(duì)已有業(yè)務(wù)的sql進(jìn)行修改。切片邏輯完成后,僅需要在業(yè)務(wù)中加入少量標(biāo)記代碼
    的頭像 發(fā)表于 07-09 17:26 ?641次閱讀
    使用<b class='flag-5'>mybatis</b>切片實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制

    UCGUI單片機(jī)源碼

    UCGUI單片機(jī)源碼
    發(fā)表于 07-04 17:11 ?1次下載

    labview實(shí)例源碼控壓取樣系統(tǒng)

    labview源碼,包含報(bào)表、曲線、通訊等
    發(fā)表于 06-06 11:23 ?1次下載