跟着鲁班读Mybatis

Mybatis源码阅读

核心四步:

  1. MapperProxy -> 动态代理
  2. SqlSession -> SQL会话
  3. Executor -> 执行器
  4. StatementHandler -> JDBC处理器
import com.harlon.mybatis.entity.User;
import org.apache.ibatis.executor.*;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;
import org.junit.Before;
import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;


/**
 *     执行次序:
 *      SqlSession -> CacheExecutor(二级缓存) -> BaseExecutor (一级缓存)
 *                  -> Simple、Reuse、Batch Executor(最下层执行器)
 */
public class TestExecutor {


    private Configuration configuration;

    private Connection connection;

    private JdbcTransaction jdbcTransaction;

    @Before
    public void init() throws SQLException {
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();

        SqlSessionFactory build = factoryBuilder.build(User.class.getResourceAsStream("/mybatis-config.xml"));
        configuration =  build.getConfiguration();

        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatisplus", "root",
                                        "thr010410");

        jdbcTransaction = new JdbcTransaction(connection);
    }

    /**
     *  simpleExecutor,每次执行SQL需要预编译SQL语句
     */
    @Test
    public void simpleTest() throws SQLException {
        SimpleExecutor executor = new SimpleExecutor(configuration, jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.harlon.mybatis.dao.UserDao.queryById");
        List<Object> objects = executor.doQuery(ms, 3, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        objects.forEach(System.out::println);
    }

    /**
     *  可重用执行器,可以减少重复Sql的编译
     *      原理在于其内部维护一个 Map<Sql, Statement>  执行过后会将Statement放入Map
     */
    @Test
    public void reuseTest() throws SQLException {
        ReuseExecutor executor = new ReuseExecutor(configuration, jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.harlon.mybatis.dao.UserDao.queryById");
        List<Object> objects = executor.doQuery(ms, 3, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        objects.forEach(System.out::println);

        List<Object> objects1 = executor.doQuery(ms, 3, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        objects1.forEach(System.out::println);
    }


    /**
     *  批处理执行器 --> 只针对增删改操作
     *      必须手动刷新
     *
     *    手动刷新过程就是 ——批处理刷新
     */
    @Test
    public void batchTest() throws SQLException {
        BatchExecutor executor = new BatchExecutor(configuration, jdbcTransaction);
        MappedStatement ms = configuration.getMappedStatement("com.harlon.mybatis.dao.UserDao.queryById");
        List<Object> objects = executor.doQuery(ms, 3, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        objects.forEach(System.out::println);

        List<Object> objects1 = executor.doQuery(ms, 3, RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql(10));
        objects1.forEach(System.out::println);
        executor.doFlushStatements(false);
    }

    /**
     *  BaseExecutor 基础执行器实现了一级缓存,通过调用query进行缓存命中,如果未命中则执行子类实现的doQuery查数据库
     */
    @Test
    public void baseTest(){
        Executor executor = new SimpleExecutor(configuration, jdbcTransaction);
        //executor.query()
    }

    /**
     *  使用 cacheExecutor 实现了二级缓存功能,装饰者模式的体现
     *      注意点:二级缓存必须提交事物后才能查到
     */
    @Test
    public void cacheTest() throws SQLException {
        Executor executor = new SimpleExecutor(configuration, jdbcTransaction);

        Executor cacheExecutor = new CachingExecutor(executor);

        MappedStatement ms = configuration.getMappedStatement("com.harlon.mybatis.dao.UserDao.queryById");
        cacheExecutor.query(ms, 3, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
        cacheExecutor.query(ms, 3, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);

    }
}

一级缓存命中场景

未命名文件

首先看下面这样一个例子:

public class FirstCacheTest {

    private SqlSession sqlSession;

    private SqlSessionFactory factory;


    @Before
    public void init(){

        factory = new SqlSessionFactoryBuilder().build(User.class.getResourceAsStream("/mybatis-config.xml"));

        sqlSession = factory.openSession();
    }


    @Test
    public void test1(){
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        User user = mapper.queryById(3L);
        User user1 = mapper.queryById(3L);
        System.out.println(user1 == user);
    }
}

最终输出结果为 true, 就表明了一级缓存的命中,之所以直接说一级缓存是因为此处事务并未提交,所以此处不考虑二级缓存。

⚠️总结一下:

一级缓存命中的八个条件:

  1. Sql 和参数必须相同
  2. 必须是相同的statementID
  3. sqlSession必须一样(会话级缓存的由来)
  4. RowBounds 返回行的范围必须相同
  5. 不存在手动清空缓存现象 如:sqlSession.clearCache(); flushCache = true
  6. 中间未插入更新操作,否则会直接清空缓存
  7. 缓存作用域不是Statement. (同时可以看出关闭一级缓存的方式:将缓存作用域改为STATEMENT,但是同样会作用于嵌套查询)
  8. 期间未出现事务提交或者事务回滚

一级缓存代码分析

执行流程

未命名文件-2

一级缓存的核心类是:PerpetualCache

⚠️缓存的Key:(最后一个参数是环境变量)

image-20221020170314333

这里就可以好奇一下这个Key是怎么判断重复的?

image-20221020170652169

简单来看就是说对updateList中的元素进行逐个比较,这也同时应证了之前的命中条件。

一级缓存失效分析

先做一个小小总结

  1. 与会话相关
  2. 参数条件相关
  3. 提交修改都会清楚一级缓存

一级缓存失效的原因在于 Spring 管理 SqlSession后每次执行Sql都会创建一个新的会话,进而导致一级缓存失效

解决方案

image-20221020173342251

手动开启事务,防止自动提交导致的Sql会话重制

动态代理的核心目的在于 拦截方法调用

Spring整合后的Mybatis,会将Mapper进行动态代理,结果就是 SqlSessionTemlate,又通过SqlSessionInterceptor进行会话拦截,最终才走到SqlSessionFactory

值得一提的是 SqlSessionTemlate 其实是SqlSession的具体实现

二级缓存的定义与分析

二级缓存也称作是应用级缓存,与一级缓存不同的是他的作用范围是整个应用,而且可以夸线程使用,所以二级缓存有着更高的命中率,适合缓存一些修改较少的数据

二级缓存的扩展需求:

  1. 硬盘
  2. 内存
  3. 第三方集成
  4. 溢出淘汰
  5. 过期清理
  6. 命中率统计
  7. 序列化

二级缓存组件结构

image-20221021094855322

二级缓存命中条件

  1. 会话提交后
  2. Sql语句、参数相同
  3. 相同的StatementID
  4. RowBounds 相同

二级缓存的开启:1. 配置文件 2.mapper注解或者标签

image-20221021112209342

⚠️注意: flushCache默认会清除所有缓存。

<cache-ref namespace="com.harlon.mybatis.dao.UserDao"/>

同时存在 mapper 缓存和 xml 缓存时会报错,因为缓存对象不唯一,所以需要在 xml中引用缓存。

二级缓存为什么提交后才能命中

image-20221021113905625

防止脏读现象的出现

image-20221021114304723

缓存区的缓存空间是 Mapper 唯一的。也就是之前的装饰器加责任链设计体系。

flushCache是清理的暂存区

二级缓存执行流程

image-20221021151503997

image-20221021153413369

直接调用TracscationCache 的 clear方法不会直接清除缓存,而是打上了clearOnCommit标记,将来在 commmit后会将该缓存清除掉。同样的这也是为了防止脏读的出现。

StatementHandler

定义:

JDBC处理器,基于JDBC构建Statement,并设置参数,然后执行Sql。每调用会话当中一次SQL,都会有与之对应的且唯一的Statement实例。

image-20221022104636340

StatementHandler执行过程

image-20221022105354557

结果集处理流程

image-20221024115400372

MetaObject执行流程

image-20221025092940155

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

沈自在-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值