Mybatis 一级缓存 源码解析

Mybtais我想大家已经很熟悉了,毕竟算是国内最火的orm框架。Mybtais 自带双层缓存机制,其中第一层缓存是默认开启的。

我们使用 Mybatis 的时候,通常会配置 SqlSessionFactory 与 SqlSessionTemplate,这两个类在 Mybatis 中担任着非常重要的角色。


/**
 * 配置 SqlSessionFactory
 */
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(dataSource);
    Resource[] resource = new PathMatchingResourcePatternResolver().getResources("classpath:xml/*.xml");
    sqlSessionFactoryBean.setMapperLocations(resource);
    // 生成 SqlSessionFactory,默认实现为 DefaultSqlSessionFactory
    return sqlSessionFactoryBean.getObject();
}

/**
 * 配置 SqlSessionTemplate
 */
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
}

使用 Mybatis 时,我们只需要定义一个 Mapper(也有习惯叫Dao的)接口类,在里面定义方法,并在指定目录生成 XML 编写 sql 语句。在项目启动时,Mybatis 会为我们定义的接口类生成代理实现类。而生成代理实现类的方法是 MapperFactoryBean 里面的 getObject 方法。


public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private boolean addToConfig = true;

    public MapperFactoryBean() {
    }

    public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    protected void checkDaoConfig() {
        super.checkDaoConfig();
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        Configuration configuration = this.getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception var6) {
                this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
                throw new IllegalArgumentException(var6);
            } finally {
                ErrorContext.instance().reset();
            }
        }

    }

    /**
     * 此方法生成代理实现类
     */
    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

    public Class<T> getObjectType() {
        return this.mapperInterface;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public void setAddToConfig(boolean addToConfig) {
        this.addToConfig = addToConfig;
    }

    public boolean isAddToConfig() {
        return this.addToConfig;
    }
}

可以看到,MapperFactoryBean 继承了 SqlSessionDaoSupport,而 getObject 方法里面的 this.getSqlSession() 则是父类中的,此方法的返回值正是 SqlSessionTemplate。


public abstract class SqlSessionDaoSupport extends DaoSupport {
    
    /* 省略其他方法 */

    private SqlSessionTemplate sqlSessionTemplate;

    public SqlSession getSqlSession() {
        return this.sqlSessionTemplate;
    }
}

生成代理类的逻辑不在本文章范围内,就不说了。只需要知道生成代理类是调用的 SqlSessionTemplate 内的方法,最终执行 sql 的也是 SqlSessionTemplate 就行了。

让我们看看这个 SqlSessionTemplate 它到底是什么呢?


public class SqlSessionTemplate implements SqlSession, DisposableBean {

    private final SqlSessionFactory sqlSessionFactory;

    private final ExecutorType executorType;

    /**
     * 动态生成的代理 SqlSession,执行 sql 方法全靠它
     */
    private final SqlSession sqlSessionProxy;

    private final PersistenceExceptionTranslator exceptionTranslator;

    /**
     * 我们在配置 SqlSessionTemplate 类的时候调用的构造方法
     */
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        // 生成代理类
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionInterceptor());
    }
}

可以看到它实现了 SqlSession,要我看它其实就是 SqlSession 的装饰器。SqlSessionTemplate 内部定义了一个 sqlSessionProxy,这个 sqlSessionProxy 才是用于执行 sql 方法的东西。可以在构造方法里面看到 sqlSessionProxy 其实是一个代理类,代理对象为 SqlSessionInterceptor。


private class SqlSessionInterceptor implements InvocationHandler {
    private SqlSessionInterceptor() {
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取 sqlSession
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
        Object unwrapped;
        try {
            // 代理方法 -- 执行
            Object result = method.invoke(sqlSession, args);
            // 事务相关
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }
            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw (Throwable)unwrapped;
        } finally {
            // 最终需要关闭 SqlSession
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
        return unwrapped;
    }
}

很明显,sqlSessionProxy 在执行方法时靠得就是它。

方法的第一步需要通过 SqlSessionUtils.getSqlSession 方法获取 SqlSession,只有得到了 SqlSession 才能去实际执行 sql 语句。而 Mybatis 的第一级缓存,就是 SqlSession 级别的缓存。SqlSessionUtils.getSqlSession 方法的第一个入参是 SqlSessionTemplate 内的 sqlSessionFactory,这个 SqlSessionFactory 是我们配置的时候定义的,也就是 DefaultSqlSessionFactory。


