色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

詳細解讀MyBatis的一次查詢流程

OSC開源社區 ? 來源:京東技術 ? 作者:京東物流 李春廷 ? 2022-10-10 11:43 ? 次閱讀

導讀

本文通過MyBatis一個低版本的bug(3.4.5之前的版本)入手,分析MyBatis的一次完整的查詢流程,從配置文件的解析到一個查詢的完整執行過程詳細解讀MyBatis的一次查詢流程,通過本文可以詳細了解MyBatis的一次查詢過程。在平時的代碼編寫中,發現了MyBatis一個低版本的bug(3.4.5之前的版本),由于現在很多工程中的版本都是低于3.4.5的,因此在這里用一個簡單的例子復現問題,并且從源碼角度分析MyBatis一次查詢的流程,讓大家了解MyBatis的查詢原理

01 問題現象

在今年的敏捷團隊建設中,我通過Suite執行器實現了一鍵自動化單元測試。Juint除了Suite執行器還有哪些執行器呢?由此我的Runner探索之旅開始了!

1.1 場景問題復現

如下圖所示,在示例Mapper中,下面提供了一個方法queryStudents,從student表中查詢出符合查詢條件的數據,入參可以為student_name或者student_name的集合,示例中參數只傳入的是studentName的List集合

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

期望運行的結果是
select * from student WHERE student_name IN ( 'lct' , 'lct2' )
但是實際上運行的結果是

==> Preparing: select * from student WHERE student_name IN ( ? , ? ) AND student_name = ?

==> Parameters: lct(String), lct2(String), lct2(String)

<== Columns: id, student_name, age

<== Row: 2, lct2, 2

<== Total: 1

通過運行結果可以看到,沒有給student_name單獨賦值,但是經過MyBatis解析以后,單獨給student_name賦值了一個值,可以推斷出MyBatis在解析SQL并對變量賦值的時候是有問題的,初步猜測是foreach循環中的變量的值帶到了foreach外邊,導致SQL解析出現異常,下面通過源碼進行分析驗證

02 MyBatis查詢原理

理解,首先 MCube 會依據模板緩存狀態判斷是否需要網絡獲取最新模板,當獲取到模板后進行模板加載,加載階段會將產物轉換為視圖樹的結構,轉換完成后將通過表達式引擎解析表達式并取得正確的值,通過事件解析引擎解析用戶自定義事件并完成事件的綁定,完成解析賦值以及事件綁定后進行視圖的渲染,最終將目標頁面展示到屏幕。

2.1 MyBatis架構

2.1.1 架構圖

先簡單來看看MyBatis整體上的架構模型,從整體上看MyBatis主要分為四大模塊:

接口:主要作用就是和數據庫打交道

數據處理層:數據處理層可以說是MyBatis的核心,它要完成兩個功能:

通過傳入參數構建動態SQL語句;

SQL語句的執行以及封裝查詢結果集成List

框架支撐層:主要有事務管理、連接池管理、緩存機制和SQL語句的配置方式

引導層:引導層是配置和啟動MyBatis 配置信息的方式。MyBatis 提供兩種方式來引導MyBatis :基于XML配置文件的方式和基于Java API 的方式

2.1.2 MyBatis四大對象

貫穿MyBatis整個框架的有四大核心對象,ParameterHandler、ResultSetHandler、StatementHandler和Executor,四大對象貫穿了整個框架的執行過程,四大對象的主要作用為:

ParameterHandler:設置預編譯參數

ResultSetHandler:處理SQL的返回結果集

StatementHandler:處理sql語句預編譯,設置參數等相關工作

Executor:MyBatis的執行器,用于執行增刪改查操作

2.2從源碼解讀MyBatis的一次查詢過程

首先給出復現問題的代碼以及相應的準備過程

2.2.1 數據準備

CREATE TABLE `student`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `student_name` varchar(255) NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1;


-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, 'lct', 1);
INSERT INTO `student` VALUES (2, 'lct2', 2);

2.2.2 代碼準備

1.mapper配置文件

2.示例代碼

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

2.2.3 查詢過程分析

1.SqlSessionFactory的構建

先看SqlSessionFactory的對象的創建過程

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

代碼中首先通過調用SqlSessionFactoryBuilder中的build方法來獲取對象,進入build方法

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

調用自身的build方法

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

圖1 build方法自身調用調試圖例

