前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java架构之路-(源码)mybatis的一二级缓存问题

java架构之路-(源码)mybatis的一二级缓存问题

作者头像
小菜的不能再菜
修改2019-10-09 09:11:35
4740
修改2019-10-09 09:11:35
举报
文章被收录于专栏:java_python

  上次博客我们说了mybatis的基本使用,我们还捎带提到一下Mapper.xml中的select标签的useCache属性,这个就是设置是否存入二级缓存的。

回到我们正题,经常使用mybatis的小伙伴都知道,我们的mybatis是有两级缓存的,一级缓存默认开启,我们先来一下一级缓存吧,超级简单。 一级缓存:

我们还拿上次的源码来说

代码语言:javascript
复制
package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);//这句执行了sql,也就是说,这句给一级缓存塞了值
        StudentBean result2 = mapper.selectUser(1);//这句执行了sql,也就是说,这句给一级缓存塞了值

        System.out.println(result==result2);
    }
}

我们可以看到打印结果为true,说明了命中了我们的一级缓存。

一级缓存的限制比较多,需要在同一个session,同一个会话,同一个方法(statement),内执行完全相同的sql,才能保证缓存的成功。

我们打开Mybatis里的BaseExecutor类我们找到152行代码。

代码语言:javascript
复制
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

这个就是我们的一级缓存localCache。清空方法是在BaseExcutor的116行clearLocalCache,来清空我们的一级缓存的,所以说执行update以后一级缓存会被清空,后面有机会我会告诉大家我是怎么找到的,只要记住一级缓存默认开启,是sqlSession级别的,几乎是没有生命的。然后记住什么情况下可以用,什么情况下不可以用,初级面试应该可以应付。

二级缓存:

二级缓存需要手动设置,只要在我们的配置文件内加入Cache标签就可以了。或者加入@Cache注解也是ok的,二级缓存是在session关闭时才写入的。为什么这样设计呢?我们来假想一下,我们开启session,做了一个insert写入,这时还没有提交,然后我们进行了查询,如果这时写入缓存,然后我们将insert进行回滚,那么我们的缓存就多了我们刚才写入的数据,这样的设计是显然不合理的,我们先来看一下二级缓存是怎么设置的。 谁说查询时候先查二级缓存,二级缓存没有再查一级缓存的,一律打死,一级缓存作用在session会话范围,你二级缓存的存入条件是session关闭,session都关闭了,还有毛线一级缓存了....

还是上次的代码:我们来回顾一下。

代码语言:javascript
复制
package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);
        session.close();

        session = sqlSessionFactory.openSession();
        StudentMapper mapper2 = session.getMapper(StudentMapper.class);
        StudentBean result2 = mapper2.selectUser(1);
        System.out.println(result == result2);

    }

}
代码语言:javascript
复制
<?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="mybatis.dao.StudentMapper">
    <cache></cache>
    <select id="selectUser" resultType="mybatis.bean.StudentBean">
        select * from student t where t.id = #{id}
    </select>
</mapper>

我们只需要加入cache标签即可以使用我们的二级缓存。select标签内有一个useCache属性设置成false就是说,这个sql不写入我们的缓存。需要注意的是要给予我们的实体Bean序列化,正因为序列化,我们的输入结果是false,说明并不是一个对象的。后面我会解释为什么需要做一个序列化,可以带着问题继续阅读。

注解方式这样写就ok了。

代码语言:javascript
复制
package mybatis.dao;

import mybatis.bean.StudentBean;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Select;


@CacheNamespace
public interface StudentMapper {

    @Select("select * from student t where t.id = #{id}")
    StudentBean selectUser(int id);
}

二级缓存适用范围:

1,必须是session提交以后,二级缓存才写入。

2,必须是同一个命名空间之下。

3,必须是相同的sql和参数。

4,如果是readWrite=true,实体类必须序列化

代码语言:javascript
复制
@CacheNamespace(readWrite = false)

这也就是我们说的为什么需要实例化,其实也可以不序列化的。但是我们要是改了其中一个数据,另外一个拿到的数据一定是修改后的,没有特殊需求最好是做一个序列化,不要写readWrite=false的设置,不写readWrite=false会提高一点点性能,但是自我觉得没必要冒那种风险。拿着这段代码自己测试一下,不带序列化的深拷贝对象会造成的结果。

代码语言:javascript
复制
package mybatis;

import mybatis.bean.StudentBean;
import mybatis.dao.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

public class Test1 {

    public SqlSession session;
    public SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        session =  sqlSessionFactory.openSession();
    }

    @Test
    public void studentTest(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        StudentBean result = mapper.selectUser(1);
        System.out.println(result);
        result.setId(2222);
        session.commit();

        session = sqlSessionFactory.openSession();
        StudentMapper mapper2 = session.getMapper(StudentMapper.class);
        StudentBean result2 = mapper2.selectUser(1);
        System.out.println(result2);

        System.out.println(result == result2);
    }
}

5,必须是相同的statement相同的方法。

内部还可以加很多属性的。

@CacheNamespace( implementation = PerpetualCache.class, // 缓存实现 Cache接口 实现类 eviction = LruCache.class,// 缓存算法 flushInterval = 60000, // 刷新间隔时间 毫秒 size = 1024, // 最大缓存引用对象 readWrite = true, // 是否可写 blocking = false // 是否阻塞,防止缓存击穿的。 )

我们来简单的深入一下二级缓存的源码,我们在Mybatis的包里会看到这样一个文件,一个叫Cache的文件,也就是我们的缓存文件。

而且我们发现很多叫***Cahe的类都实现了他

TransactionalCache注释里明显的写到The 2nd level cache transactional buffer.二级缓存事务缓冲区。那么我们把断点打在他的get和put方法上,(可能是一个错误的示范,我会一步步告诉你们错了怎么改)

断点进到了getObject方法,我们点击开右边的参数栏,点击this,我们会看到我们的delegate参数,写着什么什么cache,再次点击还会发现什么什么Cache,直到不能向下点击为止

我们发现貌似实际存储的貌似是PerpetualCache,我们发现我们的错误了,重新来过,清楚断点,打开我们的PerpetualCache类,断点重新打在PerpetualCache类的get和put方法下。我们左侧的方法区,我们看到是这样的

从Perpetualcahe一直查到TransactionalCache,我们来张图解释一下。

大致就是这样的,逐层去寻找的。这里就是一个装饰者模式。

  我们还可以将断点打在CachingExecutor方法的query方法下来观察我们的二级缓存。这个方法在很早就先帮我们把Cache获取好了,且直接获取到SynchronizedCache层了。有兴趣的小伙伴可以自行测试一下,这里我就不再多说了,下次博客我们来具体深入的来看看Mybatis的执行流程,源码级。

感觉自己现在心中知道怎么去读源码,但是还是说不清楚,不能很好的表达出来,我再改进改进,可能还是看的不够深吧。。。

最近搞了一个个人公众号,会每天更新一篇原创博文,java,python,自然语言处理相关的知识有兴趣的小伙伴可以关注一下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-09-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档