Mybatis源码解析(三)------SqlSession

Mybatis源码解析(三)------SqlSession

  • 序言
  • SqlSession接口
  • SqlSession的实现类
  • DefaultSqlSession
    • Select
      • 获取Statement
      • 查询

序言

Mybatis里面的核心就是SqlSession这个接口,前面我们已经研究了Mybatis的配置过程和Mapper的注册过程,在本篇文章中我们就来研究SqlSession。

/**
 * The primary Java interface for working with MyBatis.
 * Through this interface you can execute commands, get mappers and manage transactions.
 *
 * @author Clinton Begin
 */

SqlSession接口

先来看看sqlSession的接口:

接口方法 接口描述
T selectOne(String statement); 查询单行数据,无参
T selectOne(String statement, Object parameter); 带参数查询单行数据
List selectList(String statement); 无参数查询集合
List selectList(String statement, Object parameter); 带参数查询集合
List selectList(String statement, Object parameter, RowBounds rowBounds); 带参数查询指定行内的数据
Map selectMap(String statement, String mapKey); selectMap 是一种特殊情况,它旨在根据结果对象中的一个属性将结果列表转换为 Map。 例如。 为 selectMap(“selectAuthors”,“id”) 返回 Map[Integer,Author]
Map selectMap(String statement, Object parameter, String mapKey); 带参数,同上
Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); 带指定行数,同上
Cursor selectCursor(String statement); Cursor和list一样,不过Cursor用Iterator来懒加载数据
Cursor selectCursor(String statement, Object parameter); 带参数,同上
Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds); 带指定行数,同上
void select(String statement, Object parameter, ResultHandler handler); 用ResultHandler来处理查询结果,返回一条数据(方法注释上是一条,handler注释上是处理每条,我觉得返回结果也是处理多条)
void select(String statement, ResultHandler handler); 不带参,同上
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); 带行数限制,同上
int insert(String statement); 执行插入操作
int insert(String statement, Object parameter); 带参数插入
int update(String statement); 更新操作
int update(String statement, Object parameter); 带参数更新
int delete(String statement); 删除操作
int delete(String statement, Object parameter); 带参数删除
void commit(); 刷新批处理语句并提交数据库连接。 请注意,如果没有调用更新/删除/插入,则不会提交数据库连接。 强制提交调用commit(boolean)
void commit(boolean force); 刷新批处理语句并提交数据库连接。
void rollback(); 丢弃挂起的批处理语句并回滚数据库连接。 请注意,如果没有调用更新/删除/插入,数据库连接将不会回滚。 强制回滚调用rollback(boolean)
void rollback(boolean force); 丢弃挂起的批处理语句并回滚数据库连接。 请注意,如果没有调用更新/删除/插入,数据库连接将不会回滚。
List flushStatements(); 刷新批处理语句。
void close(); 关闭session连接
void clearCache(); 清除本地缓存
Configuration getConfiguration(); 获取当前的配置
T getMapper(Class type); 获取Mapper
Connection getConnection(); 获取连接
看起来SqlSession接口定义很简单也很全面了,我们需要的数据库操作不外乎就是CRUD了。接着往下看……

SqlSession的实现类

Mybatis对SqlSession的实现有三个:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate。
Mybatis源码解析(三)------SqlSession_第1张图片
接下来分别对三个实现类进行分析。

DefaultSqlSession

SqlSession的默认实现

/**
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */

分别从CRUD来看看这个实现类吧。

Select

select是业务系统中使用最多的操作,根据源码绘制下图,从图中就可以看出,查询工作最终是落到了private List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) 上。
Mybatis源码解析(三)------SqlSession_第2张图片

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MappedStatement:在原文中也没有注释,我们用原始的jdbc的时候也会有一步操作,获取Statement。至于Statement翻译成中文是什么,还真不好直译,网上说的 Statement是Java 执行数据库操作的一个重要接口,这里的MappedStatement也就是这个意思吧。

这里Select的过程就是从configuration里面那到对应的statement然后交个executor进行查询。

获取Statement

取得过程也很简单,就是去一个Map里面通过statment字符串去取就好了:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.get(id);
  }

可是,MappedStatement是如何放到Map里面的呢?利用Idea看了下,是在Mapper注册的时候放进去的。关于Mapper的注册可以看下Mybatis源码解析(二)------Mapper注册,不过我也没太仔细分析注册过程。
Mybatis源码解析(三)------SqlSession_第3张图片

查询

那到Statement后,执行器开始执行查询操作,执行查询前看下跟一下这里的执行器是哪个,这个要从SqlSession session = sqlSessionFactory.openSession();开始看,SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);从这里得知是DefaultSqlSessionFactory, 然后这样再那样,得到执行器是SimpleExecutor,而SimpleExecutor继承的是BaseExecutor,并且没有重写query方法。那么接下来就是看看BaseExecutor的query方法了。

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

先从ms中取出BoundSql,然后生成一个缓存key。

BoundSql是什么?在处理完动态内容后,从SqlSource中取出的一个实际的Sql字符串,这条sql可能带有占位符?,还有一个参数映射集合,以及一些参数描述性信息。
Mybatis源码解析(三)------SqlSession_第4张图片
换句话说就是BoundSQL就是解析我们的Mapper生成的,每个接口方法和sql对应一个BoundSql。

然后继续查询:

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

先从缓存中取,然后没取到就从数据库查。从数据库取的时候肯定得王缓存里面放。

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

doQuery是一个抽象方法,有子类实现。

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

看下SimpleExecutor的doQuery方法吧:

 @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的:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
  ………………
}

上面这个结构,我觉着是啥设计模式呢?对设计模式了解不是非常深入,说是代理模式也不像啊,把整个对象都放给调用者了。有了解设计模式的大佬指教下呢。

在Demo这个场景下,这边用的是PreparedStatementHandler;接着,用StatementHandler来准备一个Statement,看下准备过程准备了什么:

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

你可能感兴趣的:(#,mybaits,mybatis,tomcat,java)