/**
 * SqlSessionUtils.getSqlSession 实现
 */
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    } else {
        LOGGER.debug(() -> {
            return "Creating a new SqlSession";
        });
        // 打开 sqlSession
        session = sessionFactory.openSession(executorType);
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

让我们看一下 sessionFactory.openSession 方法。sessionFactory 类是 DefaultSqlSessionFactory。openSession 在 DefaultSqlSessionFactory 的实现中,调用的是 openSessionFromDataSource 方法。


private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        Environment environment = this.configuration.getEnvironment();
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 下面创建的 DefaultSqlSession 中,靠的就是 executor 执行 sql 方法
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 可以看到,最终的 SqlSession 就是这个 DefaultSqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

看下 this.configuration.newExecutor 方法 ,创建 Executor 时使用的实现类是 CachingExecutor。


public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    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 {
        // 如果没开启二级缓存,默认创建 SimpleExecutor
        executor = new SimpleExecutor(this, transaction);
    }
    // cacheEnabled 默认是 true,所以 Executor 的实现类是 CachingExecutor
    if (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

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

SqlSessionUtils.getSqlSession 方法得到的 SqlSession,就是这个 DefaultSqlSession,而 DefaultSqlSession 中真正去执行 Sql 方法的,就是 Executor 这个类。

以 DefaultSqlSession 中的 selectList 方法为例,可以看到最终执行的其实是 executor.query。


private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        // 实际上执行 sql 语句的方法
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }

    return var6;
}

executor 实现类是 CachingExecutor,看下 CachingExecutor 的 query 方法。


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) {
        this.flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            this.ensureNoOutParams(ms, boundSql);
            List<E> list = (List)this.tcm.getObject(cache, key);
            if (list == null) {
                list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                this.tcm.putObject(cache, key, list);
            }

            return list;
        }
    }
    // 没开启二级缓存,执行此方法。
    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

在默认没开启二级缓存的情况下,执行的是 this.delegate.query 方法,delegate 是构造方法里面传入的 Executor,通过上面的 newExecutor 方法可以看到这个 Executor 默认是 SimpleExecutor。

SimpleExecutor 是 BaseExecutor 的子类, SimpleExecutor并未对 BaseExecutor 内的 query 方法进行重载,所以 CachingExecutor 内 query 方法里面的 this.delegate.query ,就是 BaseExecutor 里面的 query 方法。


public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 将本次查询方法解析成第一级缓存的key
    CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
    return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

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 (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
            this.clearLocalCache();
        }

        List list;
        try {
            ++this.queryStack;
            // this.localCache 就是第一级缓存
            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
            if (list != null) {
                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 第一级缓存中没有,去数据库查询数据
                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            --this.queryStack;
        }

        if (this.queryStack == 0) {
            Iterator var8 = this.deferredLoads.iterator();

            while(var8.hasNext()) {
                DeferredLoad deferredLoad = (DeferredLoad)var8.next();
                deferredLoad.load();
            }

            this.deferredLoads.clear();
            if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                this.clearLocalCache();
            }
        }

        return list;
    }
}

至此,我们终于见到了 第一级缓存 的芳容。

我们再看一下 this.queryFromDatabase 方法。


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        // 查询数据库,由子类 SimpleExecutor 实现
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        this.localCache.removeObject(key);
    }
    // 查到数据后,放入第一级缓存
    this.localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}

那么第一级缓存里面的数据何时会被清空呢?答案是关闭 SqlSession,或者增删改的时候都会情况第一级缓存中的数据。


/**
 * 增删改都会掉用到 BaseExecutor 内的此方法
 */
public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (this.closed) {
        throw new ExecutorException("Executor was closed.");
    } else {
        // 清空第一级缓存数据
        this.clearLocalCache();
        return this.doUpdate(ms, parameter);
    }
}

在执行完 sql 查询后,SqlSessionTemplate 内的代理类的方法会调用 SqlSessionUtils.closeSqlSession 关闭 SqlSession,也会清空第一级缓存。所以使用 SqlSessionTemplate 装饰的 SqlSession时,第一级缓存是不生效的。在我们用 SqlSessionFactory 创建 SqlSession 时,第一级缓存才会生效。


@Resource
private TUserInfoMapper tUserInfoMapper;

@Resource
private SqlSessionFactory sqlSessionFactory;

@Test
void test3() {
    // 此时 第一级缓存不生效,因为执行方法的是 SqlSessionTemplate 内的 代理 SqlSession
    // 代理方法会在执行完 Sql 方法的时候关闭 SqlSession,顺带清空第一级缓存
    TUserInfo tUserInfo = tUserInfoMapper.getById(1L);
    System.out.println(tUserInfo);
    tUserInfo = tUserInfoMapper.getById(1L);
    System.out.println(tUserInfo);
    // 使用 sqlSessionFactory 创建 SqlSession,此时 SqlSession 就是纯粹的 DefaultSqlSession
    // 这个时候只有执行增删改方法,第一级缓存内的数据才会被情空
    SqlSession sqlSession = sqlSessionFactory.openSession();
    TUserInfoMapper mapper = sqlSession.getMapper(TUserInfoMapper.class);
    TUserInfo byId = mapper.getById(1L);
    byId = mapper.getById(1L);
    System.out.println(byId);
    sqlSession.close();
}

-- 我是 Keguans,一名生于 99 年的菜鸡研发

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Keguans

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值