谈谈Mybatis的一级缓存和二级缓存

一级缓存

Mybatis支持缓存,默认情况下是开启的一级缓存,一级缓存是SqlSession级别,缓存的是SqlSession对象。在SQL语句、参数都相同的情况下,我们使用同一个SqlSession对象调用同一个Mapper方法的时候,只需要执行一次SQL,因为在第一次执行Mapper方法后,Mybatis会将其缓存起来,后面查询的时候如果参数,SQL语句相同就会直接取缓存的数据,而不会再去查询数据库,这样大大提高了查询效率。

这里借用一下别人的缓存示意图,清晰明了!

 一级缓存的生命周期

  • MyBatis一级缓存是基于SQLSession的,MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
  • SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用

一级缓存触发条件

  1. 传入的statementId一样
  2. 查询时要求的结果集中的结果范围一样
  3. 这次查询所产生的最终要传递给JDBC java.sql.的SqlPreparedstatement语句字符串(boundSql.getSql() )一样
  4. 传递给java.sql.Statement要设置的参数值一样

一级缓存使用案例

首先,实体类(注意:需要可序列化,我这里实现了Serializable接口

public class TtUser implements Serializable{
    private Long id;
    private String loginName;
    private String password;

    // 这里省略get、set方法
}

 dao方法

public interface TtUserMapper {
    TtUser selectByPrimaryKey(Long id);
    int updateByPrimaryKeySelective(TtUser record);
}

mapper.xml方法

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiateng.dao.user.TtUserMapper">

  <!--<cache eviction="LRU" flushInterval="10000" size="1024"  />-->

  <resultMap id="BaseResultMap" type="com.xiateng.entity.TtUser">
    <id column="id" jdbcType="BIGINT" property="id" />
    <result column="login_name" jdbcType="VARCHAR" property="loginName" />
    <result column="password" jdbcType="VARCHAR" property="password" />
  </resultMap>

  <sql id="Base_Column_List">
    id, login_name, password
  </sql>
  <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from t_user
    where id = #{id,jdbcType=BIGINT}
  </select>
  
  <update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TtUser">
    update t_user
    <set>
      <if test="loginName != null">
        login_name = #{loginName,jdbcType=VARCHAR},
      </if>
      <if test="password != null">
        password = #{password,jdbcType=VARCHAR},
      </if>
      <if test="blong != null">
        blong = #{blong,jdbcType=SMALLINT},
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>
  
</mapper>

Controller调用

@RequestMapping(value = "/a")
    @ResponseBody
    public String a(){
        // 开启一个SqlSession(会话)
        SqlSession sqlSession = sqlSessionFactory.openSession();
        TtUserMapper mapper = sqlSession.getMapper(TtUserMapper.class);
        TtUser ttUser = mapper.selectByPrimaryKey(1L);
        System.out.println("-----------------------------------------"+ttUser);
        System.out.println("-----------------------------------------第二次执行");
        TtUser ttUser2 = mapper.selectByPrimaryKey(1L);
        System.out.println("-----------------------------------------"+ttUser2);
        // 关闭会话
        sqlSession.close();
        return "成功";
    }

 控制台打印

我们会发现只打印了一次sql,说明第二次没有查询数据库 

 二级缓存

二级缓存是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与
第一次存入的对象是不一样的。二级缓存是namespace级别的缓存

看图

使用二级缓存需注意

  • 映射语句文件中的所有select语句将会被缓存。
  • 映射语句文件中的所有insert、update和delete语句会刷新缓存。
  • 缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
  • 根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
  • 缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
  • 缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

Mybatis默认是不开启二级缓存的,要开启的话需要进行配置,并且返回的POJO必须是可序列化的(实体类实现Serializable)。下面我们举个例子:

实体跟方法还是上面的,只不过在mapper.xml里添加配置<cache/>配置如下

<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

属性含义:

  • eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
  1. LRU,最近最少使用的,一处最长时间不用的对象
  2. FIFO,先进先出,按对象进入缓存的顺序来移除他们
  3. SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
  4. WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU, 移除最长时间不用的对形象 flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当 SQL被执行的时候才会去刷新缓存。
  • size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。 这里配置的是1024个对象
  • readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有 办法修改缓存,他的默认值是false,不允许我们修改

接下来配置Mybatis开启二级缓存,因为我是SpringBoot项目,所以我是在application.yml文件中加入如下配置:

最后测试Controller代码

    @RequestMapping(value = "/b")
    @ResponseBody
    public Map<String, Object> b(){
        Map map = new HashMap();
        TtUser ttUser = ttUserMapper.selectByPrimaryKey(1L);
        map.put("ttUser",ttUser);
        System.out.println("-----------------------------------------"+ttUser);
        System.out.println("-----------------------------------------第二次执行");
        TtUser ttUser2 = ttUserMapper.selectByPrimaryKey(1L);
        map.put("ttUser2",ttUser2);
        System.out.println("-----------------------------------------"+ttUser2);
        System.out.println("-----------------------------------------第三次执行");
        TtUser ttUser3 = ttUserMapper.selectByPrimaryKey(2L);
        System.out.println("-----------------------------------------"+ttUser3);
        map.put("ttUser3",ttUser3);
        return map;
    }

打印结果

从打印结果我们会发现,第一次查询打印了sql语句,它直接查询了数据库,第二次查询没打印sql语句,由于第二次参数,statementId,sql都一样,直接命中了二级缓存,没有查询数据库,而第三次参数不一样,打印了sql语句,直接查询了数据库

一级缓存和二级缓存区别

一级缓存:默认开启,基于SQLSession,在操作数据库时需要构造SQLSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SQLSession直接是胡不影响的。在一个SQLSession中执行增删改并提交到数据库,一级缓存会清空,避免脏读。

当关闭一个sqlsession时,一级缓存也随之消失。

二级缓存:默认不开启,基于namespace,同一个namespace下的多个SQLSession会公用一个缓存,二级缓存同样是使用HashMap就行数据存储,对比一级缓存,二级缓存作用域更大,可跨越多个SQLSession。

猜你喜欢

转载自blog.csdn.net/qq_43037478/article/details/112311351