Mybatis缓存。

注:在实际中一二级缓存的使用习惯和普遍采用的策略,这个需要慢慢积累~~~

目录

0.什么是缓存,为什么要引入缓存:

1.Mybatis中的缓存简介:

(1)一级缓存和二级缓存简介

(2)缓存的范围

(3)在编码中如何使用二级缓存,二级缓存的规则

2.案例演示

(1)案例1:一级缓存

当然,第一次查询的时候,是会执行SQL语句,从数据库去提取结果的;

但是,第二次查询相同的数据的时候,第二次就会从本sqlSession的一级缓存中去提取数据了;

不同的sqlSession之间,其缓存的对象只对自己available

sqlSession执行commit之后,会清空一级缓存

(2)案例2:二级缓存(重点内容)

首先,二级缓存初体验,开启二级缓存

catch 标签的四个属性(重点内容)

SQL语句标签上的属性


0.什么是缓存,为什么要引入缓存:

什么是缓存:缓存是缓冲存储的意思,是Mybatis中用于数据优化,提高程序执行效率的有效方式。

比如,第一次查询时获取到了【“婴幼儿奶粉”这条数据】,紧接着因为程序的需要,还需要重新的获取一次【“婴幼儿奶粉”这条数据】;

如果没有缓存:其需要再次从数据库中把【“婴幼儿奶粉”这条数据】提取出来;但是,要知道MySQL的数据是存储在硬盘上的,硬盘的读写速度很慢;而且,上面两次获取的都是【“婴幼儿奶粉”这条数据】;

为了优化:引入缓存,把第一次查询时获取到的【“婴幼儿奶粉”这条数据】放在某个内存的区域中,当再次需要获取【“婴幼儿奶粉”这条数据】的时候,不去读取数据库了,而是直接从内存中提取【“婴幼儿奶粉”这条数据】;内存的读取速度比硬盘快几十倍。


1.Mybatis中的缓存简介:

(1)一级缓存和二级缓存简介

(1) 一级缓存:是Mybatis默认开启的,其数据对象存储的范围是SqlSession这个级别,即在同一个SqlSession处理的过程中,对同一个数据进行反复提取时,其就会利用到缓存机制,来提高程序的处理速度。

(2) 二级缓存:没有默认开启,需要手动开启,其存储的范围是Mapper NameSpace。即,其范围是Mapper映射器的某个命名空间;

(注:这个范围很容易理解了,就是可访问的方位,或者数available的范围)

……………………………………………………

(2)缓存的范围

(1)存储对象: 上图,比如当前程序有两个命名空间,当调用Mapper XML文件中某个命名空间(比如goods这个命名空间爱你)的SQL语句后,查询到的【“婴幼儿奶粉”这条数据】这条数据在实际存储的时候是以对象的形式存储的,而这个对象 可以认为 是“隶属于”该命名空间(比如goods这个命名空间)的,这个对象是被存储到了JVM内存的。。同时,Mybatis中存储对象,其本质是使用Map这种数据结构来保存的。

(2)SqlSession范围的:一级缓存: 比如实际中,有3个用户,每一个用户都需要创建他自己的SqlSession会话对象;默认情况下SqlSession的一级缓存是开启的,在SqlSession中进行的数据查询工作得到的数据对象都会保存下来;;;但是在某个SqlSession中得到的任何查询结果对象,其默认的生存周期都是仅限于当前的SqlSession对象中;;;;即,每一个用户所得到的一级缓存,其只对自己有效,而且,当用户的SqlSession对象被释放的时候,存储在里面的缓存对象都会被清空。

一般,某个用户为了访问数据库,其会建立一个SqlSession对象,通常用户在进行完某一个操作后,其马上会把这个SqlSession释放掉,,,这也意味着,存储在SqlSession的一级缓存重复使用率不高,同时也可能会浪费额外的内存空间。

(3)NameSpace命名空间的: 二级缓存:为了解决一级缓存的问题,引入了二级缓存。

范围隶属于goods这个命名空间的缓存对象,会被所有同命名空间中的所有的SqlSession共享。。。。( _ _ 待验证问题:同一个SqlSession也可能会调用不同命名空间中的SQL哎~~~__ )。。。。即,他的意思是,比如SqlSession1调用了goods命名空间中的某条SQL,获取到了【“婴幼儿奶粉”这条数据】。。。。当SqlSession2还调用goods命名空间中的那条SQL,想获取【“婴幼儿奶粉”这条数据】的时候,二级缓存就起作用了。

……………………………………………………

(3)在编码中如何使用二级缓存,二级缓存的规则

(1) 所有的二级缓存,默认都会为当前命名空间中的 查询 操作,提供缓存;而,二级缓存的开启需要在Mapper XML中进行简要配置;

(2) 这么做的目的是:保证数据的一致性。设想,A用户进行写操作后(PS:由于事务的关系,写操作如果想影响到数据库需要commit啦),这个写操作已经修改了某条数据,,如果不清空缓存,当B用户来查询该条数据的时候,如果其还是从缓存中获取,(但实际上,这条数据已经被修改了),这样B用户得到的数据和数据库底层存储的就不一致了。。。。。。所以,在任何用户在commit后,都会把该NameSpace下的所有的缓存都强制清空,这样以后,后面再获取数据都需要从数据库中读取数据了,(保证数据的一致)。

