Mybatis框架学习笔记 --- [缓存初步理解]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis框架学习笔记 --- [缓存初步理解]相关的知识,希望对你有一定的参考价值。

mybatis3版本的文档 -->mybatis文档


模拟CPU,内存和硬盘之间的关系

CPU运行最快,这三个之间的运行速度差距可能会导致并发问题

本来,数据在硬盘存着,现在可以考虑把数据缓存到内存上;这样的话,CPU要读取数据,直接在内存上读取缓存即可;暂时减轻数据库的压力;

但是有的数据还是必须要去硬盘直接读取的,比如说要读取的这部分数据变动比较快;缓存的数据还保留着之前的数据,但是原数据已经变了;那就得去硬盘读取数据了;

使用缓存(cache)可减少数据库的压力,提高数据库的性能;缓存是使用 Map 集合缓存数据
实际就是缓解查询的压力;
就是把上次在数据库查询的对象存储到内存中去;如果下次还要查询使用;直接在缓存里面取出即可;而减少了查询的次数;减少对数据库的访问次数;

一级缓存

mybatis的话,包含着一级缓存和二级缓存;

只要不进行缓存相关的配置,mybatis的一级缓存是默认存在的;

一级缓存是sqlSession 会话级别的;即只要保证在同一个会话内的数据就会被缓存下来;直到这次会话结束关闭时,才会让缓存中的数据销毁掉;如果没有声明需要刷新,并且缓存没有超时的情况下, SqlSession 都会取出当前缓存的数据,而不会再次发送 SQL 到数据库

一级缓存的声明周期

  • MyBatis 开启一个数据库会话后,创建一个新的 SqlSession 对象,这个SqlSession 对象中包含一个新的Executor 对象Executor 对象里面包含一个新的 PerpetualCache 对象,若此时 SqlSession 调用close()方法来关闭掉这次会话,会释放掉一级缓存 PerpetualCache 对象,一级缓存就失效了。
  • 让SqlSession 调用 clearCache(),就会清空 PerpetualCache 对象中的缓存数据,但是该对象仍可使用,即还能使用一级缓存功能.
  • 若在SqlSession 中执行任何的(增删改sql操作)(update(),delete(),insert()) ,同样会清空PerpetualCache 对象的数据,但是该对象可以继续使用;即可以继续使用一级缓存功能

案例,还是用这个年龄大于某个范围的sql来作为执行案例吧

持久层接口EmployeeMapper下的方法

//根据年龄大于某个范围进行查询;
List<Employee> getEmpByExceedAge(Integer age);

对应的映射文件定义sql

<!--根据年龄大于某个范围进行查询;-->
    <select id="getEmpByExceedAge" resultType="employee">
       select  * from t_employee where age<![CDATA[ > ]]> #{age};
    </select>

测试一级缓存;
在同一次会话下进行相同的查询; 然后再开一个会话进行同样的查询

 //测试一级缓存;   getEmpByExceedAge
@Test
public void getEmpByExceedAge2(){
    //调用工具类;
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //获取代理对象;
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    //调用方法; 查询年龄大于20岁的员工;
    List<Employee> res1 = mapper.getEmpByExceedAge(20);
    List<Employee> res2 = mapper.getEmpByExceedAge(20);

    System.out.println("在一次会话内取到得结果是否来自同一地址---->"+(res1 == res2));
    //两次取到得结果是否来自同一地址---->true

    //关闭第一次的会话sqlSession;
    sqlSession.close();

    //若再开一个会话2;
    //调用工具类;
    SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
    //获取代理对象;
    EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
    //调用方法; 查询年龄大于20岁的员工;
    List<Employee> res3 = mapper2.getEmpByExceedAge(20);


    System.out.println("看看这两次会话取得是否来自同一地址--->"+(res2 == res3));
    // 两次取到得结果是否来自同一地址--->false
    //关闭第二次会话sqlSession;
    sqlSession2.close();
}

执行时可发现;在同一会话下;在次查询同一个方法,且参数相同,即查询得到的对象相同;第二次查询的话,它读取的是缓存里面的数据;

但是关闭了第一次会话后;开启了第二次会话,再去查询,那个缓存的数据信息已经不存在了;所以它会去访问数据库进行查询

那么,我要是在同一会话下进行不同的查询呢;

