Mybatis-Plus版本3.4.3

为啥要提前申明下版本,因为该文章是基于Mybatis-Plus3.4.3 版本源码做的分析,有一些源码与以前旧版本有不一样的地方,没有说明版本的话会引导初学者迷惑。有的说1有的说2 不知道该看那个文章才是适用自己的,不管怎么,大致思想不会变化,最好建议大家学会使用后多看源码。万变不离其中。

基本缓存问题

  • 什么是缓存?

1.存在内存中的临时数据
2.将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库 数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  • 为什么使用缓存

减少和数据库的交互次数,减少系统开销或IO,提高系统效率

  • 什么样的场景使用缓存?

经常查询同时不经常修改的数据。

Mybatis 缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地 提升查询效率。

一级缓存

一级缓存:也称为本地缓存,基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为SqlSession,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,各个SqlSession之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis默认打开一级缓存、不允许关闭。

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_缓存

二级缓存(默认是开启)

也称为全局缓存,是mapper级别的缓存。二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,所以默认也是本地缓存。不同之处在于其存储作用域为 Mapper(Namespace),可以在多个SqlSession之间共享,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的,并且可自定义存储源,如 Ehcache、Redis。默认开启二级缓存,但是还需要配置才可以使用。

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_缓存_02


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_03

查询流程

一级缓存流程图

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_ide_04

演示代码
InputStream inputStream = Resources.getResourceAsStream(XML);
        SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
        SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
        MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
        MpUser mpUser = sessionOneMapperOne.selectById(1L);
        MpUser mpUser2 = sessionOneMapperOne.selectById(1);
二级缓存流程图

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_ide_05

演示代码
InputStream inputStream = Resources.getResourceAsStream(XML);
        SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSessionOne = sqlSessionFactory.openSession(false);
        SqlSession sqlSessionTwo = sqlSessionFactory.openSession(false);
        MpUserMapper sessionOneMapperOne = sqlSessionOne.getMapper(MpUserMapper.class);
        MpUserMapper sessionTwoMapperMapperOne = sqlSessionTwo.getMapper(MpUserMapper.class);
        MpUser mpUser = sessionOneMapperOne.selectById(1L);
        sqlSessionOne.commit();
        MpUser mpUser2 = sessionTwoMapperMapperOne.selectById(1);
整体流程图

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_06

缓存源码分析

一级缓存查询

  • 获取执行器设置到Sessioon

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_mybatis_07


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_08

  • 获取代理Mappper
  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_09


  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_10

  • 查询分析
  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_缓存_11


  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_mybatis_12


  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_java_13


  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_缓存_14


  • springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_mybatis_15

二级缓存查询

  • 首先配置二级缓存的存储位置我这次选择的是Redis 大家可以按照自己需求来选择,默认不建议,因为他也是本地的缓存,分布式情况还需要再次请求数据库缓存到本地。
public class RedisCache implements Cache {
    private final String id;
    private static Jedis cache;

    static {
        cache = new Jedis("127.0.0.1", 6379);
        cache.auth("1111111");
    }


    public RedisCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    /**
     * 返回缓存所有键值对的数量
     *
     * @return
     */
    @Override
    public int getSize() {
        Long dbSize = cache.dbSize();
        return dbSize.intValue();
    }

    /**
     * 向缓存中存入数据
     *
     * @param key
     * @param value
     */
    @Override
    public void putObject(Object key, Object value) {
        byte[] keyBs = SerializationUtils.serialize((Serializable) key);
        byte[] valueBs = SerializationUtils.serialize((Serializable) value);
        cache.set(keyBs, valueBs);
    }

    /**
     * 从缓存中获取数据
     *
     * @param key
     * @return
     */
    @Override
    public Object getObject(Object key) {
        byte[] keyBs = SerializationUtils.serialize((Serializable) key);
        byte[] valueBs = cache.get(keyBs);
        if (valueBs != null) {
            return SerializationUtils.deserialize(valueBs);
        }
        return null;
    }

    /**
     * 清除缓存
     *
     * @param key
     * @return
     */
    @Override
    public Object removeObject(Object key) {
        byte[] keyBs = SerializationUtils.serialize((Serializable) key);
        byte[] valueBs = cache.get(keyBs);
        Object obj = SerializationUtils.deserialize(valueBs);
        cache.del(keyBs);
        return obj;
    }

    /**
     * 清空缓存
     */
    @Override
    public void clear() {
        cache.flushDB();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }

    @Override
    public boolean equals(Object o) {
        if (getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        }
        if (this == o) {
            return true;
        }
        if (!(o instanceof Cache)) {
            return false;
        }

        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
    }

    @Override
    public int hashCode() {
        if (getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        }
        return getId().hashCode();
    }
}

我使用的注解形式

@CacheNamespace(implementation = RedisCache.class,flushInterval = 1)

目前支持两种模式一种是在xml 配置 一种上面注解模式,为啥两种可以通过源码分析得出。

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_ide_16


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_17


这里简答说下上面截图就是设置二级缓存cache 对象的位置,可以发现一种是xml 解析赋值,一种注解解析赋值。

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_sql_18


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_ide_19


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_mybatis_20


springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_java_21


最后切记缓存的实体对象需要实例化。

springboot mybatis关闭控制台sql日志 mybatisplus关闭缓存_缓存_22


总结:缓存这里细节也很多,也有使用不当造成异常问题或者性能问题。特别是并发情况的嵌套查询。后面分析嵌套查询的延迟加载,以及为啥一级缓存要最开始要加占位符。