Mybatis 缓存

一、一级缓存

测试一
  1. 测试:
@Test
    public void test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
		//第一次查询
        List<User> users = sqlSession.selectList("com.example.mapper.UserMapper.findAll");
        users.forEach(user -> {
            System.out.println(user);
        });

        System.out.println("=============================");
		//第二次查询
        List<User> users2 = sqlSession.selectList("com.example.mapper.UserMapper.findAll");
        users2.forEach(user -> {
            System.out.println(user);
        });
    }
  1. 查看sql日志
13:23:58,390 DEBUG findAll:159 - ==>  Preparing: select * from user 
13:23:58,419 DEBUG findAll:159 - ==> Parameters: 
13:23:58,443 DEBUG findAll:159 - <==      Total: 5
User{id=1, username='lucy'}
User{id=2, username='tom'}
User{id=3, username=''}
User{id=5, username='555'}
User{id=6, username='666'}
=============================
User{id=1, username='lucy'}
User{id=2, username='tom'}
User{id=3, username=''}
User{id=5, username='555'}
User{id=6, username='666'}
测试二

1.代码:

 @Test
    public void test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

	 	//第一次查询
        List<User> users = sqlSession.selectList("com.example.mapper.UserMapper.findAll");
        users.forEach(user -> {
            System.out.println(user);
        });

	 	修改其中一个用户信息的username
        User user  = new User();
        user.setId(3);
        user.setUsername("ceshi");
        sqlSession.update("com.example.mapper.UserMapper.update", user);

        System.out.println("=============================");
	 	//第二次查询
        List<User> users2 = sqlSession.selectList("com.example.mapper.UserMapper.findAll");
        users2.forEach(user2 -> {
            System.out.println(user2);
        });
    }

2.查看sql日志

13:34:32,869 DEBUG findAll:159 - ==>  Preparing: select * from user 
13:34:32,895 DEBUG findAll:159 - ==> Parameters: 
13:34:32,921 DEBUG findAll:159 - <==      Total: 5
User{id=1, username='lucy'}
User{id=2, username='tom'}
User{id=3, username=''}
User{id=5, username='555'}
User{id=6, username='666'}
13:34:32,924 DEBUG update:159 - ==>  Preparing: update user set username=? where id=? 
13:34:32,925 DEBUG update:159 - ==> Parameters: ceshi(String), 3(Integer)
13:34:32,934 DEBUG update:159 - <==    Updates: 1
=============================
13:34:32,934 DEBUG findAll:159 - ==>  Preparing: select * from user 
13:34:32,934 DEBUG findAll:159 - ==> Parameters: 
13:34:32,936 DEBUG findAll:159 - <==      Total: 5
User{id=1, username='lucy'}
User{id=2, username='tom'}
User{id=3, username='ceshi'}
User{id=5, username='555'}
User{id=6, username='666'}
结论

1.第一次查询所有用户的信息,先去缓存中查找,如果缓存中没有,查询数据库,将查询的结果放在一级缓存中。 2.如果没有进行增删改操作,第二次查询时,会直接查询一级缓存,得到结果,不去查询数据库。 3.如果有进行增删改操作,会清空一级缓存,避免脏读。

一级缓存源码解析
  1. 查看org.apache.ibatis.session.SqlSession中的查询方法,以selectList为例
  2. 查看selectList的实现类,默认实现类为DefaultSqlSession
  3. 依次跟踪
  4. 查看Executor实现类 BaseExecutor中的query方法
  5. query方法中,会根据当前MappedStatement、参数、分页、结果集去创建一个缓存key,然后继续查看重载方法
  6. 查看查询方法
  7. 查看查询数据库方法

####### 一级缓存存储结构

二级缓存

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去 缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也 就 是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相 同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中

如何开启二级缓存

  1. mybatis核心配置文件 默认是开启的 可以不配置
    <!--开启全局的二级缓存配置-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
  1. mapper.xml
    1. 针对整个mapper 添加 <cache></cache>标签开启缓存
      	<cache>标签属性
      	eviction:缓存的回收策略
      		LRU - 最近最少使用,移除最长时间不被使用的对象
      		FIFO - 先进先出,按对象进入缓存的顺序来移除它们
      		SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
      		WEAK - 弱引用,更积极地移除基于垃圾收集器和弱引用规则的对象
      	默认的是LRU 
      

