程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(3)

MyBatis缓存机制详解(一级缓存,二级缓存)

发布于2021-05-29 22:36     阅读(1281)     评论(0)     点赞(28)     收藏(1)


MyBatis缓存机制详解(一级缓存,二级缓存)

一,什么是缓存

缓存就是数据交换的缓存区(称作Cache),是存储数据(使用频繁的数据)的临时地方。当用程序查询数据时,首先会在缓存中寻找,如果找到了就直接返回。如果找不到,则会去数据库中查找。缓存的本质就是用空间换时间,牺牲数据的实时性,以服务器内存中的数据暂时代替从数据库中读取的最新数据,减少程序与数据库之间的IO,减轻服务器压力,减少网络延迟,加快页面打开速度。

二,MyBatis中的缓存

MyBatis支持声明式数据缓存( declarative data caching )。当一条SQL语句被标记为“可缓存”后,首次执行它时从数据库中获取的所有数据会被存储在一段高速缓存中,今后执行这条语句时就会从高速缓存中读取结果,而不是再次命中数据库。

MyBatis提供了默认下基于Java HashMap的缓存实现,以及用于与OSCache,Ehcache,Hazelcast和Mecached连接的默认连接器。MyBatis还提供API供其他缓存实现使用。

注意了!

在这里插入图片描述

上面内容的重点:MyBatis执行SQL语句之后,这条语句从数据库中获取的数据就会被缓存,后面再需要用到这些数据的时候,会直接从缓存中拿结果,而不是再次执行SQL。

MyBatis提供了两种缓存(一级缓存和二级缓存),接下来将对这两种缓存进行详细的介绍。

MyBatis一级缓存

默认情况下,MyBatis只启用了一级缓存(也称为本地缓存),一级缓存的作用域是SqlSession。它仅仅对一个会话中的数据进行缓存。

接下来我们分情况对一级缓存进行测试说明

①同个SqlSession进行两次相同查询
public class Test {

    @org.junit.Test
    public void test1() throws IOException {

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session1 = sqlSessionFactory.openSession();

        try {
            User user1 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user1);
            User user2 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user2);

            System.out.println(user1 == user2);
        } finally {
            session1.commit();
            session1.close();
        }
    }
}

日志打印结果:

在这里插入图片描述

第一次查询,执行了SQL语句,然后返回了查询结果,第二次查询并没有执行SQL语句,直接从缓存中获取到了结果;通过"=="运算符对user1和user2进行比较,返回true,发现user1和user2实际上指向的是同一个对象。

结论:同个SqlSession进行两次相同查询,MyBatis只进行一次数据库查询,第二次会直接从缓存中获取查询结果。

②同个SqlSession进行两次不同的查询。
public class Test {

    @org.junit.Test
    public void test1() throws IOException {

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session1 = sqlSessionFactory.openSession();

        try {
            User user1 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user1);
            User user2 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 2);
            System.out.println(user2);

            System.out.println(user1 == user2);
        } finally {
            session1.commit();
            session1.close();
        }
    }
}

日志打印结果:

在这里插入图片描述

两次查询都执行了SQL语句;通过"=="运算符对user1和user2进行比较,返回false,user1和user2是两个不同的对象。

结论:同个SqlSession进行两次不同的查询,会执行两次SQL。

额,实际上这里并没有使用到缓存。。。

在这里插入图片描述

不过这应该在大家的意料之中吧,查询条件不同,第二次查询的数据缓存中根本没有,当然会执行SQL从数据库中取数据了!

③不同SqlSession,进行相同查询。
public class Test {

    @org.junit.Test
    public void test1() throws IOException {

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session1 = sqlSessionFactory.openSession();
        SqlSession session2 = sqlSessionFactory.openSession();

        try {
            User user1 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user1);
            User user2 = session2.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user2);

            System.out.println(user1 == user2);
        } finally {
            session1.commit();
            session2.commit();
            session1.close();
            session2.close();
        }
    }
}

日志打印结果:

在这里插入图片描述

