您当前的位置: 首页 >  缓存

庄小焱

暂无认证

  • 3浏览

    0关注

    805博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Mybatis——缓存原理和分析

庄小焱 发布时间:2021-04-01 10:10:38 ,浏览量:3

摘要

主要是分析的一下Mybatis的一级缓存和二级缓存的原理和源码的操作。每一次在和数据库进行会话的过程中,MyBatis 都会创建一个SqlSession对象。同一次会话期间,只要是查询过的数据,都会保存在当前SqlSession对象的一个Map中。所以在一次会话的过程中,如果我们对一条查询SQL 语句进行了多次执行,经过判断后和之前执行的查询语句相同,并且在缓存中存在该SQL 语句的执行结果,那么这次查询就不会再从数据库中获取数据,而是直接从缓存中获取数据。因为一级缓存是建立在SqlSession对象中的,所以也可以说一级缓存是一个SqlSession级别的缓存。在默认的情况下,一级缓存是开启的。MyBatis 提供了二级缓存机制,二级缓存是一个namespace级别的缓存,相对于一级缓存而言,二级缓存允许跨SqlSession工作,因此二级缓存的作用范围更大。每个SqlSession在执行查询操作的时候,都会将查询的结果放在当前会话的一级缓存中。如果当前会话关闭,一级缓存中的数据会被保存在二级缓存中。因此二级缓存是在一级缓存的基础上进行扩展的,不同的namespace查出的数据都会将数据存在自己对应的缓存中,这些缓存信息使用Map存储。

一级缓存测试

mapper 接口:

    Employee getEmpById(Integer id);

SQL 映射文件:

    
        SELECT  * FROM t_employee WHERE id = #{id}
    

测试代码:

    // 用于创建SqlSession 对象
    private SqlSession getSqlSession() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        return sqlSession;
    }

    @Test
    public void testCache() throws IOException {
        SqlSession sqlSession = getSqlSession();
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

        // 从数据库中获得id 为1 的员工信息
        Employee employee1 = employeeMapper.getEmpById(1);
        System.out.println(employee1);

        // 再一次从数据库中获得id 为1 的员工信息
        Employee employee2 = employeeMapper.getEmpById(1);
        System.out.println(employee2);

        sqlSession.close();
    }

上面的测试过程中一共对id为1 的员工信息进行了两次查询,并且在中间过程中没有进行其他的操作,下面我们通过SQL 日志来看看执行的过程。

虽然我们执行了两次查询,但是从执行结果来看,其实只发送了一条SQL 语句,在第二次获得员工信息的时候并不是从数据库中获取数据,而是直接在一级缓存中获得数据,这就是一级缓存起到的作用。

一级缓存失效

我们知道,一级缓存是一个SqlSession级别的缓存,所以一级缓存在工作的过程中会有一定的局限。一级缓存失效的情况:

  • 1.只对当前SqlSession对象生效,对于其他的SqlSession来说一级缓存是不起作用的。
  • 2.是同一个SqlSession对象,但是执行的是不同条件的SQL 查询语句。
  • 3.同一个SqlSession对象执行同一条件的查询语句,但中间执行了增删改的操作,之前一级缓存中的数据会失效。如果执行的是不同条件的查询语句,一级缓存仍然生效,并且会把新的查询数据也加入到一级缓存中。
  • 4.多次相同查询语句在执行期间通过sqlSession.clearCache();方法清除了缓存。
  • 5.SqlSession 对象调用了close()方法,当前会话结束,一级缓存不再生效。

对于上面的几种情况,一级缓存都是不起作用的。所以在处理业务的时候要根据需求,具体分析能不能使用一级缓存。

一级缓存的生命周期
  •  MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
  • SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;

二级缓存

MyBatis 提供了二级缓存机制,二级缓存是一个namespace级别的缓存,相对于一级缓存而言,二级缓存允许跨SqlSession工作,因此二级缓存的作用范围更大。每个SqlSession在执行查询操作的时候,都会将查询的结果放在当前会话的一级缓存中。如果当前会话关闭,一级缓存中的数据会被保存在二级缓存中。因此二级缓存是在一级缓存的基础上进行扩展的,不同的namespace查出的数据都会将数据存在自己对应的缓存中,这些缓存信息使用Map存储。需要注意的地方是:MyBatis 在开启二级缓存的情况下,如果发出了一条SQL 查询语句,会先向二级缓存中查询是否有对应的缓存数据,如果没有再接着查询一级缓存中的数据,如果一级缓存中也没有对应的缓存数据,才会向数据库发送SQL。下面附一张蹩脚的二级缓存运行机制图。

实现二级缓存的步骤
  • 1.在MyBatis 全局配置文件中开启二级缓存的配置,默认也是开启的,建议自己设置为开启的。
  • 2.在对应的SQL 映射文件中加入标签。
  • 3.将返回的JavaBean 数据类型的类实现Serializable(序列化)接口。
二级缓存测试

MyBatis 全局配置文件:

    
    

POJO 实现序列化接口:

package com.jas.mybatis.bean;

import java.io.Serializable;

public class Department implements Serializable{
    private Integer id;
    private String departmentName;

    //省略get、set 与toString 方法
}

mapper 接口:

    Department getDeptById(Integer id);

SQL 映射文件:

    
    
    
        SELECT id, dept_name departmentName FROM t_dept WHERE id = #{id}
    

测试代码:

    // 用于返回SqlSessionFactory  对象
    private SqlSessionFactory getSqlSession() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);

        return new SqlSessionFactoryBuilder().build(is);  
    }

    @Test
    public void secondLevelTest() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSession();

        //创建两个不同的会话
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        DepatmentMapper depatmentMapper1 = sqlSession1.getMapper(DepatmentMapper.class);
        DepatmentMapper depatmentMapper2 = sqlSession2.getMapper(DepatmentMapper.class);

        Department department1 = depatmentMapper1.getDeptById(1);
        System.out.println(department1);    
        // 关闭当前的会话1, 否则二级缓存不起作用
        sqlSession1.close();

        Department department2 = depatmentMapper2.getDeptById(1);
        System.out.println(department2);
        // 关闭会话2
        sqlSession2.close();

    }

当会话1 查询结束并关闭后,我们使用另一个会话执行与会话1 相同的SQL 查询语句,发现并没有发送SQL 语句,因为缓存中已经保存了要查询的数据,这次直接从缓存中获取数据,这就是MyBatis 提供的跨会话级别(SqlSession)的二级缓存机制。

二级缓存相关的设置

1.可以在MyBatis 全局配置文件中的setting标签中配置cacheEnabled属性,表示是否启用二级缓存机制,不过不影响一级缓存。

2.select查询标签中可以设置useCache属性,配置是否使用二级缓存,默认一级缓存是一直生效的。

3.sqlSession.clearCache();方法默认只清除一级缓存中的数据。

4.可以在每个增删改的标签中设置flushCache属性,设置为true后,如果执行了该增删改操作,则一级缓存与二级缓存中的数据都会清空。

二级缓存的缺点

MyBatis 提供的二级缓存机制好像并没有获得开发人员的青睐。原因是二级缓存是一个namespace级别的缓存,如果在不同的namespace下操作同一SQL 语句,可能导致缓存中的数据不正确。在进行多表联查的时候,也可能会导致二级缓存中的数据不正确。所以在实际的开发过程中要慎用二级缓存。(权限名+id 或实现的SQL语句的唯一操作,但是如果是的namespace级别的话会导致的是缓存数据查询不是我们想要的表中的数据。)

关注
打赏
1657692713
查看更多评论
立即登录/注册

微信扫码登录

0.0391s