为什么需要缓存
- 查询数据库需要消耗资源
- 1次查询的结果,放到可以取到的地方,也就是内存
- 我们再次查询,就不用走缓存,就直接走数据库了
缓存是什么
- 放到内存中的临时数据
- 在没有缓存的时候,随着用户的增加,肯定需要加服务器,但随着服务器增加会出现读写问题,也就是并发问题
- 第1次打开网页的时候需要加载很长时间,是需要进行数据库操作
- 读是走缓存的,写不走缓存,读写分离
- 读写分离体现在不同服务器有不同的职能,比如3台服务器写,1台负责读,因为写比较慢,需要分配多态服务器
- 还有1个主从复制的概念,又叫做哨兵,学redis数据库的时候可以体会到,写的操作完成之后,立刻进行刷新读的操作
缓存的作用是什么
- 减少和数据库交互次数,减少系统开销,提高系统效率
- 什么样的数据适合缓存,经常查询但不经常改变的数据,大部分网页数据都在进行查操作
- 什么样的数据不适合缓存,不经常查询且经常改变的数据,比如视频下面显示的当前正在观看的人数
Mybatis缓存
- Mybatis可以方便的配置和定制缓存
- Mybatis系统默认配置了二级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启,sqlSession开启和close之间有效,比如在sqlSession开启和关闭之间查询之间查询同一用户2次,但此时sql只走了1次,并且查询出来的2个对象是相等的
- 二级缓存需要手动开启配置,namespace级别的缓存,1个namespace对应1个Mapper接口,所以说在整个Mapper.xml文件下都有效
- 为了提高扩展性,Mybatis定义了缓存接口Cache,可以实现Cache接口来定义二级缓存
小结
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
一级缓存示例代码
下面是根据id查询用户的例子
public class UnitTest {
@Test
public void queryUserByIdTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
//查询相同用户
User user1 = mapper.queryUserById(1);
System.out.println(user1);
//第二次查询的时候sql从缓存里面拿
sqlSession.close();
}
}
再把修改操作加到两个查询中间
public class UnitTest {
@Test
public void queryUserByIdTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查询用户
User user = mapper.queryUserById(1);
System.out.println(user);
//修改用户
mapper.updateUser(new User(6,"Tom","Tom404@123.it"));
//查询相同用户
User user1 = mapper.queryUserById(1);
System.out.println(user1);
//第二次查询的时候sql从缓存里面拿
sqlSession.close();
}
}
测试需要,先开启日志,结果出现了3次Preparing,说明sql在第二次查询的时候并没有走缓存,因为增删改查语句会修改修改表数据,再次进行查询会出现不同的结果
[com.bkms.dao.UserMapper.queryUserById]-==> Preparing: select * from info where id = ?;
[com.bkms.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
[com.bkms.dao.UserMapper.queryUserById]-<== Total: 1
User(id=1, name=John, email=John123@163.com)
[com.bkms.dao.UserMapper.updateUser]-==> Preparing: update info set name=?,email=? where id=?
[com.bkms.dao.UserMapper.updateUser]-==> Parameters: Tom(String), Tom404@123.it(String), 6(Integer)
[com.bkms.dao.UserMapper.updateUser]-<== Updates: 1
[com.bkms.dao.UserMapper.queryUserById]-==> Preparing: select * from info where id = ?;
[com.bkms.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
[com.bkms.dao.UserMapper.queryUserById]-<== Total: 1
User(id=1, name=John, email=John123@163.com)
缓存失效的情况
- 查询不同的东西
- 存在增删改查操作
- 手动删除
sqlSession.clearCache();
- 查询不同的Mapper.xml
二级缓存示例代码
- 全局开启缓存,二级缓存会在1个Mapper.xml文件中全部生效
<settings>
<setting name="logImpl" value="LOG4J" />
<setting name="cacheEnabled" value="true" />
</settings>
- 在Mapper.xml中开启二级缓存
加cache标签,就开启了二级缓存
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkms.dao.UserMapper">
<!--开启二级缓存-->
<cache/>
<!--根据id查询用户-->
<select id="queryUserById" resultType="User">
select * from info where id = #{id};
</select>
<!--修改用户-->
<update id="updateUser" parameterType="user">
update info set name=#{name},email=#{email} where id=#{id}
</update>
</mapper>
下面这个例子,这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会出现线程安全问题
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 测试二级缓存
会话关闭时意味着一级缓存死亡,一级缓存的数据会被保存到二级缓存中,只有当会话提交或关闭时才会提交到二级缓存
@Test
public void queryUserByIdTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//查询用户
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.close();
//程序运行到此处一级缓存死亡
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
//查询相同用户
User user1 = mapper2.queryUserById(1);
System.out.println(user1);
System.out.println(user==user1);//true
sqlSession2.close();
}
- cache标签属性注意点
cache标签中的readOnly默认为false,而可读写的缓存会通过序列化返回缓存对象的拷贝,此时需要实体类(这里是User)实现Serializable接口或者配置readOnly=true,否则会报错