Version: Next

1. 缓存简介

查询:连接数据库,消耗资源

一次查询的结果,暂存在一个地方:缓存 (可接Redis)

再次查询相同数据的时候,直接走缓存,就不用走数据库了

  1. 什么是缓存[cache]
    • 存在内存中的临时数据
    • 将用户经常查询的数据放在缓存中,用户去查询就不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决高并发系统的性能问题
  2. 为什么实用缓存
    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存
    • 经常查询并且不经常改变的数据

2. Mybatis缓存

  • Mybatis包含一个非常强大的查询缓存特性,它可以方便地定制和配置缓存。缓存可以极大地提高查询效率
  • Mybatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只开启一级缓存(SqlSession级别地缓存,也成为本地缓存)
    • 二级缓存需要手动开启和配置,它是基于namespace级别地缓存
    • 为可提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

2.1 一级缓存

  • 一级缓存也叫本地缓存(SqlSession级别)

    • 与数据库同一次会话期间查询到地数据会放在本地缓存中
    • 以后如果需要获取相同地数据,直接从缓存中拿,没必要再去查询数据库
  • 新建项目Mybatis09

  • 新建POJO——User

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    private int id;
    private String name;
    private String password;
    }
  • 接口

    public interface UserMapper {
    User queryUserById(@Param("id") int id);
    }
  • UserMapper.xml

    <mapper namespace="com.bsx.dao.UserMapper">
    <select id="queryUserById" resultType="com.bsx.pojo.User" parameterType="_int">
    SELECT * from user where id = #{id}
    </select>
    </mapper>
  • 测试

    @Test
    public void testEnv() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.queryUserById(1);
    System.out.println(user);
    user = mapper.queryUserById(1);
    System.out.println(user);
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    sqlSession.close();
    }
    }

    查询id=1两次,根据日志可见,只去了一次数据库

    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1848415041.
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6e2c9341]
    [com.bsx.dao.UserMapper.queryUserById]-==> Preparing: SELECT * from user where id = ?
    [com.bsx.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
    [com.bsx.dao.UserMapper.queryUserById]-<== Total: 1
    User(id=1, name=改名的, password=123456)
    User(id=1, name=改名的, password=123456)
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6e2c9341]
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6e2c9341]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1848415041 to pool.
  • 映射语句文件中的所有select语句的结果会被缓存
  • 映射语句文件中的所有insert、update、delete语句会刷新缓存(使缓存失效)
  • 缓存会使用最少使用算法(LRU Least Recently Used)算法来清楚不需要的缓存
  • 缓存不会定时刷新(没有刷新间隔),不定时刷新
  • 手动清理缓存sqlSession.clearCache()

2.2 二级缓存

  • 二级缓存也叫全局缓存,因为一级缓存的作用于太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个命名空间,对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的使,会话关闭了,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

要启用全局的二级缓存,只需要在mapper.xml文件中添加一行

<cache/>

可以定制一些缓存策略

<cahce
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
/>

FIFO缓存,每隔60秒刷新,最多可以存储结果对象或列表的512个引用,而且返回的对象被认为是只读的,因此对他们进行修改可能会在不同线程中的调用者之间产生冲突

步骤:

  • 开启全局缓存 根据在settings章节的内容,在mybatis-config.xml中设置开启全局缓存

    <settings>
    <setting name="logImpl" value="LOG4J"/>
    <setting name="cacheEnabled" value="true"/>
    </settings>
  • 在mapper.xml中添加<cache/>开启二级缓存

    <mapper namespace="com.bsx.dao.UserMapper">
    <!-- 开启二级缓存-->
    <cache/>
    <select id="queryUserById" resultType="com.bsx.pojo.User" parameterType="_int">
    SELECT * from user where id = #{id}
    </select>
    </mapper>

    也可以直接在CRUD标签上用useCache指定是否使用缓存

  • POJO必须实现Serializable接口

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User implements Serializable{
    private int id;
    private String name;
    private String password;
    }
  • 测试 创建两个sqlSession,获取两个Mapper接口,两个Mapper查询同一条数据库记录

    @Test
    public void testSecondaryCache() {
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    try {
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user1 = mapper1.queryUserById(1);
    sqlSession1.close();
    User user2 = mapper2.queryUserById(1);
    sqlSession2.close();
    System.out.println(user1);
    System.out.println(user2);
    System.out.println("user1 == user2 ? -> " + user1.equals(user2));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    结果整明只去了一次数据库, 并且日志直接显示了从缓存拿的

    rg.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 297927961.
    [com.bsx.dao.UserMapper.queryUserById]-==> Preparing: SELECT * from user where id = ?
    [com.bsx.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
    [com.bsx.dao.UserMapper.queryUserById]-<== Total: 1
    [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@11c20519]
    [org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 297927961 to pool.
    [com.bsx.dao.UserMapper]-Cache Hit Ratio [com.bsx.dao.UserMapper]: 0.5
    User(id=1, name=改名的, password=123456)
    User(id=1, name=改名的, password=123456)
    user1 == user2 ? -> true

3. Mybatis缓存原理