【Mybatis进阶】一级缓存和二级缓存

程序中为什么使用缓存?
  先了解一下缓存的概念:原始意义是指访问速度比一般随机存取存储器快的一种RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。对于我们编程来说,所谓的缓存,就是将程序或系统经常要调用的对象(临时数据)存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统的开销,提高效率。
  对缓存有了一定的了解以后就知道了使用缓存是为了减少和数据库的交互次数,提高执行效率。那么下一个问题来了。什么样的数据能使用缓存,什么样的数据不能使用?
  这是我们使用缓存必须要明确的事情,实际上适用于缓存的数据:经常查询并且不经常改变的,并且的数据的正确与否对最终结果影响不大的、不适用于缓存的数据:经常改变的数据,数据的正确与否对最终结果影响很大的。
  Mybatis中的一级缓存和二级缓存到底缓存了什么,缓存了以后又有什么效果,缓存的数据什么时候会被清空?
  一级缓存:它指的是Mybatis中sqlSession对象的缓存,当我们执行查询以后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map,当我们再次查询同样的数据,mybatis会先去sqlsession中查询是否有,的话直接拿出来用,当SqlSession对象消失时,mybatis的一级缓存也就消失了,同时一级缓存是SqlSession范围的缓存,当调用SqlSession的修改、添加、删除、commit()、close()等方法时,就会清空一级缓存。
  二级缓存:他指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,但是其中缓存的是数据而不是对象,所以从二级缓存再次查询出得结果的对象与第一次存入的对象是不一样的。
  

一级缓存和二级缓存的关系

实例

通过简单的例子来加深理解一级缓存和二级缓存。

还是老例子,用户和订单。本篇博客基于mybatis的环境已经搭建完成,如果不知道如何搭建,具体可以阅读笔者的博客——【从零开始学Mybatis笔记(三)】Dao开发方法

第一步:先简单的写个查询和修改方法

    <select id="queryUserById" parameterType="int" resultType="User">
        SELECT *
        FROM user
        WHERE id = #{id};
    </select>

    <update id="updateUserById" parameterType="User">
        UPDATE user
        SET username = #{username},
            birthday = #{birthday},
            sex      = #{sex},
            address  = #{address}
        WHERE id = #{id};
    </update>

命中一级缓存

第二步:调用同一条SQL测试

    @Test
    public void test2(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //第一次获取该用户
        User user1 = userMapper.queryUserById(1);
        System.out.println(user1);
        //第二次获取该用户
        User user2 = userMapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user1 == user2);
        sqlSession.close();
    }

测试代码非常简单,就是调用同一个查询两次,分别指向不同的实体,最后判断实体的地址是否相同。让我们来看看结果吧。

结果显而易见,如图所示。

第三步:两次调用同一条SQL中插入一条更新的SQL

    @Test
    public void test2(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //第一次获取该用户
        User user1 = userMapper.queryUserById(1);
        System.out.println(user1);
        //更新
        user1.setBirthday(new Date());
        userMapper.updateUserById(user1);
        //第二次获取该用户
        User user2 = userMapper.queryUserById(1);
        System.out.println(user2);
        System.out.println(user1 == user2);
        sqlSession.close();
    }

代码简洁而且清晰,所以我们直接来看结果。

可以看出两次查询的结果不同,是因为update方法commit了sqlsession。

命中二级缓存

第四步:配置二级缓存

1.配置Mybatis框架支持二级缓存

<setting name="cacheEnabled" value="true"/>

2.配置UserMapper.xml支持二级缓存

 <cache/>

可以看到,具体的mapper中仅仅就一个标签,并没有配置啥东西,这是因为mybatis中有默认的实现,我们如果不配置,那么就默认使用那个默认的实现。
  3.配置查询的方法支持二级缓存

    <select id="queryUserById" parameterType="int" resultType="User"  useCache="true">
        SELECT *
        FROM user
        WHERE id = #{id};
    </select>

第五步:测试

@Test
    public void test3(){
        //第一次查询 并更新二级缓存
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUserById(1);
        System.out.println(user1);
        //commit()方法提交二级缓存 同时清空一级缓存
        sqlSession1.commit();
        sqlSession1.close();

        //第二次查找命中二级缓存
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.queryUserById(1);
        System.out.println(user2);
        //commit()方法提交二级缓存 同时清空一级缓存
        sqlSession2.commit();
        sqlSession2.close();
        System.out.println(user1 == user2);
    }

如果出现序列化错误,可能是实体类没有序列化。如下修改即可

public class User implements Serializable{

第六步:对比测试

@Test
    public void test3(){
        //第一次查询 并更新二级缓存
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.queryUserById(1);
        System.out.println(user1);
        //commit()方法提交二级缓存 同时清空一级缓存
        sqlSession1.commit();
        sqlSession1.close();

        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
        User user3 = userMapper3.queryUserById(1);
        user3.setAddress("美国");
        userMapper3.updateUserById(user3);
        sqlSession3.commit();
        sqlSession3.close();

        //第二次查找命中二级缓存
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.queryUserById(1);
        System.out.println(user2);
        //commit()方法提交二级缓存 同时清空一级缓存
        sqlSession2.commit();
        sqlSession2.close();
        System.out.println(user1 == user2);
    }

小结

mybatis的一级缓存是SqlSession级别的缓存,一级缓存缓存的是对象,当SqlSession提交、关闭以及其他的更新数据库的操作(例如insert,update,delete)发生后,一级缓存就会清空。

二级缓存是SqlSessionFactory级别的缓存,同一个SqlSessionFactory产生的SqlSession都共享一个二级缓存,二级缓存中存储的是数据,当命中二级缓存时,通过存储的数据构造对象返回。单纯的由SqlSessionFactory创造的SqlSession提交、关闭或者更新数据库的操作(例如insert,update,delete)发生后,二级缓存都不会清空。只有当更新了数据库的SqlSession进行提交操作,才会导致二级缓存的清空。

查询数据的时候,查询的流程是二级缓存>一级缓存>数据库。

其他配置

mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。

<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">

  这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数据库中获取。
在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

设置statement配置中的flushCache=”true” 属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">

  一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可,这里只是提一下。

参考文献

Mybatis的一级缓存和二级缓存的理解以及用法
手把手带你阅读Mybatis源码(三)缓存篇
【MyBatis学习13】MyBatis中的二级缓存

原文地址:https://www.cnblogs.com/zllk/p/14245208.html