mybatis sqlSession 工作原理

先看 SqlSession 的使用方式

// 加载全局配置⽂件
InputStream resourceAsStream = Resources.getResourceAsStream("myabtis-config.xml");

// 获得 sqlSession ⼯⼚对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

// 方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
  User user = (User) session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);
}

// 方式二(面向切面的使用方式,后面讲)
try (SqlSession session = sqlSessionFactory.openSession()) {
  UserMapper mapper = session.getMapper(UserMapper.class);
  User user = mapper.findById(101);
}

可以看出 SqlSession 是从 SqlSessionFactory(DefaultSqlSessionFactory) 获取的,SqlSessionFactory 在初始化的时候就已经构建好了,包括一个 Configuration 对象, 前面已经分析过,这里就不再赘述了

SqlSession 是个非常重要的工具,是程序与数据库交互的桥梁,提供了一系列方法操作数据库,比如 selectOne、selectList、update、delete、insert 等;还提供了 getMapper 方法来获取 mapper 接口的代理对象,调用 mapper 接口的对象来简化数据库的操作;底层是通过其属性 Executor 调度各个组件配合工作达到目的

SqlSession 获取

DefaultSqlSessionFactory 获取 SqlSession 的关键代码

public class DefaultSqlSessionFactory implements SqlSessionFactory {
	
    // 获取 SqlSession
    public SqlSession openSession() {
        // this.configuration.getDefaultExecutorType():默认是 org.apache.ibatis.session.ExecutorType#SIMPLE
        // 第二个参数:事务隔离级别,null 表示使用数据库的隔离级别
        // 第三个参数:true 表示事务自动提交,false 事务需要手动提交
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), 
                                              (TransactionIsolationLevel)null, 
                                              false);
    }
    
    // 具体获取 SqlSession 关键代码
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        ...
        // 全局配置文件的 environment 节点
        Environment environment = this.configuration.getEnvironment();
        // environment 节点基本不会自定义配置事务工厂,所以这里会返回一个托管事务工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 通过事务工厂生成一个事务(数据库连接、隔离级别、是否自动提交)
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 生成一个执行器(事务、执行器类型)
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 生成一个 SqlSession,是 DefaultSqlSession(全局配置、执行器、是否自动提交)
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
		...
        return var8;
    }
}

再看下 Executor 的创建

// org.apache.ibatis.session.Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // 保证执行器类型不为空(防止因为粗心手动设置为 null 的情况吗?)
    executorType = executorType == null ? this.defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    // 创建执行类型的执行器
    Object 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 (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

    // 插件
    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    return executor;
}

// org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
    // 自己编写插件就要继承 Interceptor 类
    for (Interceptor interceptor : interceptors) {
        // 调用插件的 plugin 方法包装(自己编写插件要实现 plugin 方法)
		target = interceptor.plugin(target);
    }
    return target;
}

SqlSession 工作流程

  1. executor 真正完成查询操作,有 4 个执行器:简单(SimpleExecutor)、重用(ReuseExecutor)、批量(BatchExecutor),缓存(CacheExecutor)
  2. 创建 Executor 时会明确是哪种,如果开启了二级缓存就使用 CacheExecutor,不然就是别的三种,默认是 SimpleExecutor
  3. 如果开启了缓存,使用 CacheExecutor 查询,如果没查到,还是会使用其余的三种去查询

看下 session.selectOne("com.stydu.mybatis.mapper.UserMapper.findById", 101);底层怎么执行的

public <T> T selectOne(String statement) {
    return this.selectOne(statement, (Object)null);
}

public <T> T selectOne(String statement, Object parameter) {
    // 调用了 selectList
    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;
    }
}

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var5;
    try {
        // 到 mappedStatements 里面获取 MappedStatement(初始化维护了 mappedStatements)
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // 可以看到底层的查询是 executor 完成的,看看里面做了什么
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception var9) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
    } finally {
        ErrorContext.instance().reset();
    }
    return var5;
}

this.executor.query() 看这个方法做了什么

// org.apache.ibatis.executor.CachingExecutor#query
@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);
}

// org.apache.ibatis.executor.CachingExecutor#query
  @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;
        }
    }
    // 如果缓存未命中,调用 delegate 来查询,delegate 就是另外的三种执行器
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

可以明确的是,如果开启了二级缓存就是用 CachingExecutor 查询二级缓存,没有的话就调用别的查询器再次查询

如果不开启二级缓存直接调用别的查询器查询,少了一个查询二级缓存的步骤

假设就是默认的执行器,SimpleExecutor 中没有 query,所以调用父类 BaseExecutor 的 query 方法,源码如下:

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

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    List<E> list;
    try {
        // 查询缓存(一级)
    	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--;
    }
    ...
    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 {
        // 这个方法直接抛异常,太常见了,模板方法,调用子类实现的方法,所以又回到了 SimpleExecutor 中
    	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;
}

SimpleExecutor#doQuery 源码:

// org.apache.ibatis.executor.SimpleExecutor#doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        Configuration configuration = ms.getConfiguration();
        // 处理参数(根据 MappedStatement 具体类型创建 StatementHandler 对象的过程中处理工作完成)
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 获得数据库连接,创建 Statement 或者 PrepareStatement
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        // 由 handler 执行 SQL 语句,并将数据库返回结果转化为设定的对象,比如 List,Map 或者是 POJO
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}

SimpleStatementHandler#query 源码

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 这里能看到 sql(带占位符的)
    String sql = boundSql.getSql();
    // 执行 sql
    statement.execute(sql);
    // 映射结果
    return resultSetHandler.handleResultSets(statement);
}

总结

  1. 根据 SqlSessionFactoryBuilder 创建 SqlSessionFactory,其实是 DefaultSqlSessionFactory
  2. SqlSessionFactory.openSession 得到 SqlSession,其实是 DefaultSqlSession,内部持有一个 Executor 对象
  3. 调用方法时,SqlSession 先到 configuration.mappedStatements 中获取 MappedStatement
  4. Executor 根据 MappedStatement 查询数据
    1. 如果开启了二级缓存,先查询二级缓存
    2. 二级未命中或者根本没有使用二级,查询一级缓存
    3. 一级未命中才真正查询数据库
  5. 处理参数、创建连接、映射结果
posted @ 2023-07-24 16:24  黄光跃  阅读(18)  评论(0编辑  收藏  举报