MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)

 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)一级缓存和二级缓存简介

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第1张图片

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

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

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

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

(2)缓存的范围

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第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)在编码中如何使用二级缓存,二级缓存的规则

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第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所在的标签,设置flushCache=true,则表示在调用这条SQL以后,清空该NameSpace下的缓存。


 

2.案例演示

(1)案例1:一级缓存

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第4张图片

 

当然,第一次查询的时候,是会执行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);
        }
    }
}

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第5张图片

 

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第6张图片

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第7张图片

 

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第8张图片

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第9张图片

 

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第10张图片


 

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

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

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

    
    

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第11张图片

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);
        }
    }
}

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第12张图片

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

 

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第13张图片

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

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


 

Mybatis缓存中目前存储的对象 Obj1 Obj2 Obj3 Obj4 Obj512
该对象距离上次被提取访问的时间 15 99 875 1 137

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

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

………………………… 

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

 

Mybatis缓存中目前存储的对象 Obj1 Obj2 Obj3 Obj4 Obj512
该对象最近一段时间内,被访问的次数 35 16 13 6 24

 

上表中,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。

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第14张图片

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

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

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

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

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

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

MyBatis进阶三:Mybatis缓存;(包括一级缓存和二级缓存)_第15张图片

自然,flushCache也可以在标签上设置【flushCache=true】后,执行完该标签的返回结果也不会被放入缓存。

 

 

你可能感兴趣的:(Mybatis)