mybatis一级缓存和二级缓存,第二天

正如大多数持久层框架一样,MyBatis 同样提供了一级缓存二级缓存的支持

  1. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空

  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace)并且可自定义存储源,如 Ehcache。

  3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

mybatis的一级缓存:
  MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,
当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,
不需要再进行一次数据库查询了。

  MyBatis会在一次会话的表示----一个SqlSession对象中创建一个本地缓存(local cache),
对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,
就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。

  一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

  一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。

  二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

  二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。

如果缓存中有数据就不用从数据库中获取,大大提高系统性能。


一级缓存

首先做一个测试,创建一个mapper配置文件和mapper接口,我这里用了最简单的查询来演示。

<mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper">

    <select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User">
        SELECT * FROM user
    </select>
</mapper>
public interface UserMapper {
    List<User> findUsers()throws Exception;
}

然后编写一个单元测试

public class UserMapperTest {


    SqlSession sqlSession = null;
    @Before
    public void setUp() throws Exception {
        // 通过配置文件获取数据库连接信息
        Reader reader = Resources.getResourceAsReader("cn/elinzhou/mybatisTest/config/mybatis.xml");
        // 通过配置信息构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 通过sqlSessionFactory打开一个数据库会话
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void testFindUsers() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        System.out.println(users);
    }
}

运行,可以看到控制台输出(先配好log4j)为类似如下图日志 
技术分享

日志说明了该操作执行的sql语句已经查询的内容,最后一行是我手动通过System.out.printf输出的结果。

然后再加一条语句

users = userMapper.findUsers();

之前的单元测试就变成了这个样子

技术分享

也就是在执行完userMapper.findUsers();后立刻再执行一遍userMapper.findUsers(); 可以想象,其实这两个操作执行的sql是完全相同的,而且在这期间没有对数据库进行过其他操作。然后执行该单元测试,发现效果跟上面执行一条的时候完全相同,也就是执行第二次userMapper.findUsers();操作的时候没有对数据库进行查询,那么得到的数据是从哪里来的?答案是一级缓存。

mybatis一级缓存是指在内存中开辟一块区域,用来保存用户对数据库的操作信息(sql)和数据库返回的数据,如果下一次用户再执行相同的请求,那么直接从内存中读数数据而不是从数据库读取。 
其中数据的生命周期有两个影响因素。

  1. 对sqlsession执行commit操作时

对sqlsession执行commit操作,也就意味着用户执行了update、delete等操作,那么数据库中的数据势必会发生变化,如果用户请求数据仍然使用之前内存中的数据,那么将读到脏数据。所以在执行sqlsession操作后,会清除保存数据的HashMap,用户在发起查询请求时就会重新读取数据并放入一级缓存中了。

技术分享

技术分享

上述测试就是在第一查询完后执行了commit操作,再进行查询。与之前的测试不同的是,这次测试控制台打印了两组查询结果,说明在commit之后mybatis对数据重新进行了查询。

  1. 关闭sqlsession

一般在mybatis集成spring时,会把SqlSessionFactory设置为单例注入到IOC容器中,不把sqlsession也设置为单例的原因是sqlsession是线程不安全的,所以不能为单例。那也就意味着其实是有关闭sqlsession的过程的。其实,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 
而一级缓存的设计是每个sqlsession单独使用一个缓存空间,不同的sqlsession是不能互相访问数据的。当然,在sqlsession关闭后,其中数据自然被清空。

二级缓存

在使用二级缓存之前,先测试之前提到过的关闭sqlsession后会清空缓存的问题,把junit代码修改一下

@Test
    public void testFindUsers() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        //关闭sqlsession
        sqlSession.close();

        //通过sqlsessionFactroy创建一个新的sqlsession
        sqlSession = sqlSessionFactory.openSession();
        //获取mapper对象
        userMapper = sqlSession.getMapper(UserMapper.class);
        users = userMapper.findUsers();
        System.out.println(users);
    }

这段代码在第一次查询完后关闭sqlsession,然后创建新的sqlsession和mapper来重新执行一次查询操作,可以预见,执行结果如图

技术分享

说明关闭了sqlsession后的确把之前的缓存数据清空了,之后再执行同样的查询操作也会再访问一遍数据库。为了解决这个问题,需要使用二级缓存

一级缓存的作用域仅限于一个sqlsession,但是二级缓存的作用域是一个namespace。但并不是意味着同一个namespace创建的mapper可以互相读取缓存内容,这里的原则是,如果开启了二级缓存,那么在关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到namespace的二级缓存中。

接下测试,先需要开启二级缓存。

1.打开二级缓存总开关 
打开总开关,只需要在mybatis总配置文件中加入一行设置

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

2.打开需要使用二级缓存的mapper的开关

在需要开启二级缓存的mapper.xml中加入caceh标签

<cache/>

3.POJO序列化

让需要使用二级缓存的POJO类实现Serializable接口,如

public class User implements Serializable {

通过之前三步操作就可以使用二级缓存了,接下来测试。添加一个Junit方法

@Test
    public void testFindUsersCache() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        //关闭sqlsession
        sqlSession.close();

        //通过sqlsessionFactroy创建一个新的sqlsession
        sqlSession = sqlSessionFactory.openSession();
        //获取mapper对象
        userMapper = sqlSession.getMapper(UserMapper.class);
        users = userMapper.findUsers();
        System.out.println(users);
    }

执行后可以发现,控制台值输出了一次查询过程,也可以证明二级缓存开启成功。

还有一个问题,之前说了,即使开启了二级缓存,不同的sqlsession之间的缓存数据也不是想互访就能互访的,必须等到sqlsession关闭了以后,才会把其一级缓存中的数据写入二级缓存。为了测试这个,把上述代码中的

sqlSession.close();

注释,那么之前的代码就变成了 
技术分享

再执行,发现控制太又输出了两次的查询过程,所以可以印证,只有关闭了sqlsession之后,才会把其中一级缓存数据写入二级缓存。

缓存配置

  • 关闭刷新

在默认情况下,当sqlsession执行commit后会刷新缓存,但是也可以强制设置为不刷新,在不需要刷新的标签中加入

flushCache="false" 

<select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User" flushCache="false">

那么,无论是否执行commit,缓存都不会刷新了。但是这样会造成脏读,只有在特殊情况下才使用

  • 自动刷新

有些情况下,需要设置自动刷新缓存,那么需要配置对应mapper中的cache标签。

flushInterval="10000"

该属性表示每隔10秒钟自动刷新一遍缓存




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值