说明一个问题:只有查询操作的SqlSession在commit的时候是不会清空二级缓存的, 但是会清空一级缓存。● SqlSession执行更新操作(update、delete、insert)后并执行SqlSession.commit()时,不仅清空其自身的一级缓存(执行更新操作的结果),也清空二级缓存。● 但是在查询时,commit是不会清除二级缓存的,所以这里没有效果由于查询操作其实并不会改变数据,因此对数据没有影响。所以我们通常会说commit之后会将二级缓存也清空。

(3) 可以设置某条SQL不使用缓存。(即,调用该SQL产生的结果对象不存储到缓存空间中吧)只需要在该SQL所在的<select>标签中,设置useCache=false;

(4) 如果给某条SQL所在的<select>标签,设置flushCache=true,则表示在调用这条SQL以后,清空该NameSpace下的缓存。


2.案例演示

(1)案例1:一级缓存

以这个goods命名空间下的这个selectById为例啦;

当然, 第一次 查询的时候,是会执行SQL语句,从数据库去提取结果的;

 
    package com.imooc.mybatis;
 
    import com.imooc.mybatis.dto.GoodsDTO;
    import com.imooc.mybatis.entity.Goods;
    import com.imooc.mybatis.entity.Student;
    import com.imooc.mybatis.utils.MyBatisUtils;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
 
    import java.io.IOException;
    import java.io.Reader;
    import java.sql.Connection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
 
    /**
     * JUnit单元测试类
     */
    public class MyBatisTestor {
 
        @Test
        public void testLv1Cache() throws Exception {
            SqlSession session = null;
            try {
                session = MyBatisUtils.openSession();
                Goods goods = session.selectOne("goods.selectById", 1666);
                System.out.println(goods.getGoodsId()+":"+goods.getTitle());
            } catch (Exception e) {
                throw e;
            }finally {
                MyBatisUtils.closeSession(session);
            }
        }
    }

但是,第二次查询 相同的 数据的时候,第二次就会从本sqlSession的一级缓存中去提取数据了;

不同的sqlSession之间,其缓存的对象只对自己available

sqlSession执行commit之后,会清空一级缓存


(2)案例2:二级缓存(重点内容)

首先,二级缓存初体验,开启二级缓存

在对应的Mapper的XML映射文件中增加以下配置,开启二级缓存

        <!--cache标签,开启二级缓存-->
        <cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>

 
    package com.imooc.mybatis;
 
    import com.imooc.mybatis.dto.GoodsDTO;
    import com.imooc.mybatis.entity.Goods;
    import com.imooc.mybatis.entity.Student;
    import com.imooc.mybatis.utils.MyBatisUtils;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
 
    import java.io.IOException;
    import java.io.Reader;
    import java.sql.Connection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
 
    /**
     * JUnit单元测试类
     */
    public class MyBatisTestor {
 
        @Test
        public void testLv2Cache() throws Exception {
            SqlSession session = null;
            try {
                session = MyBatisUtils.openSession();
                Goods goods = session.selectOne("goods.selectById", 1666);
                session.commit();
                Goods goods1 = session.selectOne("goods.selectById", 1666);
                System.out.println(goods);
                System.out.println(goods1);
            } catch (Exception e) {
                throw e;
            }finally {
                MyBatisUtils.closeSession(session);
            }
 
            try {
                session = MyBatisUtils.openSession();
                Goods goods = session.selectOne("goods.selectById", 1666);
                Goods goods1 = session.selectOne("goods.selectById", 1666);
                System.out.println(goods);
                System.out.println(goods1);
            } catch (Exception e) {
                throw e;
            }finally {
                MyBatisUtils.closeSession(session);
            }
        }
    }
 

此时的运行效果:看效果;

其中,【Cache Hit Ratio】表示 缓存命中率 。sqlSession中只有查询的话,在Commit的时候,是不会清空二级缓存的。

catch 标签的四个属性(重点内容)

