(七)Mybatis-缓存
作者:互联网
(七)Mybatis-缓存
一、简介
问题:查询=》连接数据库=》消耗资源!
解决方案:
- 一次查询的结果,给他暂存在一个可以直接取到的地方=》内存:缓存。
- 我们再次查询相同数据的时候,直接走缓存,就不去数据库查了。
- 什么是缓存【cache】
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据文件)查询,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
- 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
- 什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。
- 比如菜单数据
二、Mybatis缓存
- mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大地提升查询效率
- mybatis系统中默认定义了2级缓存:一级缓存和二级缓存。
- 默认情况下啊,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。namespace又和一个接口绑定,一个接口mapper对应一个表或一个业务,所以是一张表级别的缓存,整个mapper文件里的所有方法共享。
- 为了提供扩展性,mybatis定义了缓存接口cache,我们可以通过cache接口来定义二级缓存。
三、一级缓存
在一次sqlSession会话中有效,如下从开启到关闭sqlSession
@Test
public void testGetBlogList() {
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
List<Blog> blogList = mapper.getBlogList();
for (Blog blog : blogList) {
System.out.println(blog);
}
}
}
一级缓存默认开启,我们只需要测试即可
3.1 测试步骤
1 开启日志
在核心配置文件mybatis-config.xml
<settings>
<!-- <setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2 编写测试实体pojo类
package com.happy.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
3 编写mapper接口
package com.happy.dao;
import com.happy.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
//规范最好给参数取名字
User getUserById(@Param("id") int id);
}
4 编写mapper.xml文件
<?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.happy.dao.UserMapper">
<select id="getUserById" parameterType="int" resultType="user">
select *
from user
where id = #{id}
</select>
</mapper>
5 测试使用
测试步骤
- 开启日志
- 测试在一个session内查询两次相同记录
- 查看日志输出
package com.happy.dao;
import com.happy.pojo.User;
import com.happy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserMapperTest {
@Test
public void testGetUserList() {
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
@Test
public void testGetUserById() {
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println("============第1次查询=============");
User user1 = mapper.getUserById(3);
System.out.println(user1);
System.out.println("============第2次查询=============");
User user2 = mapper.getUserById(3);
System.out.println(user2);
}
}
}
缓存失效情况
-
查询不同的东西
-
增删该操作DML操作,可能会改变原来的数据,所以必定会刷新缓存。
即在两次查询之间,加入修改操作后(即使是修改其他行数据),缓存也会刷新。
-
查询不同的mapper.xml
-
手动清楚缓存。
6 小结:
- 一级缓存默认是开启的,只在一次sqlSession中有效
- 也就是拿到连接和关闭连接这个区间段。
- sqlSession的一级缓存就是一个map,下次来会去查这个map。
四、二级缓存
4.1 二级缓存产生的原因
默认情况下,只启用了本地的会话缓存即一级缓存,它仅仅对一个会话中的数据进行缓存
。
二级缓存产生的原因:
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
- 当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,
一级缓存中的数据被再次保存到二级缓存中。
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中。
4.2 二级缓存介绍
要启用全局的二级缓存,只需要在你的sql映射文件中添加一行:
<cache/>
二级缓存只作用于cache标签所在的映射文件中的语句,如果你混合使用Java API和XML映射文件,在共用接口中的语句将不会被默认缓存、你需要使用@CacheNamespaceRef注解指定缓存作用域。
这些属性可以通过cache元素的属性来修改,比如:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
上面这个更高级的配置策略:
-
创建了一个FIFO缓存,
-
每隔60秒刷新,
-
最多可以存储结果对象或列表512个应用
-
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同的线程中调用者产生冲突。
可用的清除策略有:
- LRU- 最近最少使用:移除最长时间不被使用的对象。
- FIFO-先进先出:按对象进入缓存的顺序来移除他们。
- SOFT-软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK-弱引用:更积极地基于垃圾收集器和弱引用规则移除对象。
4.3 开启步骤
1 在核心配置文件显示开启全局二级缓存
虽然默认为true
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2 在mapper文件里开启缓存
在要使用二级缓存的mapper.xml文件里开启,也可以自定义一些参数
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
3 测试使用
@Test
public void testGetUserByIdByCache2() {
SqlSession sqlSession1=null;
SqlSession sqlSession2=null;
try {
sqlSession1 = MybatisUtils.getSqlSession();
sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println("============第1次查询=============");
User user1 = mapper1.getUserById(1);
System.out.println(user1);
System.out.println("============第2次查询=============");
User user2 = mapper1.getUserById(1);
System.out.println(user2);
// 注意这里在其他sqlsession使用前必须关闭,如果不关闭sqlsession,不会把对象引用放到二级缓存共享给其他sqlsession
sqlSession1.commit();
// sqlSession1.close();
System.out.println("============第3次查询,使用sqlsession2=============");
User user3 = mapper2.getUserById(1);
System.out.println(user3);
System.out.println("============判断两个对象是否相同=============");
System.out.println(user1 == user3);
}
catch (Exception e){
}finally {
sqlSession1.close();
sqlSession2.close();
}
}
4 小结事项:
注意:
readOnly="true"/>
- 如果readOnly设置为true,则不需要让
user类实现Serializable接口
,否则需要。 - 如果readOnly设置为true,则两次从缓存中取出对象为一个,否则不是一个。
- 注意这里在其他sqlsession使用前必须关闭或者提交,如果不关闭sqlsession,不会把对象引用放到二级缓存共享给其他sqlsession。
五、自定义缓存(二级缓存)
实现以下接口
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
mybatis已经实现的缓存策略如下:默认使用LRU,清除最不常使用。
六、ehcache(第三方缓存)
- ehcache是一个纯java的进程内缓存框架,具有快速、精干等特点,是hibernate中默认的cache provider。
- 是一种广泛使用的开源java分布式缓存,主要面向通用缓存。
要在程序中使用ehcache
6.1 使用步骤
1 引入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
2 集成第三方缓存
除了上述自定义缓存方式,可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<!-- 开启二级缓存,在这个mapper文件中有效-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
type="org.mybatis.caches.ehcache.EhcacheCache"
/>
在mapper中指定使用我们的ehcache缓存实现。
3 将ehcache的配置文件ehcache.xml放在resources下
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!-- 配置缓存文件的路劲
java.io.tmpdir,表示临时文件夹,windows表示在C:\Documents and Settings\Administrator\Local Setting\Temp
-->
<diskStore path="../temp/ehcache"/>
<!-- 设定缓存的默认数据过期策略 -->
<!--
name:缓存名称
maxElementsInMemory:缓存最大个数
eternal:对象是否永久有效,一但设置了,timeout将不起作用
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒),仅当eternal=false对象不是永久有效时使用
timeToLiveSeconds:设置对象在失效前允许存活时间,最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB
maxElementsOnDisk:硬盘最大缓存个数
diskPersistent: 是否缓存虚拟机重启期数据
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU。
最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU
clearOnFlush:内存数量最大时是否清除
-->
<defaultCache
maxElementsInMemory="1000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"/>
<cache name="userCache"
maxElementsInMemory="1000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
4 测试使用
和mybatis自带的二级缓存没有区别,只是多了一些缓存持久化存放到本地。
七、Mybatis的一级缓存和二级缓存执行顺序
1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库
2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取
3、一般不会关闭一级缓存
4、二级缓存默认不开启
5、如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库
6、如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库;
综上:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;缓存执行顺序是:二级缓存–>一级缓存–>数据库
八、其他缓存
工作中一般使用redis作为数据库缓存,而不使用自定义缓存。
标签:mapper,缓存,查询,println,二级缓存,Mybatis,out 来源: https://www.cnblogs.com/happycarpediem/p/16217342.html