Preliminary analysis of Mybatis source code (3) Complete

Following the above mentioned, let’s look for the third part, how the sql statement is executed.
Before that I need to clarify some issues.
When constructing sqlseesion, we clearly remember to pass in several parameters, including executor, configuration, autocommit, etc.
Regarding executor, we need to analyze this thing. Actuator.
Click on Executor

public interface Executor {
    
    

  ResultHandler NO_RESULT_HANDLER = null;

  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;

  List<BatchResult> flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

We clearly see that this is an interface, and then we see some implementation diagrams of this interface.
Insert image description here
Receive the above to construct the executor

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

Obviously when constructing sqlseesion, an executor is constructed first, click on the method to see

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

It was found that there are three defined types, and there is a cacheEnabled field, which means that this field will be new only if it is true. This field is the default setting of caching.

public enum ExecutorType {
    
    
  SIMPLE, REUSE, BATCH
}

So naturally there are three executors. They all inherit from baseExecutor
and call simpleexecutor by default. Take a look at this class.

/*
 *    Copyright 2009-2021 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

/**
 * @author Clinton Begin
 */
public class SimpleExecutor extends BaseExecutor {
    
    

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    
    
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    
    
    Statement stmt = null;
    try {
    
    
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
    
    
      closeStatement(stmt);
    }
  }

  @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.query(stmt, resultHandler);
    } finally {
    
    
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    
    
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    
    
    return Collections.emptyList();
  }

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

}

There are many familiar methods in it, such as doquery, doupdate, connection and a series of jdbc things. So it is a reasonable guess that the part we want to verify is probably related to this.
Let's get back to the topic, in our first part, parse config.xml through xmlconfigbuilder's parseConfiguration and put the parsed value into the configuration class to obtain the association with the database.
Then a default sqlsessionfactory is built through the configuration class, and then opensession constructs a default sqlsession through executor, autocommit, configuration, etc. as parameters, and then passes sqlsession.getmapper (configuration.getmapper—>mapperregister.getmapper—>getmapper()) Obtain a proxy instance of mapperProxy, and then call the execute method in the invoke method, which executes the selectone method through keyword matching, and then there is a getBoundSql to obtain the sql statement.
The next step is to verify how to execute this sql statement. Without looking further, you should understand that it must be an executor to complete these things. The default is simpleexecutor.

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

When sql is obtained, the createCacheKey method is obviously creating a cache key, because we say that the mybatis first-level cache is enabled by default. The reasons are also mentioned above and are reflected in the source code.
Take a look at this method

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    
    
    if (closed) {
    
    
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
    
    
      if (parameterMapping.getMode() != ParameterMode.OUT) {
    
    
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
    
    
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
    
    
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    
    
          value = parameterObject;
        } else {
    
    
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
    
    
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

Obviously, it is to save some of the content in boundsql just now into a new cachekey instance. The following query method:

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

It's easy to understand. Use the key to check whether there is anything in the localcache. If so, get it directly from the localcache.

  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    
    
    if (ms.getStatementType() == StatementType.CALLABLE) {
    
    
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
    
    
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
    
    
          if (parameterMapping.getMode() != ParameterMode.IN) {
    
    
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

If not then go the other way, queryFromDatabase

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

First store this key in the localcache, because a doQuery operation is about to be performed, and the things found by doQuery are matched with the key and stored in the localCache. Obviously this doQuery executes a sql statement.
Before that, let's take a look at what localCache is. According to speculation, it is a hashmap and satisfies the kv structure. Click on it and see that it is a PerpetualCache class.

public class PerpetualCache implements Cache {
    
    

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    
    
    this.id = id;
  }

  @Override
  public String getId() {
    
    
    return id;
  }

  @Override
  public int getSize() {
    
    
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    
    
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    
    
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    
    
    return cache.remove(key);
  }

  @Override
  public void clear() {
    
    
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    
    
    if (getId() == null) {
    
    
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
    
    
      return true;
    }
    if (!(o instanceof Cache)) {
    
    
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    
    
    if (getId() == null) {
    
    
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

Closer to home, let us continue to look at the doQuery method, which comes from BaseExecutor. We know that the default method is a simpleExecutor method.

  @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.query(stmt, resultHandler);
    } finally {
    
    
      closeStatement(stmt);
    }
  }

StatementHandler appears. What is this? It is a statement sql object. It is connected to jdbc. I'm so excited.

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

prepareStatement literally looks like a prepared statement, but is actually a precompiled SQL statement object that can be used to prevent SQL injection.
What is next, handler.query(stmt, resultHandler); When a query result appears, isn’t that the result set?
Click in, it is still org.apache.ibatis.executor.statement.SimpleStatementHandler simple

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    
    
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }

Get the sql from boundsql, use statement to execute the sql, return the result set, and the jdbc code appears.

At this point, the running process of mybatis that has been completed once is probably over, and my initial guess has been verified, and the task of executing sql statements is actually handed over to sqlseesion.

This is just a simple source code analysis, including many details that were not studied in detail, including how to open the second-level cache and wait for a series of issues, as well as batchexecutor and when to call refuseexecutor, etc. were not carefully explored.

But even through this simple reading, I also understood the advantages of excellent frameworks, as well as a lot of code inspiration.

Supongo que te gusta

Origin blog.csdn.net/Yoke______/article/details/123970083
Recomendado
Clasificación