(1)eviction属性: 代表了缓存的清除策略;(即,当缓存对象数量达到上限(或者达到在flushInterval定义的时间)后,自动触发对应的算法清除缓存对象;)( eviction的意思是驱逐,驱赶。

● 第一种策略, LRU策略 :移除最长时间不被使用的对象;

Mybatis缓存中目前存储的对象Obj1Obj2Obj3Obj4Obj512
该对象距离上次被提取访问的时间15998751137

上表中,Obj3对象闲置的时间最长;当前缓存中最多只能容纳512个对象,当第513个对象进入缓存,此时就会自动触发缓存清除策略,在LRU算法中会将闲置最久的Obj3对象剔除出缓存,然后将第513个对象放入缓存……后面的第514个对象等同理。

Mybatis的eviction属性默认使用LRU算法,,因为LRU可以保证缓存的命中率尽可能的高。

…………………………

● 第二种策略: LFU策略 :最近最少使用的被踢出

Mybatis缓存中目前存储的对象Obj1Obj2Obj3Obj4Obj512
该对象最近一段时间内,被访问的次数351613624

上表中,Obj4最近被访问了6次,次数最少;那么当第513个对象进入缓存,此时其会剔除Obj4……

…………………………

● 第三种策略:FIFO策略:先进先出,按对象进入缓存的顺序来移除他们;

这种策略,不能保证缓存的命中率,在实际中使用的比较少。

…………………………

● 第四种策略:SOFT策略:软引用:移除基于垃圾收集器状态和软引用规则的对象;

(这种策略,其本身并不是Mybatis进行管理的,而是Mybatis将这个对象的移除工作交给了JVM的垃圾回收器,基于垃圾回收器的状态来进行。)

软引用是在JVM中一种相对弱的对象关联状态,当JVM内存不够的时候,JVM会将基于SOFT策略的对象进行清除。

(软引用不推荐使用,因为这不是Mybatis自己来进行控制的。)

…………………………

● 第五种策略:WEAK策略:弱引用:更积极的移除基于垃圾收集器状态和弱引用规则的对象;

(这种策略,其本身并不是Mybatis进行管理的,而是Mybatis将这个对象的移除工作交给了JVM的垃圾回收器,基于垃圾回收器的状态来进行。)

在JVM中,只要是进行垃圾回收时,JVM就会将基于WEAK策略的对象进行清除。

(弱引用不推荐使用,因为这不是Mybatis自己来进行控制的。)

(2)flushInterval属性: 代表缓存的清除间隔。即,间隔多长时间,自动清除缓存。单位为毫秒,1000毫秒等于1秒啦;比如【flushInterval=“600000”】表示每隔10分钟就自动对缓存进行一下全局清除。

● flushInterval属性可以适当的设置长一些,比如设置为3600000毫秒(即1小时)。

● 这个属性可以帮助我们有效的及时回收内存。

● 如果内存非常富裕的话,该属性也可以再设置的长一些。

(3)size属性: 缓存的长度。即当前的goods这个命名空间中,最多可以缓存多少个对象。

● 查询结果是一条记录,其对象就是一个实体类,这是一个对象;;;查询结果是多条记录,其对象就是一个包含了多个实体类的List集合,这个集合也是一个对象;

● 但在实际中,不建议把List作为对象,保存到二级缓存中,,,,,因为,往往返回List的查询结果一般是多边的,其缓存命中率比较低。

● 如果查询结果是一条记录,对象就是一个实体类的,这种对象保存到二级缓存中就很OK;

● size的长度不要太小,比如如果t_goods表中有1500条记录,那么相应的在内存足够的情况下,这个size就应该不小于1500;这就意味着,所有的商品实体对象都可以保存在二级缓存中;;;;这样做的的好处是,在进行按Id查询的时候,会直接从内存中提取数据,这个效率是非常高的。

(4)readOnly属性: 是否是只读的,有true和false两个属性。

● readOnly=true:代表返回只读缓存,意思是每次直接把缓存对象从内存中取出来,,,这样的处理效率比较高;(其实就是,让新创建的变量直接指向缓存中对象的地址啦)

● readOnly=false:在获取缓存中的对象的时候,不是获取缓存中原始的对象,而是原始对象的一个副本;(其实就是,mybatis会创建缓存中对象的一个副本,让新创建的变量指向这个被“copy”的对象啦)。。。。这样做,每一次取出的对象都是不同的,安全性比较高,因为对副本的修改并不会影响到程序的全局。。。。。。。。。。。但是,相比于缓存的用途,往往缓存的数据在取出来之后并不会做修改,即便是做了修改,也会马上会进行数据的新增和保存,所以在大多是情况下,readOnly设置为true(因为 readOnly=true效率高~~~)

SQL语句标签上的属性

(1)useCache属性: 是否使用缓存;

●【useCache=true】表示使用缓存,即本条SQL标签中的查询结果保存到缓存中;

●【useCache=false】 表示不使用缓存,即本条SQL标签中的查询结果不保存到缓存中,同时也意味着每一次执行这个查询的时候,也都不从缓存中找,而是从数据库中去查询。

● 根据,上述的几个例子,可以发现,当一个SQL标签不写useCache属性时候,其默认是true。

………………………………

(2)flushCache属性: 立即、强制清空缓存。

●【flushCache=true】表示立即清空缓存;【flushCache=false】表示不立即清空缓存。

● 根据,上述的几个例子,可以发现,当一个SQL标签不写flushCache属性时候,其默认是false。

已知,当执行了如新增、删除、修改这些操作后,sqlSession执行commit后,一级缓存和二级缓存都会被清空。(PS:如果只进行了查询,commit只会清空一级缓存,而不会清空二级缓存)

但是,比如下面执行完<insert>中的语句后,立马清空缓存,而不是等待sqlSession执行commit的时候再清空。

自然,flushCache也可以在<select>标签上使用,当在某<select>标签上设置【flushCache=true】后,执行完该<select>标签中的SQL语句后会立即清空缓存,同时该<select>标签的返回结果也不会被放入缓存。