在這個方法里會創建一個XMLConfigBuilder的對象,用來解析傳入的MyBatis的配置文件,然后調用parse方法進行解析

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

圖2 parse解析入參調試圖例

在這個方法中,會從MyBatis的配置文件的根目錄中獲取xml的內容,其中parser這個對象是一個XPathParser的對象,這個是專門用來解析xml文件的,具體怎么從xml文件中獲取到各個節點這里不再進行講解。這里可以看到解析配置文件是從configuration這個節點開始的,在MyBatis的配置文件中這個節點也是根節點

然后將解析好的xml文件傳入parseConfiguration方法中,在這個方法中會獲取在配置文件中的各個節點的配置

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

圖3 解析配置調試圖例

以獲取mappers節點的配置來看具體的解析過程

進入mapperElement方法
mapperElement(root.evalNode("mappers"));

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

圖4 mapperElement方法調試圖例

看到MyBatis還是通過創建一個XMLMapperBuilder對象來對mappers節點進行解析,在parse方法中

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


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

通過調用configurationElement方法來解析配置的每一個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中的增刪改查的標簽來看看是如何解析一個mapper文件的

進入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還是通過創建一個XMLStatementBuilder對象來對增刪改查節點進行解析,通過調用這個對象的parseStatementNode方法,在這個方法里會獲取到配置在這個標簽下的所有配置信息,然后進行設置

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

圖5 parseStatementNode方法調試圖例

解析完成以后,通過方法addMappedStatement將所有的配置都添加到一個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方法調試圖例

可以看到一個mappedstatement中包含了一個增刪改查標簽的詳細信息

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

圖7 mappedstatement對象方法調試圖例

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

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

圖8 config對象方法調試圖例

具體的流程

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

圖9 SqlSessionFactory對象的構建過程

2.SqlSession的創建過程

SqlSessionFactory創建完成以后,接下來看看SqlSession的創建過程

SqlSession sqlSession = sqlSessionFactory.openSession();

首先會調用DefaultSqlSessionFactory的openSessionFromDataSource方法

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

在這個方法中,首先會從configuration中獲取DataSource等屬性組成對象Environment,利用Environment內的屬性構建一個事務對象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();
  }
}

事務創建完成以后開始創建Executor對象,Executor對象的創建是根據 executorType創建的,默認是SIMPLE類型的,沒有配置的情況下創建了SimpleExecutor,如果開啟二級緩存的話,則會創建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;
}

創建executor以后,會執行executor = (Executor) interceptorChain.pluginAll(executor)方法,這個方法對應的含義是使用每一個攔截器包裝并返回executor,最后調用DefaultSqlSession方法創建SqlSession

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

圖10 SqlSession對象的創建過程

3.Mapper的獲取過程

有了SqlSessionFactory和SqlSession以后,就需要獲取對應的Mapper,并執行mapper中的方法

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

在第一步中知道所有的mapper都放在MapperRegistry這個對象中,因此通過調用org.apache.ibatis.binding.MapperRegistry#getMapper方法來獲取對應的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對應的都是一個代理類,獲取到mapper對應的代理類以后執行newInstance方法,獲取到對應的實例,這樣就可以通過這個實例進行方法的調用

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的獲取過程

4.查詢過程

獲取到mapper以后,就可以調用具體的方法

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

首先會調用org.apache.ibatis.binding.MapperProxy#invoke的方法,在這個方法中,會調用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;
}

首先根據SQL的類型增刪改查決定執行哪個方法,在此執行的是SELECT方法,在SELECT中根據方法的返回值類型決定執行哪個方法,可以看到在select中沒有selectone單獨方法,都是通過selectList方法,通過調用org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)方法來獲取到數據

@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對象中獲取MappedStatement,在statement中包含了Mapper的相關信息,然后調用org.apache.ibatis.executor.CachingExecutor#query()方法

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

圖12 query()方法調試圖示

在這個方法中,首先對SQL進行解析根據入參和原始SQL,對SQL進行拼接

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

圖13 SQL拼接過程代碼圖示

調用MapperedStatement里的getBoundSql最終解析出來的SQL為

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

圖14 SQL拼接過程結果圖示

接下來調用org.apache.ibatis.parsing.GenericTokenParser#parse對解析出來的SQL進行解析

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

圖15 SQL解析過程圖示

最終解析的結果為

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

圖16 SQL解析結果圖示

