Mybatis的缓存

缓存

1.什么是缓存?

想想我们之前所有的查询最后都要连接数据库,然而连接数据库很耗资源!
然后我们要想办法解决: 我们想能不能一次查询的结果,给他暂存在一个可以直接取到的地方,这个地方一般在内存里!
放在内存的这一些查询的数据就叫缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2.为什么使用缓存?

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

3.什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。【可以使用缓存】
  • 不经常查询且经常改变的数据。【不可以使用缓存】

Mybatis缓存

  • Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。
  • Mybatis系统中默认定义了两级缓存: 一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别地缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

一级缓存:

  • 一级缓存也叫本地缓存:SqlSession
    • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

注意:一级缓存也就是从拿到SqlSession开始,到SqlSession关闭之间存在!

测试步骤:

1.开启日志!

2.测试在一个SqlSession中查询两次相同记录

@Test
public void test(){
    //一级缓存开始<<=================
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    System.out.println("=========================");
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user == user2);


    sqlSession.close();
    //一级缓存结束========================>>
}

3.查看日志输出

image-20210505181304399

一级缓存失效的情况:

1.查询不同的用户,一级缓存失效

image-20210505181719159

2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

下面这段代码,第一次查询id为1的用户,中间更新了id=2的用户信息,再查一次id为1的用户,结果显示不会走一级缓存,还是会查询数据库!

@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    mapper.updateUser(new User(2,"aaa","bbb"));

    System.out.println("=========================");
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user == user2);


    sqlSession.close();
}

image-20210505182712304

3.查询不同的mapper.xml

这个就不用测试了,不同的mapper肯定不会有一级缓存!

4.手动清理缓存

下面手动清理缓存sqlSession.clearCache(),查询id为1的用户信息走了两次数据库,没有走缓存,这样也会使缓存失效!

@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.queryUserById(1);
    System.out.println(user);

    //手动清理缓存
    sqlSession.clearCache();

    System.out.println("=========================");
    User user2 = mapper.queryUserById(1);
    System.out.println(user2);

    System.out.println(user == user2);


    sqlSession.close();
}

image-20210505183151838

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

一级缓存就是一个map集合,往map里面放东西,取的时候从map里面取!

二级缓存:

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中

    • 新的会话查询信息,就可以从二级缓存中获取内容

    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

测试步骤:

1.在mybatis-config.xml核心配置文件里显式地开启全局缓存!(也即是二级缓存),虽然默认Mybatis是开启的,但我们最好写一下!

<!--显式地开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

2.在要使用二级缓存的Mapper中开启

<!--在当前Mapper.xml中使用二级缓存-->
<cache />

也可以自定义一些参数:

<!--在当前Mapper.xml中使用二级缓存-->
<cache  eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

3.测试

@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);


    User user = mapper.queryUserById(1);
    System.out.println(user);

    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);


    sqlSession.close();
    sqlSession2.close();
}

mapper.xml中不加二级缓存的开启

image-20210505190800424

不加测试结果,走了两次数据库:

image-20210505190907324

mapper.xml文件加上二级缓存的配置:

如下:当第一个会话关闭的时候,将数据缓存到了二级缓存中,一个会话死了,致使另一个会话也能从缓存中拿到数据,缓存能在不同的会话中取到,作用域提升了一级,所以才叫二级缓存!(当然前提是在同一个mapper里面的方法)

@Test
public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);
    sqlSession.close();

    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = mapper2.queryUserById(1);
    System.out.println(user2);

    System.out.println(user == user2);

    sqlSession2.close();
}

image-20210505191333103

出现的问题:

如果mapper.xml文件这样配置二级缓存:

<!--在当前Mapper.xml中使用二级缓存-->
<cache/>

测试会报如下这个序列化错误:

image-20210505192209637

因此我们需要为实体类实现序列化接口:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;
}

image-20210505192556530

注意:最后的为false是因为一级缓存内容存到了二级缓存中,所以内存地址变化对比为false!

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中,只有当会话提交或者关闭的时候,才会提交到二级缓存中!

缓存原理

Mybatis的缓存原理:先看二级缓存中有没有缓存的数据,在看一级缓存有没有,如果都没有再走查询数据库!

image-20210505215621818

自定义缓存-ehcache:

除了上门一级,二级缓存之外,我们可以自定义缓存实现,但是我们不自己写,我们引用一个第三方写好的自定义缓存ehcache!

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存

使用步骤:

要在我们的程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

并且新建一个ehcache.xml的文件:

<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!--
        diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
        user.home - 用户主目录
        user.dir - 用户当前工作目录
        java.io.tmpdir - 默认临时文件路径
    -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <!--
        defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
    -->
    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <!--
        name:缓存名称.
        maxElementsInMemory:缓存最大数目
        maxElementsOnDisk:硬盘最大缓存个数
        eternal:对象是否永久有效,一旦设置了,timeout将不起作用。
        overflowToDisk:当系统宕机时,是否保存到磁盘
        diskPersistent:是否缓存虚拟机重启期数据
        timeToIdleSeconds:设置对象在失效前的允许闲置时间
        timeToLiveSeconds:设置对象在失效前允许存活时间
        memoryStoreEvictionPolicy:可选清除对象策略:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
    -->
    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

然后再对应mapper.xml文件中按照如下写:

<!--在当前Mapper.xml中使用ehcache自定义缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

然后测试效果跟一级、二级缓存没啥区别:

image-20210505223311268

最后提一句: 我们在工作中会用Redis数据库做缓存!

码云地址:https://gitee.com/mo18/Mybatis-Study.git 这篇文章在mybatis-09模块!

posted @ 2021-05-05 22:44  一万年太久只争朝夕  阅读(61)  评论(0编辑  收藏  举报