【Mybatis】14 缓存
1、什么是缓存?
- 缓存是指把经常需要读写的数据,保存到一个高速的缓冲区中,这个行为叫缓存
- 也可以是指被保存在高速缓冲区的数据,也叫缓存
2、Mybatis缓存
Mybatis中分为一级缓存和二级缓存
- 一级缓存,数据缓存在这个SqlSession的作用范围内
- 二级缓存,数据缓存在这个SqlSesssionFactory的作用范围内
一级缓存:
一级缓存是默认开启的,那么如何证实是开启的呢?
同一个SQL语句只会执行一次,并留下缓存,
如果在这个SqlSession存在的期间,再次调用,那么Mybatis将不会执行SQL
而是直接调用缓存执行
案例:
映射接口
User getUserById(Integer id);
映射器
<select id="getUserById" resultType="user" parameterType="int">
SELECT *
FROM t_user
WHERE id = #{id}
</select>
测试类
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.getUserById(1);
System.out.println(user1); User user2 = userMapper.getUserById(1);
System.out.println(user2); User user3 = userMapper.getUserById(1);
System.out.println(user3); User user4 = userMapper.getUserById(1);
System.out.println(user4); sqlSession.close();
}
测试结果:
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 336371513.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
User(id=1, last_name=阿伟, gender=0)
User(id=1, last_name=阿伟, gender=0)
User(id=1, last_name=阿伟, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@140c9f39]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 336371513 to pool. Process finished with exit code 0
这里可以看到SQL语句只执行了一次
4次查询只执行了一次,这证明了缓存的存在
也就是说,实际上缓存存放的数据是首次查询出来的一个结果
如果我们反复调用相同的结果,Mybatis就会从缓存中返回数据给我们
但是在查询不同情况下的值的时候,Mybatis还是无法调用缓存来完成
例如我们这样查询不同的数据出来:
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.getUserById(1);
System.out.println(user1); User user2 = userMapper.getUserById(2);
System.out.println(user2); User user3 = userMapper.getUserById(3);
System.out.println(user3); User user4 = userMapper.getUserById(4);
System.out.println(user4); sqlSession.close();
}
结果就是不会触发缓存,因为每次查询的都不一样
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 336371513.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 2(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=2, last_name=阿伟, gender=1)
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 3(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=3, last_name=杰哥, gender=0)
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 4(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=4, last_name=阿强, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@140c9f39]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 336371513 to pool. Process finished with exit code 0
原理示意:
一级缓存失败的四种情况:
- 不在同一个SqlSession对象中【同一个SQL语句】
- 执行的语句的参数不一样,缓存中也不存在数据【就是上面的演示】
- 执行增、删、改、会清除缓存
- 自行手动清除
手动清除是指SqlSession调用清除缓存方法
sqlSession.clearCache();
为什么增、删、改、也会清除缓存?
是因为底层在SQL执行完默认就调用了这个方法清除了
二级缓存:
首先,二级缓存默认是不开启的,我们需要在Mybatis的核心配置文件中
配置关于二级缓存的SETTINGS选项,和在映射器的配置文件中加入cache标签
并且,需要被二级缓存的对象,必须要实现序列化接口
示意图:
开启二级缓存的配置操作:
1、核心配置中添加二级缓存配置
2、映射器加入cache标签
3、被缓存的对象所属类必须实现序列化接口
二级缓存开启配置
<setting name="cacheEnabled" value="true"/>
映射器配置cache标签
<cache/>
测试类
public void cacheTest(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userById = mapper.getUserById(1);
System.out.println(userById); sqlSession.close();
} @Test
public void sync(){
cacheTest();
cacheTest();
}
如果不实现序列化接口,二级缓存在调用时,就会出现未序列化异常
[cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.0
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1025309396.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d1cfad4]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1025309396 to pool. org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: cn.dai.pojo.User at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:94)
at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:55)
at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:49)
at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:43)
at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:116)
at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:99)
at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:44)
at org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor.java:61)
at org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession.java:263)
at BuildTest.cacheTest(BuildTest.java:59)
at BuildTest.sync(BuildTest.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.io.NotSerializableException: cn.dai.pojo.User
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at java.util.ArrayList.writeObject(ArrayList.java:766)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:90)
... 35 more Process finished with exit code -1
所以需要我们自己来把实体类序列化
再次测试:
[cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.0
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1025309396.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3d1cfad4]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1025309396 to pool.
[cn.dai.mapper.UserMapper]-Cache Hit Ratio [cn.dai.mapper.UserMapper]: 0.5
User(id=1, last_name=阿伟, gender=0) Process finished with exit code 0
可以看到二次调用时不再调用SQL查询,而是使用了二级缓存保留的数据返回结果
一些说明:
useCache属性,这个属性是放在SQL查询标签中的<SELECT>
默认TRUE(就是不写也表示开启的),表示使用二级缓存,前提是二级缓存是开启的
在上面的全局测试中已经演示了结果
如果更改为False就是取消这个SQL的二级缓存
测试结果就是第二次查询就需要再次调用SQL了
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1504642150.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@59af0466]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1504642150 to pool.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Checked out connection 1504642150 from pool.
[cn.dai.mapper.UserMapper.getUserById]-==> Preparing: SELECT * FROM t_user WHERE id = ?
[cn.dai.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.dai.mapper.UserMapper.getUserById]-<== Total: 1
User(id=1, last_name=阿伟, gender=0)
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@59af0466]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1504642150 to pool. Process finished with exit code 0
flushCache属性,这个属性是放在增、删、改、SQL语句中,
表示自动清除缓存,默认值TRUE,另外不要手贱改FALSE
如果不清除缓存,二次调用就从缓存的数据进行返回
为什么这么说?
- 先查询一个结果
【主键:01,名字:阿伟,性别:男】
- 不清除缓存进行修改记录,变更为
【主键:01,名字:杰哥,性别:男】
- 当二次查询时,Mybatis不会再调用SQL重新查询,
直接跑到缓存中返回数据,这个查询返回的结果就是
【主键:01,名字:阿伟,性别:男】
但实际上数据库已经更改,这样查询返回的结果是不对的
所以不要修改flushCache属性为False!!!
3、Cache标签:
当你在映射器中标注了此标签,Mybatis会默认开启这些功能:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。【就是能放多少个缓存】
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
1、最近最少使用算法
【Least Recently Used 】LRU算法来清除不需要的缓存。
移除符合这个描述的对象,优化内存空间
除此之外还有其他算法,这个属性值由eviction配置
2、先进先出算法
FIFO,First In First Out 先进先出
按对象进入缓存的顺序来移除
3、软引用算法
SOFT,移除基于GC回收状态和软引用规则的对象
4、弱引用算法
WEAK,弱引用,更积极的移除基于GC回收状态和弱引用规则的对象
当然,默认使用的是LRU最少使用原则清除
LRU
FIFO
SOFT
WEAK
二、可读可写的说明:
readOnly="true"
可读是共享对象的,所有的SqlSession如果需要调用这个二级缓存
指针就会直接引用缓存返回对象
可读是非共享的,所有SqlSession如果调用二级缓存,
那么Mybatis会分别new一个对象,并把缓存对象的属性值赋值给这些new出来的对象
指针则引用这些对象进写入修改
三、自定义二级缓存
可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
我们可以查看Mybatis的缓存实现类是怎么写的
package org.apache.ibatis.cache.impl; import java.util.HashMap;
import java.util.Map;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException; public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap(); public PerpetualCache(String id) {
this.id = id;
} public String getId() {
return this.id;
} public int getSize() {
return this.cache.size();
} public void putObject(Object key, Object value) {
this.cache.put(key, value);
} public Object getObject(Object key) {
return this.cache.get(key);
} public Object removeObject(Object key) {
return this.cache.remove(key);
} public void clear() {
this.cache.clear();
} public boolean equals(Object o) {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else if (this == o) {
return true;
} else if (!(o instanceof Cache)) {
return false;
} else {
Cache otherCache = (Cache)o;
return this.getId().equals(otherCache.getId());
}
} public int hashCode() {
if (this.getId() == null) {
throw new CacheException("Cache instances require an ID.");
} else {
return this.getId().hashCode();
}
}
}
四、缓存的执行顺序
1、当我们执行一个查询语句的时候,mybatis会先去二级缓存中查询数据,如果二级缓存中没有,就到一级缓存中查找
2、如果一级缓存也没有,调用SQL执行
3、执行返回,并且结果保存进一级缓存
4、SqlSession关闭,一级缓存保存到二级缓存中
【Mybatis】14 缓存的更多相关文章
- 八 mybatis查询缓存(一级缓存,二级缓存)和ehcache整合
1 查询缓存 1.1 什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存.
- MyBatis一级缓存引起的无穷递归
MyBatis一级缓存引起的无穷递归 引言: 最近在项目中参与了一个领取优惠劵的活动,当多个用户领取同一张优惠劵的时候,使用了数据库锁控制并发,起初的设想是:如果多个人同时领一张劵,第一个到达的人领取 ...
- 【MyBatis源码解析】MyBatis一二级缓存
MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...
- mybatis学习--缓存(一级和二级缓存)
声明:学习摘要! MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级) ...
- 三)mybatis 二级缓存,整合ehcache
mybatis-config.xml <setting name="cacheEnabled" value="true" /> PersonMapp ...
- MyBatis 示例-缓存
MyBatis 提供两种类型的缓存,一种是一级缓存,另一种是二级缓存,本章通过例子的形式描述 MyBatis 缓存的使用. 测试类:com.yjw.demo.CacheTest 一级缓存 MyBati ...
- MyBatis框架——缓存机制
使⽤缓存机制的作⽤也是减少 Java 应⽤程序与数据库的交互次数,从⽽提升程序的运⾏效率. ⽐如第 ⼀次查询出某个对象之后,MyBatis 会⾃动将其存⼊缓存,当下⼀次查询同⼀个对象时,就可以直接从 ...
- [.net 面向对象程序设计进阶] (14) 缓存(Cache) (一) 认识缓存技术
[.net 面向对象程序设计进阶] (14) 缓存(Cache)(一) 认识缓存技术 本节导读: 缓存(Cache)是一种用空间换时间的技术,在.NET程序设计中合理利用,可以极大的提高程序的运行效率 ...
- 通过源码分析MyBatis的缓存
前方高能! 本文内容有点多,通过实际测试例子+源码分析的方式解剖MyBatis缓存的概念,对这方面有兴趣的小伙伴请继续看下去~ MyBatis缓存介绍 首先看一段wiki上关于MyBatis缓存的介绍 ...
- MyBatis 一级缓存与二级缓存
MyBatis一级缓存 MyBatis一级缓存默认开启,一级缓存为Session级别的缓存,在执行以下操作时一级缓存会清空 1.执行session.clearCache(); 2.执行CUD操作 3. ...
随机推荐
- MySQL创建表的时候建立联合索引的方法
1.MySQL创建表建立联合索引的步骤 在MySQL中,联合索引(也称为复合索引或多列索引)是基于表中的多个列创建的索引.这种索引可以提高多列查询的性能,特别是当查询条件涉及这些列时.下面是一个详细的 ...
- LeetCode 37. Sudoku Solver II 解数独 (C++/Java)
题目: Write a program to solve a Sudoku puzzle by filling the empty cells. A sudoku solution must sati ...
- for while 要求选慢速的,但是for不卡,while 跟 递归 这两个容易卡
for比while慢,但是for不卡,while跟递归容易卡 int index = 0; bool jump=flase: for( index;index==0;;)/*这个空分号算一个语句*/ ...
- Mysql int类型字段插入表达式,值为0或1
CREATE TABLE `t_user` ( `uId` INT(11) DEFAULT NULL, `uName` VARCHAR(20) DEFAULT NULL, `uPwd` VARCHAR ...
- 搭建springboot redis项目
1.创建项目 如果出现init失败(需要等待网络可以正常连接) 或者运行主类的时候报错(错误: 找不到或无法加载主类),需要重新导入maven项目再重新编译试试. 2.引入pom jar <?x ...
- RestApi请求地址支持多路径访问
RestApi请求地址支持多路径访问 @RestController@RequestMapping("/test") //单路径@RequestMapping(path = {&q ...
- 测试网络的小工具WinMTR
ping网络的小工具 搜集了两个版本中文版和英文版 中文版---- WinMTR中文版.rarhttps://www.aliyundrive.com/s/bZqmokL5dTt提取码: k6v7 英文 ...
- 工具类——EventManager
EventManager using UnityEngine; using System.Collections; using System.Collections.Generic; using Un ...
- 用cvCvtColor转化RGB彩色图像为灰度图像时发生的小失误
版本信息 MAC版本:10.10.5 Xcode版本:7.2 openCV版本:2.4.13 在运行程序的时候发现cvCvtColor的地方程序报错 error: (-215) src.depth() ...
- 服务器上安装centos7系统遇到的坑
centos7的安装报错"no controller found" 出现no controller found解决方案1.等待命令行出现 2.输入ls /dev/sd* 找到自 ...