视频原作者CSDN: https://blog.csdn.net/qq_33369905
这篇是自己整理了一下,以便自己复习。
摘自 狂神说MyBatis07:缓存
1、什么是缓存 [ Cache ]?
2、为什么使用缓存?
3、什么样的数据能使用缓存?
合理利用缓存可提高系统运行效率!
MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
此外提供了缓存接口Cache,可自定义二级缓存。
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '张三', '1123');
INSERT INTO `user` VALUES ('2', '李四', 'ksqo');
INSERT INTO `user` VALUES ('3', '王五', 'wwww');
User类
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
//推荐Lombok
//构造,有参,无参
//set/get
//toString()
}
添加log4j日志以便查看执行的SQL。
此不赘述,可查看:MyBatis笔记(三)——ResultMap结果集映射,日志,分页的多种实现方式
UserMapper接口类:
public interface UserMapper {
//查询所有User
List<User> selectUser();
//按照id查询User
User selectUserById(int id);
}
UserMapper.xml
<mapper namespace="com.piao.dao.UserMapper">
<select id="selectUser" resultType="com.piao.User">
select * from user;
select>
<select id="selectUserById" parameterType="int" resultType="com.piao.User">
select * from user where id = #{id};
select>
mapper>
测试:
@Test
public void testSelectUserById() {
System.out.println("测试按照id查询用户selectUserById()");
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
List<User> users2 = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
//比较两次 查询所有 的结果集
System.out.println(users == users2);
User user = mapper.selectUserById(2);
System.out.println(user);
User user2 = mapper.selectUserById(2);
System.out.println(user);
//比较两次 按照id查询 的结果集
System.out.println(user == user2);
}
输出:可看到第二次查询所有用户和第二次查询id为2的用户没有与数据库交互,没有执行SQL。
这便是MyBatis默认开启的一级缓存的效果,对于单条查询SQL语句的结果,sqlsession会保存该数据。
DEBUG [main] - ==> Preparing: select * from user;
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 1, 张三, 1123
TRACE [main] - <== Row: 2, 李四, ksqo
TRACE [main] - <== Row: 3, 王五, wwww
DEBUG [main] - <== Total: 3
User{id=1, name='张三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
User{id=1, name='张三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
true
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
User{id=2, name='李四', pwd='ksqo'}
true
MyBatis一级缓存失效结果就是MyBatis会与数据库交互,刷新一级缓存。
每个sqlSession中保存的数据是不共享的,相互独立。因此使用不同的sqlSession对象去获取mapper来查询一摸一样的数据,仍然会与数据库交互。
刚刚上述的运行结果正验证了这一点,一个查询所有,一个按照id=2查询,分别执行了SQL,后面一级缓存便更新成了按照id=2查询的结果。
//测试按照id查询用户selectUserById()
@Test
public void testSelectUserById() {
System.out.println("测试按照id查询用户selectUserById()");
User user = mapper.selectUserById(2);
System.out.println(user);
User user3 = new User(5, "赵六", "2333");
mapper.insertUser(user3);
session.commit();//提交事务 原生jdbc则需要开启事务执行sql后提交事务
User user2 = mapper.selectUserById(2);
System.out.println(user);
//比较两次 按照id查询 的结果集
System.out.println(user == user2);
}
输出,重新查询了数据库
此时缓存已失效,因为增删改可能对数据产生影响,造成数据库与缓存不一致的可能。
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - ==> Preparing: insert into user(id,name,pwd) values (?,?,?);
DEBUG [main] - ==> Parameters: 5(Integer), 赵六(String), 2333(String)
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
false
session.clearCache();//手动清除缓存
图源百度
由上述可看到MyBatis中一级缓存的局限性,条件太苛刻(很容易失效),性能提升十分有限。
MyBatis中的二级缓存(也称全局缓存) 基于namespace级别的缓存,一个名称空间,对应一个二级缓存(全局缓存);
要启用全局的二级缓存,只需要在你的 SQL 映射文件(***Mapper.xml)中添加:
<cache/>
然后在MyBatis-config.xml中的settings添加全局开启缓存
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
官方文档有对应的说明(其实默认就是开启的):
注意这个设置是: 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
也就是说,没有加cache元素的xml映射文件依旧是默认的一级缓存!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-82uQBNa1-1589514753471)(D:\博客markdown文件\CSDN\应用框架\MyBatis\MyBatis笔记(五)]——缓存.assets\image-20200515104647231.png)
这个简单语句的效果如下:
工作机制:
这些属性可以通过 cache 元素的属性来修改。比如:
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。默认的清除策略是 LRU。
flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
在MyBatis-config.xml中的settings添加全局开启缓存(其实默认就是开启的):
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
UserMapper接口:
//操作数据库的接口
public interface UserMapper {
//查询所有User
List<User> selectUser();
//按照id查询User
User selectUserById(int id);
}
UserMapeer.xml
<mapper namespace="com.piao.dao.UserMapper">
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<select id="selectUserById" parameterType="int" resultType="com.piao.User">
select * from user where id = #{id};
select>
mapper>
测试
//测试二级缓存
@Test
public void testSelectUserById() {
SqlSession session = MybatisUtils.getSession();
User5Mapper mapper = session.getMapper(User5Mapper.class);
SqlSession session2 = MybatisUtils.getSession();
User5Mapper mapper2 = session2.getMapper(User5Mapper.class);
SqlSession session3 = MybatisUtils.getSession();
User5Mapper mapper3 = session3.getMapper(User5Mapper.class);
User user = mapper.selectUserById(2);
System.out.println(user);
session.close();//关闭第一个sqlsession 会将一级缓存提交到二级缓存
User user2 = mapper2.selectUserById(2);
System.out.println(user);
//比较两次 按照id查询 的结果集
System.out.println(user == user2);
session2.close();
User user3 = mapper3.selectUserById(2);
System.out.println(user);
//比较两次 按照id查询 的结果集
System.out.println(user == user3);
session3.close();
}
测试输出:可看到第一个SsqlSession关闭后将一级缓存提交到二级缓存,后面两个sqlsession从二级缓存中拿到id=2的数据,三次查询出的结果集User都是同一个。
同时可看到日式的输出有Cache Hit Ratio 缓存命中率:
命中率=从缓存中读取数据的次数/所有访问数据次数(磁盘读取次数+缓存读取次数)
第一次是0,因为二级缓存里没有数据。第二次为0.5,因为第二次是从缓存拿到的数据,第三次是0.66666,说明也是从缓存中拿到的数据。就是只有第一次是从MySQL读取(磁盘读取),整个过程只执行一次SQL
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.0
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.5
User{id=2, name='李四', pwd='ksqo'}
true
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.6666666666666666
User{id=2, name='李四', pwd='ksqo'}
true
结果也验证说明二级缓存是跨sqlsession的,作用域是namespace级别!
图源百度
先走一级缓存,(如果开启二级缓存的话)再走二级缓存,(如果有第三方缓存)再走第三方缓存,没有就走数据库