什么是缓存?
-
存在内存中的临时数据
-
将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
-
经常查询且不经常改变的数据适合使用缓存
为什么使用缓存?
缓存(即cache)的作用是为了减去数据库的压力,提高数据库的性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存中获取,不再向数据库执行select语句,减少对数据库的查询次数,提高了数据库的性能。缓存是使用Map集合存储数据。
可用的清除策略有:
-
LRU
-- 最近最少使用:移除最长时间不被使用的对象。 -
FIFO
-- 先进先出:按对象进入缓存的顺序来移除它们。
Mybatis缓存
MyBatis有一级缓存和二级缓存之分。
一级缓存的作用域是同一个SqlSession,在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库查询的数据写到缓存(内存),第二次会从缓存中获取数据而不进行数据库查询,大大提高了查询效率。当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。MyBtais默认启动以及缓存。
二级缓存是多个SqlSession共享的 ,其作用域是mapper的同一个namespace ,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递的参数也相同时,第一次执行完毕会将数据库中查询到的数据写到缓存(内存),第二次会直接从缓存中获取,从而提高了查询效率。MyBatis默认不开启二级缓存,需要在MyBtais全局配置文件中进行setting配置开启二级缓存。
为了提高扩展性,mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
一级缓存
MyBatis默认开启一级缓存,一级缓存只是相对于同一个SqlSession而言。所以在参数个sql完全一致的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次sql,使用SqlSession第一次查询后,MyBatis会将其放在缓存中,之后再查询时若没有缓存失效或超时,SqlSession都会取出当前缓存的数据,不会再发送sql到数据库。
测试步骤:
1.开启日志!
2.测试在一个Session中查询两次相同的记录
3.查看日志输出

缓存失效的情况:
1.查询不同的实体
java
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();
}
观察结果:发现发送了两条SQL语句!
结论:每个sqlSession中的缓存相互独立
2.增删改操作。可能会改变原来的数据,所以必定刷新缓存
增加方法
java
//修改用户
int updateUser(Map map);
编写SQL
java
<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
</update>
测试
java
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:查询在中间执行了增删改操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响
3.查询不同的mapper
java
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
观察结果:发现发送了两条SQL语句!很正常的理解
结论:当前缓存中,不存在这个数据
4.手动清理缓存
java
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.clearCache();//手动清理缓存
System.out.println("=========");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
小结:
一级缓存默认开启,作用域为同一个Sqlsession,只在一次SQLsession中有效,也就是拿到连接和关闭连接之间
一级缓存就是一个map
二级缓存
二级缓存的作用域是SqlSessionFactory级别,整个应用程序只有一个。二级缓存区域是根据mapper的namespace划分的,相同的namespace的mapper查询的数据缓存在同一个区域,如果使用mapper代理方法,每一个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper进行划分的。
每次查询都会先从缓存区域查找,如果找不到则从数据库进行查询,并将查询到的数据写入缓存。MyBtais内部缓存使用HashMap,key为hashCode+sqlid+sql语句,value为从查询出来映射生成的java对象。SqlSession执行任何一个update(修改)、delete(删除)、insert(新增)操作commit提交后都会清空缓存区域,防止脏读。
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存的数据会被保存在二级缓存中
-
新的会话查询信息,就可以从二级缓存中获取内容
-
不同的mapper查出的数据会放在自己对应的缓存中
步骤:
1.开启全局缓存
XML
<!-- 开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
2.在使用二级缓存的Mapper中开启
XML
<cache/>
也可以自定义一些参数
XML
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
//创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的
3.测试:
1.问题:我们要将实体类序列化!否则就会报错
org.apache.ibatis.cache.CacheException:
解决方式:在实体类实现接口: Serializable
java
public class User implements Serializable
小结:
-
只要开启了二级缓存,在同一个Mapper下有效
-
所有的数据都会先放在一级缓存中
-
只有当会话提交,或关闭的时候·,才会提交到二级缓存中
Mybatis缓存原理

扩充:在正式业务中,如何保证多个数据库数据一致?使用主从复制
缓存只是读,写还是数据库,为读写分离,提高数据库性能
第三方缓存实现--EhCache
Ehcache是一种广泛使用的java分布式缓存,用于通用缓存;
要在应用程序中使用Ehcache,需要引入依赖的jar包
XML
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
在mapper.xml中使用对应的缓存即可
XML
<mapper namespace = "org.acme.FooMapper" >
<cache type = "org.mybatis.caches.ehcache.EhcacheCache" />
</mapper>
编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。
XML
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home -- 用户主目录
user.dir -- 用户当前工作目录
java.io.tmpdir -- 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
</ehcache>
希望对大家有所帮助!