//测试一级缓存;   getEmpByExceedAge
@Test
public void getEmpByExceedAge0(){
    //调用工具类;
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //获取代理对象;
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    
    //调用方法; 查询年龄大于20岁的员工;
    List<Employee> res1 = mapper.getEmpByExceedAge(20);
    
    //注意 ;这里查询年龄大于22岁的员工;
    List<Employee> res2 = mapper.getEmpByExceedAge(22);
    
    //这里再次查询年龄大于20岁的员工;
    List<Employee> res3 = mapper.getEmpByExceedAge(20);

    System.out.println("在一次会话内取到得结果是否来自同一地址---->"+(res1 == res2));
    System.out.println("在一次会话内取到得结果是否来自同一地址---->"+(res1 == res3));
    //两次取到得结果是否来自同一地址---->true

    //关闭这次会话sqlSession;
    sqlSession.close();
}

注意;在这同一次会话内;它缓存的是相同的查询得到的对象信息;
我在第一次和第二次查的不是同一个信息;所以第二次就得去数据库访问查询读取;
第三次和第一次的查询一样;所以第三次就用了第一次的缓存数据

二级缓存

二级缓存是 SqlSessionFactory 级别; 会话工厂;也就是会创建sqlSession会话的那个接口;
那么也就是说,在不同的sqlSession中,都能得到之前的缓存数据信息;即使上一次的会话已经关闭了,数据也会存入二级缓存;
那么也就是说,只要在同一个mapper下;要缓存的信息都是互通的;
但是mapper不同;自然就不能得到缓存的数据了;即二级缓存的隔离级别在于mapper;

每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。

sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。

要使用到二级缓存的话;
首先去mybatis的核心配置文件下开启缓存;即在<settings>下设置开启cacheEnabled

<settings>
   <!--开启二级缓存,默认为true-->
   <setting name="cacheEnabled" value="true"/>
</settings>

其实,本人当前使用的mybatis版本下;这个已经默认配置为true状态了

然后第二步的话,还需要把实体类序列化;即让这些类实现序列化接口 Java.io. Serializable;

第三步,再去配置映射文件;
在 Mapper 映射文件中添加<cache />
我在这里的案例下;就在EmployeeMapper.xml下设置一下了;

 <!--设置开启二级缓存-->
    <cache >    
    </cache>

测试使用;查询大于某个年龄的员工

//测试二级缓存;
@Test
public void getEmpByExceedAge3(){
    //调用工具类;
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    //获取代理对象;
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    //调用方法; 查询年龄大于20岁的员工;
    List<Employee> res1 = mapper.getEmpByExceedAge(20);
    List<Employee> res2 = mapper.getEmpByExceedAge(20);
    
    //关闭第一次会话sqlSession;
    sqlSession.close();
    //若再开一个会话;
    //调用工具类;
    SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
    //获取代理对象;
    EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);
    //调用方法; 查询年龄大于20岁的员工;
    List<Employee> res6 = mapper2.getEmpByExceedAge(20);
    List<Employee> res7= mapper2.getEmpByExceedAge(20);
    
    //关闭第二次会话sqlSession;
    sqlSession2.close();
}

注意到,第二次会话根本就没去连接数据库;直接用了第一次会话的缓存数据信息

在语句中可使用useCache来开关是否使用缓存;这个默认是true

比如,我这次让这个查询不使用缓存

<!--根据年龄大于某个范围进行查询;-->
<select id="getEmpByExceedAge" resultType="employee" useCache="false">
   select  * from t_employee where age<![CDATA[ > ]]> #{age};
</select>

我再去执行测试时,这时已经没有二级缓存功能了;一次会话关闭,就没有缓存的数据了

在设置开启缓存时,还能使用flushInterval=" " 来设置缓存的刷新时间;毫秒为单位;
例如这里设置刷新时间为6秒;

<!--设置缓存,设置缓存刷新时间为6秒-->
<cache flushInterval="6000">
</cache>

以上是关于Mybatis框架学习笔记 --- [缓存初步理解]的主要内容,如果未能解决你的问题,请参考以下文章

Spring学习笔记--环境搭建和初步理解IOC

mybatis学习笔记(14)-mybatis整合ehcache

Spring学习笔记--环境搭建和初步理解IOC

SSM框架MyBatis笔记 --- 三层架构;MyBatis框架结构;MyBatis 核心配置文件;sql 映射文件;MyBatis 使用初步(通过Maven添加依赖)

SSM框架MyBatis笔记 --- 表之间的关联关系;MyBatis事务;MyBatis缓存机制;ORM概述

mybatis学习笔记(13)-查询缓存之二级缓存