最后會調用SimpleExecutor中的doQuery方法,在這個方法中,會獲取StatementHandler,然后調用org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize這個方法進行參數和SQL的處理,最后調用statement的execute方法獲取到結果集,然后 利用resultHandler對結進行處理

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

圖17 SQL處理結果圖示

查詢的主要流程為

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

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

圖18 查詢流程處理圖示

5.查詢流程總結

總結整個查詢流程如下

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

圖19 查詢流程抽象

2.3場景問題原因及解決方案

2.3.1 個人排查

這個問bug出現的地方在于綁定SQL參數的時候再源碼中位置為

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

由于所寫的SQL是一個動態綁定參數的SQL,因此最終會走到org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql這個方法中去

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

在這個方法中,會調用 rootSqlNode.apply(context)方法,由于這個標簽是一個foreach標簽,因此這個apply方法會調用到org.apache.ibatis.scripting.xmltags.ForEachSqlNode#apply這個方法中去

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

當調用appItm方法的時候將參數進行綁定,參數的變量問題都會存在bindings這個參數中區

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

進行綁定參數的時候,綁定完成foreach的方法的時候,可以看到bindings中不止綁定了foreach中的兩個參數還額外有一個參數名字studentName->lct2,也就是說最后一個參數也是會出現在bindings這個參數中的,

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 參數綁定過程

最后判定

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

可以看到在調用evaluateBoolean方法的時候會把context.getBindings()就是前邊提到的bindings參數傳入進去,因為現在這個參數中有一個studentName,因此在使用Ognl表達式的時候,判定為這個if標簽是有值的因此將這個標簽進行了解析

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

圖21 單個參數綁定過程

最終綁定的結果為

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

圖22 全部參數綁定過程

因此這個地方綁定參數的地方是有問題的,至此找出了問題的所在。

2.3.2 官方解釋

翻閱MyBatis官方文檔進行求證,發現在3.4.5版本發行中bug fixes中有這樣一句

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

圖23 此問題官方修復github記錄

修復了foreach版本中對于全局變量context的修改的bug

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

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

可以看到官方給出的修改方案,重新定義了一個對象,分別存儲全局變量和局部變量,這樣就會解決foreach會改變全局變量的問題。

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

圖24 此問題官方修復代碼示例

2.3.3 修復方案

升級MyBatis版本至3.4.5以上

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

03 源碼閱讀過程總結

理解,首先 MCube 會依據模板緩存狀態判斷是否需要網絡獲取最新模板,當獲取到模板后進行模板加載,加載階段會將產物轉換為視圖樹的結構,轉換完成后將通過表達式引擎解析表達式并取得正確的值,通過事件解析引擎解析用戶自定義事件并完成事件的綁定,完成解析賦值以及事件綁定后進行視圖的渲染,最終將目標頁面展示到屏幕。

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

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

圖25 MyBatis代碼結構圖

組合模式:如ChooseSqlNode,IfSqlNode等

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

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

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

代理模式:MyBatis實現的核心,比如MapperProxy、ConnectionLogger





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2966

    瀏覽量

    104702
  • API
    API
    +關注

    關注

    2

    文章

    1499

    瀏覽量

    61964
  • SQL
    SQL
    +關注

    關注

    1

    文章

    762

    瀏覽量

    44117

