Source code analysis of DAO execution process in Mybatis

Keywords: Session Database JDBC

Article catalog

1. Query source code analysis of all methods

The implementation class of DAO interface calls SqlSession.selectList()

public class AccountDaoImpl implements AccountDao {

    private SqlSessionFactory factory;

    public AccountDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

    public List<Account> findAll() {
        SqlSession session = factory.openSession();
        List<Account> user = session.selectList("dao.AccountDao.findAll");
        session.close();
        return user;
    }
}

Look at the call in findAll(). session.selectList(), found that SqlSession is an excuse, we need to find its implementation class

public interface SqlSession extends Closeable {

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
}

The implementation class of SqlSession used here is DefaultSqlSession. The implementation of selectList() method is as follows:

public class DefaultSqlSession implements SqlSession {  
  @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();
      }
    }
}

Three selectlists () call the next one in turn, and the final one is selectList(String statement, Object parameter, RowBounds rowBounds). It calls executor.query(), executor is defined as private final executor; and executor is an interface:

public interface Executor {
  <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;
}

Therefore, we need to find the implementation class of Executor. The caching Executor is used here

public class CachingExecutor implements Executor {
  private final Executor delegate;
  
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  @Override
  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);
  }
}

It's called again delegate.query Delegate itself is a variable of type Executor. Therefore, we need to find the implementation class of the Executor interface corresponding to delegate. BaseExecutor is used here.

public abstract class BaseExecutor implements Executor {
  @SuppressWarnings("unchecked")
  @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;
  }
}

queryFromDatabase() is called

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

Which calls doQuery (). Executor itself is an abstract class, so you need to continue to find the implementation of doQuery() in its implementation class simpleexector:

public class SimpleExecutor extends BaseExecutor {

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

Which calls handler.query(), the definition of handler is StatementHandler handler, where StatementHandler is an interface. The implementation class of the interface uses RoutingStatementHandler

public class RoutingStatementHandler implements StatementHandler {
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }
}

In this case, delegate is a variable of the implementation class PreparedStatementHandler type of StatementHandler interface.

public class PreparedStatementHandler extends BaseStatementHandler {

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

query() in PreparedStatementHandler uses execute() of PreparedStatement in JDBC to encapsulate the result using handleResultSets() in DefaultResultSetHandler, the only implementation class of resultSetHandler interface.

public class DefaultResultSetHandler implements ResultSetHandler {
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

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

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

Execution method of PreparedStatement object:

  • execute(): it can execute any statement in CRUD. The return value is boolean, indicating whether there is a result set. If there is a result set, it is true. Otherwise, it is false
  • executeUpdate(): it can only execute CUD statements, query statements cannot be executed, and the return value is the number of rows affecting database records
  • executeQuery(): it can only execute SELECT statement, cannot execute add, delete and modify, and execute result set ResultSet object encapsulated by result

2. Source code analysis of insertion, update and deletion methods

Implementation of insertion method in the implementation class of DAO interface:

public class AccountDaoImpl implements AccountDao {
    private SqlSessionFactory factory;

    public AccountDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

    public void saveUser(Account account) {
        SqlSession session = factory.openSession();
        session.insert("dao.AccountDao.saveUser", account);
        session.commit();
        session.close();
    }
}

The implementation class of SqlSession called is insert() in DefaultSqlSession

public class DefaultSqlSession implements SqlSession {
  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
}

insert() calls update().

Implementation of update method in the implementation class of DAO interface:

public class AccountDaoImpl implements AccountDao {
    private SqlSessionFactory factory;

    public AccountDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

    public void updateUser(Account account) {
        SqlSession session = factory.openSession();
        session.update("dao.AccountDao.updateUser", account);
        session.commit();
        session.close();

    }
}

It calls update() in DefaultSqlSession, the implementation class of SqlSession

public class DefaultSqlSession implements SqlSession {
  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

Implementation of deletion method in the implementation class of DAO interface:

public class AccountDaoImpl implements AccountDao {
    private SqlSessionFactory factory;

    public AccountDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

    public void deleteUser(int id) {
        SqlSession session = factory.openSession();
        session.update("dao.AccountDao.deleteUser", id);
        session.commit();
        session.close();

    }
}

It calls update() in DefaultSqlSession, the implementation class of SqlSession

public class DefaultSqlSession implements SqlSession {
  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }
}

delete() calls update() again.

To sum up, the final call of the three methods is update() in DefaultSqlSession, so you only need to analyze the source implementation of update().

Called in update() executor.update(), the executor here uses the method implementation in the implementation class cachenexecutor of the executor interface.

public class CachingExecutor implements Executor {
  private final Executor delegate;
  
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
}

Method is called delegate.update(), which calls update() in the implementation class BaseExecutor of Executor

public abstract class BaseExecutor implements Executor {
  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
}

The implementation of the update() method calls doUpdate() again

  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

It is an abstract method to find the doUpdate() in the simpleexecutior implementation class of BaseExecutor

public class SimpleExecutor extends BaseExecutor {

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

Which calls handler.update(), where the handler is of StatementHandler type, StatementHandler itself is an interface, and its implementation class needs to be found. The direct implementation class is BaseStatementHandler, which is an abstract class. Its implementation class is PreparedStatementHandler.

public class PreparedStatementHandler extends BaseStatementHandler {
  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
}

update() still uses execute() of PreparedStatement in JDBC.

3. Source code analysis of aggregate function getCount

The implementation of getCount() in the implementation class of DAO interface:

public class AccountDaoImpl implements AccountDao {
    private SqlSessionFactory factory;
    public int getCount() {
        SqlSession session = factory.openSession();
        int count = session.selectOne("dao.AccountDao.getCount");
        session.close();
        return count;
    }
}

Called SqlSession.selectOne(). Find selectOne() in DefaultSqlSession, the implementation class of sqlsession interface

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T selectOne(String statement) {
    return this.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.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;
    }
  }
}

It also calls selectList() in DefaultSqlSession

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

Call in selectList() again executor.query(). Here we also find the method implementation in the implementation class cachenexecutor of the Executor interface.

public class CachingExecutor implements Executor {
  @Override
  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);
  }
}

It calls again delegate.query(). The type of delegate is the implementation class cachexecution of Executor. Continue to see its method implementation

public class CachingExecutor implements Executor {
  @Override
  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);
  }
}

It's called again delegate.query Delegate itself is a variable of type Executor. Therefore, we need to find the implementation class of the Executor interface corresponding to delegate. BaseExecutor is used here.

public abstract class BaseExecutor implements Executor {
  @SuppressWarnings("unchecked")
  @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;
  }
}

queryFromDatabase() is called

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

Which calls doQuery (). Executor itself is an abstract class, so you need to continue to find the implementation of doQuery() in its implementation class simpleexecutior:

public class SimpleExecutor extends BaseExecutor {

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

Which calls handler.query(), the definition of handler is StatementHandler handler, where StatementHandler is an interface. The implementation class of the interface uses RoutingStatementHandler

public class RoutingStatementHandler implements StatementHandler {
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }
}

In this case, delegate is a variable of the implementation class PreparedStatementHandler type of StatementHandler interface.

public class PreparedStatementHandler extends BaseStatementHandler {

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

query() in PreparedStatementHandler uses execute() of PreparedStatement in JDBC to encapsulate the result using handleResultSets() in DefaultResultSetHandler, the only implementation class of resultSetHandler interface.

public class DefaultResultSetHandler implements ResultSetHandler {
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

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

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }

Posted by PerfecTiion on Tue, 23 Jun 2020 00:59:04 -0700