Version: Next
1. 缓存简介
查询:连接数据库,消耗资源
一次查询的结果,暂存在一个地方:缓存 (可接Redis)
再次查询相同数据的时候,直接走缓存,就不用走数据库了
- 什么是缓存[cache]
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中,用户去查询就不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决高并发系统的性能问题
- 为什么实用缓存
- 减少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样的数据能使用缓存
- 经常查询并且不经常改变的数据
2. Mybatis缓存
- Mybatis包含一个非常强大的查询缓存特性,它可以方便地定制和配置缓存。缓存可以极大地提高查询效率
- Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只开启一级缓存(SqlSession级别地缓存,也成为本地缓存)
- 二级缓存需要手动开启和配置,它是基于namespace级别地缓存
- 为可提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
2.1 一级缓存
一级缓存也叫本地缓存(SqlSession级别)
- 与数据库同一次会话期间查询到地数据会放在本地缓存中
- 以后如果需要获取相同地数据,直接从缓存中拿,没必要再去查询数据库
新建项目Mybatis09
新建POJO——User
@Data@AllArgsConstructor@NoArgsConstructorpublic 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>测试
@Testpublic 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: 1User(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@NoArgsConstructorpublic class User implements Serializable{private int id;private String name;private String password;}
测试 创建两个sqlSession,获取两个Mapper接口,两个Mapper查询同一条数据库记录
@Testpublic 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.5User(id=1, name=改名的, password=123456)User(id=1, name=改名的, password=123456)user1 == user2 ? -> true