两次查询都执行了SQL语句;通过"=="运算符对user1和user2进行比较,返回false,user1和user2是两个不同的对象。

结论:不同SqlSession,进行相同查询,会执行两次SQL。

也就是说,这里也没有用到缓存。一级缓存作为SqlSession级别的缓存,每一个SqlSession都有自己独立的缓存,不同的SqlSession之间的缓存,并不共享。

④同个SqlSession,查询之后更新数据,再次查询相同的语句。
public class Test {

    @org.junit.Test
    public void test1() throws IOException {

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session1 = sqlSessionFactory.openSession();

        try {
            User user1 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user1);
            session1.update("com.ljt.mybatis.dao.UserDao.setNameById",3);
            User user2 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user2);

            System.out.println(user1 == user2);
        } finally {
            session1.commit();
            session1.close();
        }
    }
}

日志打印结果:

在这里插入图片描述

两次查询都执行了SQL语句;通过"=="运算符对user1和user2进行比较,返回false,user1和user2是两个不同的对象。

结论:更新操作之后缓存会被清除。

既然数据更新了,那么第二次查询时,再使用缓存中过时的数据不就毫无意义了吗?

⑤调用clearCache()清除缓存,再次查询相同的语句。
public class Test {

    @org.junit.Test
    public void test1() throws IOException {

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session1 = sqlSessionFactory.openSession();

        try {
            User user1 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user1);
            session1.clearCache();
            User user2 = session1.selectOne("com.ljt.mybatis.dao.UserDao.getUserById", 3);
            System.out.println(user2);

            System.out.println(user1 == user2);
        } finally {
            session1.commit();
            session1.close();
        }
    }
}

日志打印结果:
在这里插入图片描述

两次查询都执行了SQL语句;通过"=="运算符对user1和user2进行比较,返回false,user1和user2是两个不同的对象。

结论:缓存清除后,即使在同一个SqlSession中执行相同的查询,还是会执行两次SQL。

这不废话嘛,缓存清除了,再次查找的话当然得去数据库中查找了。

在这里插入图片描述

总结:

1.在同一个 SqlSession中进行多次相同查询,只会在第一次查询时执行SQL;

2.不同的 SqlSession 之间的缓存是相互独立的;

3.可以使用clearCache()清空已有缓存;

4.任何的更新语句( UPDATE, INSERT, DELETE 语句)都会清空缓存。

MyBatis二级缓存

之所以称之为“二级缓存”,是相对于“一级缓存”而言的。

二级缓存是Mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

要启用全局的二级缓存,只需要在SQL映射文件中添加一行:

<cache/>

是不是很简单?

在这里插入图片描述

我来给大家解释一下这个简单语句产生的作用:

映射语句文件中的所有 select 语句的结果将会被缓存。
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
缓存会保存列表或对象的1024个引用。
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

这些属性可以通过 cache 元素的属性来修改。比如:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

下面对cache中常用的属性进行介绍:

①eviction

eviction(清除策略)

可用的清除策略有:

LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

②flushInterval

flushInterval(刷新间隔)

属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

③size

size(引用数目)

属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

④readOnlu

readOnly(只读)

属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

讲到这里,大家应该对二级缓存有些基本了解了;

不过既然有了一级缓存,那么为什么要提供二级缓存呢?

在这里插入图片描述

我们知道,在一级缓存中,不同SqlSession进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这就是MyBatis二级缓存的初衷。

另外,Spring和MyBatis整合时,每次查询之后都要进行关闭SqlSession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。

可如果开启二级缓存,关闭SqlSession后,会把该SqlSession一级缓存中的数据添加到mapper namespace的二级缓存中。

这样,缓存在SqlSession关闭之后依然存在。

原文链接:https://blog.csdn.net/qq_51372098/article/details/117309702



所属网站分类: 技术文章 > 博客

作者:快起来搬砖啦

链接:http://www.javaheidong.com/blog/article/207631/35f80c1d7b3612f36399/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

28 0
收藏该文
已收藏

评论内容:(最多支持255个字符)