原文標題:源碼學習之MyBatis的底層查詢原理

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    文了解MyBatis查詢原理

    本文通過MyBatis個低版本的bug(3.4.5之前的版本)入手,分析MyBatis一次完整的查詢
    的頭像 發表于 10-10 11:42 ?1422次閱讀

    一次呼叫典型流

    本章將分別給出主叫流程和被叫流程的例子,以示一次通話過程中UTRAN的典型流程。B.2 主叫流程B.3 被叫
    發表于 05-30 17:25 ?1次下載

    一次呼叫典型流程

    一次呼叫典型流程:一次呼叫典型流程B.1 概述本章將分別給出主叫流程和被叫流程的例子,以示
    發表于 11-28 17:57 ?26次下載

    一次電池為什么不能被充電?

    一次電池為什么不能被充電? 一次電池不能被充電再生是構成一次電池體系的本性所決定的,因為一次電池的電極反應不可逆,也就是說,放電后的放電產
    發表于 10-28 15:29 ?5850次閱讀

    循環充放電一次就是少一次壽命嗎?

    循環充放電一次就是少一次壽命嗎?     循環就是使用,我們是在使用電池,關心的是使
    發表于 11-11 13:59 ?849次閱讀

    電池循環充放電一次就是少一次壽命嗎?

    電池循環充放電一次就是少一次壽命嗎? 循環就是使用,我們是在使用電池,關心的是使用的時間,為了衡量充電電池
    發表于 09-06 11:05 ?3594次閱讀

    Mybatis緩存之級緩存

    本文主要講mybatis級緩存,級緩存是SqlSession級別的緩存。mybatis提供查詢緩存,用于減輕數據壓力,提高數據庫性能。
    發表于 11-27 20:44 ?1226次閱讀
    <b class='flag-5'>Mybatis</b>緩存之<b class='flag-5'>一</b>級緩存

    mybatis快速入門

    本文詳細介紹了mybatis相關知識,以及mybatis快速入門步驟詳解。
    的頭像 發表于 02-24 09:41 ?3522次閱讀
    <b class='flag-5'>mybatis</b>快速入門

    mybatis對多配置

    本文詳細介紹了mybatis關聯配置(對多配置)。
    發表于 02-24 13:53 ?1493次閱讀

    Fluent Mybatis、原生MybatisMybatis Plus對比

    使用fluent mybatis可以不用寫具體的xml文件,通過java api可以構造出比較復雜的業務sql語句,做到代碼邏輯和sql邏輯的合。不再需要在Dao中組裝查詢或更新操作,在xml或
    的頭像 發表于 09-15 15:41 ?1431次閱讀

    源碼學習之MyBatis的底層查詢原理

    本文通過MyBatis個低版本的bug(3.4.5之前的版本)入手,分析MyBatis一次完整的查詢
    的頭像 發表于 10-10 11:42 ?794次閱讀

    電氣一次識圖基礎

    電氣一次識圖基礎
    的頭像 發表于 11-12 11:24 ?2117次閱讀

    MyBatis-Plus為什么不支持聯表

    MyBatis Plus Join`款專門解決MyBatis Plus 關聯查詢問題的擴展框架,他并不款全新的框架,而是基于`
    的頭像 發表于 02-28 15:19 ?2451次閱讀
    <b class='flag-5'>MyBatis</b>-Plus為什么不支持聯表

    SpringBoot+Mybatis如何實現流式查詢

    使用mybatis作為持久層的框架時,通過mybatis執行查詢數據的請求執行成功后,mybatis返回的結果集不是個集合或對象,而是
    的頭像 發表于 06-12 09:57 ?1269次閱讀

    一次調頻和二調頻的概念 一次調頻可以實現無差調節?

    一次調頻和二調頻的概念 一次調頻可以實現無差調節? 一次調頻和二調頻的概念 1.
    的頭像 發表于 10-17 16:15 ?9321次閱讀
    主站蜘蛛池模板: 国产精品视频成人| 7m凹凸国产刺激在线视频| 亚洲精品自在在线观看| 亚洲女人毛片| 伊人最新网址| 51久久夜色精品国产| 亚洲中文在线偷拍| 91精品福利一区二区| 扒开她的黑森林让我添动态图| 闺蜜扒开我尿口使劲揉| 韩国成人理伦片免费播放| 久久热免费观看视频| 欧美最猛性XXXXX肛交| 无码日韩人妻精品久久蜜桃免费 | 美女扒开腿让男生桶爽免费APP| 免费在线亚洲视频| 肉肉描写很细致的黄文| 亚洲欧美中文字幕网站大全| 88福利视频| 国产成人免费高清视频| 精品亚洲大全| 欧美高清另类video| 西施打开双腿下面好紧| 国产一级特黄aa毛片| 国产精品亚洲精品久久品| 久9视频这里只有精品123| 暖暖视频免费观看视频| 午夜日本大胆裸艺术| 中字幕视频在线永久在线观看免费| 成人AV无码一二二区视频免费看| 国内精品蜜汁乔依琳视频| 免费精品一区二区三区AA片| 午夜国产高清精品一区免费| 2012中文字幕在线动漫电影| 国产黄片毛片| 美女张开腿让男生桶动态图| 性色AV一区二区三区咪爱四虎| 56prom在线精品国产| 国产啪视频在线播放观看| 免费无码又爽又黄又刺激网站| 学生精品国产在线视频|