【MyBatis】缓存机制

概述

什么是缓存

     缓存是在计算机内存上进行保存的数据,其特点是将数据保存在内存当中。

为什么使用缓存

    缓存在读取的时候不需要再从磁盘读入,因此具有快速读取和使用的特点。

什么时候适合用缓存

如果缓存命中率高,可以极大提升系统性能。如果缓存的命中率比较低,就没有使用缓存的必要。因此关键在于存储的内容访问的命中率.

比较适用于:经常查询但是不经常改变的,数据的正确与否对最终结果的影响不大时。

不适用于:经常改变的数据,数据的正确与否对最终结果的影响比较大时。

MyBatis中的缓存

一级缓存

MyBatis默认情况下只开启一级缓存,一级缓存只是相对于同一个SqlSession而言。

所在在查询的参数和SQL语句不变的情况下,使用同一个SqlSession对象调用同一个Mapper的方法,往往只执行一次SQL,在第一次进行查询之后,会将查询的结果存放到缓存当中,以后如果再次查询,如果没有声明需要刷新,并且缓存也没有超时的情况下,SqlSession会取出当前缓存的结果,而不是再次发送SQL到数据库。但是如果是不同的SqlSession对象,他们之间是相互隔离的,所以对于不同的SqlSession对象执行相同的SQL,还是会向数据库发起缓存。当SqlSession对象消失时,其对应的一级缓存同样消失。

测试两个不同的SqlSession对象执行相同的查询:

    public void testFindById(){
        SqlSession sqlSession1 = factory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        System.out.println("第一次查询");
        User user1 = userDao1.findById(62);
        System.out.println(user1);

        SqlSession sqlSession2 = factory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        System.out.println("第二次查询");
        User user2 = userDao2.findById(62);
        System.out.println(user2);
        sqlSession1.close();
        sqlSession2.close();
        System.out.println(user1==user2);
    }

发现使用不同的SqlSession对象是,执行相同的查询语句其结果是不同的。

通过下面程序进行测试同一个SqlSession对象执行两次相同的查询操作:

public class UserTest {
    private InputStream is;
    private SqlSession sqlSession;
    private IUserDao userDao;

    //初始化进行封装
    @Before //用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件,形成字节输入流
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    //close进行封装
    @After //用于在测试方法执行之后执行
    public void distory() throws IOException {
        //提交事务
        sqlSession.commit();
        //6.关闭资源
        sqlSession.close();
        is.close();
    }

    //测试一级缓存
    @Test
    public void testFindById(){
        System.out.println("第一次查询");
        User user1 = userDao.findById(62);
        System.out.println(user1);
        System.out.println("第二次查询");
        User user2 = userDao.findById(62);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

}

运行结果:

可以看出只有第一次查询向数据库发送了查询命令,第二次没有发送,并且两次查询结果得到的对象地址相同。 

如果第一次和第二次查询之间将SqlSession对象关闭,则缓存消失:


public class UserTest {
    private InputStream is;
    private SqlSession sqlSession;
    private IUserDao userDao;
    private SqlSessionFactory factory;

    //初始化进行封装
    @Before //用于在测试方法执行之前执行
    public void init() throws IOException {
        //1.读取配置文件,形成字节输入流
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2.获取SqlSessionFactory
        factory = new SqlSessionFactoryBuilder().build(is);
        //3.获取SqlSession对象
        sqlSession = factory.openSession();
        //4.获取dao的代理对象
        userDao = sqlSession.getMapper(IUserDao.class);
    }

    //close进行封装
    @After //用于在测试方法执行之后执行
    public void distory() throws IOException {
        //提交事务
        sqlSession.commit();
        //6.关闭资源
        sqlSession.close();
        is.close();
    }

    //测试一级缓存
    @Test
    public void testFindById(){
        System.out.println("第一次查询");
        User user1 = userDao.findById(62);
        System.out.println(user1);
        //关闭后再次新建sqlSession
        sqlSession.close();
        sqlSession = factory.openSession();
        userDao = sqlSession.getMapper(IUserDao.class);
        System.out.println("第二次查询");
        User user2 = userDao.findById(62);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

}

 另外有一个方法可以清空缓存:

sqlSession.clearCache();

那么如果数据库的数据在查询过程中发生了改变怎么办,MyBatis如何做到数据库中数据和缓存中同步?当调用SqlSession的修改,添加,删除,commit(),close() 等方法时,都会将缓存清空。

例如在两次查询之间更新数据:


    //测试缓存与数据库的同步
    @Test
    public void testClearCache(){
        System.out.println("第一次查询用户信息");
        User user1 = userDao.findById(62);
        System.out.println(user1);
        System.out.println("更新用户信息");
        user1.setAddress("河南");
        user1.setQq("1111111");
        userDao.updateUser(user1);
        System.out.println("第二次查询");
        User user2 = userDao.findById(62);
        System.out.println(user2);
        System.out.println(user1==user2);
    }

二级缓存

它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession之间共享其缓存。

SqlSessionFactory层面上的二级缓存是不开启的,二级缓存的开启需要进行配置,需要在映射XML文件中进行配置。实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列话的,也就是实现Serializeble接口。

二级缓存的使用步骤:
      第一步:让Mybatis框架支持二级缓存,mybatis-configuration.xml 文件中加入如下代码:

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

第二步:让当前的映射文件支持二级缓存,在UserMapper.xml中添加:

<cache/> 

 第三步:让当前的操作支持二级缓存,在select标签中配置:

useCache和flushCache

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

下面这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数据库中获取。

<select id="selectUserByUserId" useCache="false" resultType="com.ys.twocache.User" parameterType="int">    
select * from user where id=#{id}
</select>

注意:在mapper的同一个namespace中,如果有其它insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。

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

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.ys.twocache.User" parameterType="int">    

select * from user where id=#{id}

</select>

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

现在开启二级缓存,然后再使用两个SqlSession对象执行相同的查询操作:

    //测试二级缓存
    @Test
    public void testFindById(){
        SqlSession sqlSession1 = factory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        System.out.println("第一次查询");
        User user1 = userDao1.findById(62);
        System.out.println(user1);
        sqlSession1.close();

        SqlSession sqlSession2 = factory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        System.out.println("第二次查询");
        User user2 = userDao2.findById(62);
        System.out.println(user2);
        sqlSession2.close();
        System.out.println(user1==user2);
    }

从运行结果可以看出,虽然是两个不同的SqlSession对象,但是由于开始了二级缓存,第二次进行查找的时候屎直接从缓存中拿到的数据,但是发现两个user实例并不一样,这是因为数据在二级缓存中不是以对象的形式存在的,而是以数据的形式存在的。 

注意:开启二级缓存,POJO中必须要进行序列化,否则会报错。

注解方式

对于注解方式中开启二级缓存可以使用注解:

  第一步:让Mybatis框架支持二级缓存,mybatis-configuration.xml 文件中加入如下代码,这一步也可以省略,因为默认为开启状态:

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

第二步:在mapper类中添加注解:

@CacheNamespace(blocking = true)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章