刘耀文

刘耀文

java开发者
github

Mybatis[plus] Source Code Reading Notes

Overview of the Calling Process:#

MyBatis originated from iBATIS, and the native iBATIS executes CRUD operations as fixed operations, using methods in the SQLSession interface for adding, deleting, querying, and modifying. We first read the code for the iBATIS part, and later MyBatis [plus] expands on this by encapsulating MapperProxy and dynamic SQL conditions for preset SQL statements. Let's take a look at the core interface of iBATIS's Session:

public interface SqlSession extends Closeable {
    
  <E> List<E> selectList(String statement);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <T> Cursor<T> selectCursor(String statement);

  int insert(String statement);

  int update(String statement);

  int delete(String statement);

}

The four types of methods ultimately required in this interface are (selectList, selectMap, selectCursor), insert, delete, and update.

However, the actual SQL statements are executed by methods in the Executor.

public interface Executor {

  int update(MappedStatement ms, Object parameter) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
      CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
}

These are divided into query and update methods, ultimately executed by the actual subclass's doQuery and doUpdate methods, which are further handled by the StatementHandler object.

public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;

  void parameterize(Statement statement) throws SQLException;

  void batch(Statement statement) throws SQLException;

  int update(Statement statement) throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement) throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

You can see that it ultimately reaches the update or query methods.

image-20240611225027427

Thus, the calling logic is SqlSession->Executor->StatementHandler, where each SqlSession has one Executor, and each Executor has one StatementHandler (obtained from the configuration, wrapped by plugins, which will be discussed later).

Creation process of SqlSession:

image-20240611231131172

The core of this is Configuration, and the entire iBATIS revolves around this Configuration class, which is very important for creating Executor, ParameterHandler, ResultSetHandler, and saving MapperStatements.

1. First-Level Cache#

First-Level Cache

Exists within the executor, and each creation carries it, so there is always a first-level cache in each session: BaseExecutor.java

  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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    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--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

Before querying, it first checks the cache, where the key value is related to four aspects.

CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

It will only match when the SQL statement, parameters, pagination, and bound parameters are the same.

Second-Level Cache

Exists within the cache attribute of ms, and will only create cacheExecutor when cacheEnable in the configuration is true.

// Configuration.java
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : 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;
  }
// Default is true

At the same time, ms's useCache must be true to actually operate on ms's cache.

// CachingExecutor.java
  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);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
// Default is also true

As you can see, it will only operate on the cache when cache is not null, and the cache type is determined when parsing mapper.xml.

// XMLMapperBuilder.java  
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        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. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }
// There are many types

image

This article is updated synchronously to xLog by Mix Space The original link is https://me.liuyaowen.club/posts/default/Mybatisplus

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.