flushInterval:缓存刷新间隔 缓存多长时间清空一次,默认不清空,设置一个毫秒值 readOnly:是否只读 true:只读:mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。   mybatis为了加快获取数据,直接就会将数据在缓存中的引用交给用户 。不安全,速度快 false:读写(默认):mybatis觉得获取的数据可能会被修改 mybatis会利用序列化&反序列化的技术克隆一份新的数据给你。安全,速度相对慢 size:缓存存放多少个元素 type:指定自定义缓存的全类名(实现Cache接口即可) 默认实现类PerpetualCache ![](http://koujiaqi.cn/upload/20210329_10281045.png) 2. 针对单个标签 在变迁中添加属性useCache="true" ```开启缓存 3. 注意事项 开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操 作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取 这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口

测试一:

测试代码:
@Test
    public  void test5() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();

        User user1 = sqlSession1.selectOne("com.example.mapper.UserMapper.findById", 1);
        System.out.println(user1);

        sqlSession1.commit();//必须要commit

        User user2 = sqlSession2.selectOne("com.example.mapper.UserMapper.findById", 1);
        System.out.println(user2);
    }
查看sql日志
//第一次查询  先查询二级缓存, 缓存中不存在,查询数据库
10:03:22,572 DEBUG UserMapper:62 - Cache Hit Ratio [com.example.mapper.UserMapper]: 0.0
10:03:22,577 DEBUG JdbcTransaction:137 - Opening JDBC Connection
10:03:22,711 DEBUG PooledDataSource:406 - Created connection 2021707251.
10:03:22,711 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7880cdf3]
10:03:22,713 DEBUG findById:159 - ==>  Preparing: select * from user where id = ? 
10:03:22,735 DEBUG findById:159 - ==> Parameters: 1(Integer)
10:03:22,760 DEBUG findById:159 - <==      Total: 1
//查询结果:
User{id=1, username='lucy'}
//第二次查询  0.5表示   两次查询,在缓存中查询到一次  
10:03:22,769 DEBUG UserMapper:62 - Cache Hit Ratio [com.example.mapper.UserMapper]: 0.5
User{id=1, username='lucy'}

测试二:

测试代码:
    @Test
    public  void test5() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();

        User user1 = sqlSession1.selectOne("com.example.mapper.UserMapper.findById", 1);
        System.out.println(user1);

        sqlSession1.commit();

        User user = new User();
        user.setId(1);
        user.setUsername("jack");
        // 增删改会清空二级缓存
        sqlSession1.update("com.example.mapper.UserMapper.update",user);
        sqlSession1.commit();

        User user2 = sqlSession2.selectOne("com.example.mapper.UserMapper.findById", 1);
        System.out.println(user2);
    }
查看sql日志
10:15:21,727 DEBUG PooledDataSource:406 - Created connection 2021707251.
10:15:21,727 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7880cdf3]
10:15:21,729 DEBUG findById:159 - ==>  Preparing: select * from user where id = ? 
10:15:21,754 DEBUG findById:159 - ==> Parameters: 1(Integer)
10:15:21,775 DEBUG findById:159 - <==      Total: 1
User{id=1, username='lucy'}
10:15:21,781 DEBUG update:159 - ==>  Preparing: update user set username=? where id=? 
10:15:21,782 DEBUG update:159 - ==> Parameters: jack(String), 1(Integer)
10:15:21,784 DEBUG update:159 - <==    Updates: 1
10:15:21,784 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7880cdf3]
10:15:21,796 DEBUG UserMapper:62 - Cache Hit Ratio [com.example.mapper.UserMapper]: 0.0
10:15:21,796 DEBUG JdbcTransaction:137 - Opening JDBC Connection
10:15:21,800 DEBUG PooledDataSource:406 - Created connection 1847008471.
10:15:21,800 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6e171cd7]
10:15:21,800 DEBUG findById:159 - ==>  Preparing: select * from user where id = ? 
10:15:21,801 DEBUG findById:159 - ==> Parameters: 1(Integer)
10:15:21,802 DEBUG findById:159 - <==      Total: 1
User{id=1, username='jack'}

二级缓存源码解析

  1. 查看org.apache.ibatis.session.SqlSession中的查询方法,以selectList为例
  2. 查看Executor实现类 CacheExecutor中的query方法 CacheExecutor是BaseExecutor的装饰类
  3. query

注意事项

1.sqlSession 调用close或者commit后,不会清空二级缓存。 2.二级缓存只有在commit之后才会生效 3.如果使用默认的缓存,实体需要实现序列化接口(Serializable) 4.二级缓存在分布式下会存在脏读问题