缓存
查询 : 连接数据库,耗资源
◆ 一次查询的结果,给他暂存一个可以直接取到的地方 --> 内存:缓存
我们再次查询的相同数据的时候,直接走缓存,不走数据库了
1.什么是缓存[Cache]?
◆ 存在内存中的临时数据
◆ 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存?
◆ 减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据可以使用缓存?
◆ 经常查询或不经常改变的数据 【可以使用缓存】
Mybatis缓存
◆ MyBatis包含- - 个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
◆ MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
• 默认情况下,只有一 -级缓存开启。 (SqlSession级别的缓存, 也称为本地缓存)
• 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
• 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
一级缓存
一级缓存也叫本地缓存:SqlSession
与数据库同一次会话期间查询到的数据会放在本地缓存中
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
1.1配置环境
新建一个mybatis-08项目
表结构如下:
配置环境:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
<!--引入外部配置文件-->
<properties resource="application.properties"/>
<!--标准的日志工厂实现-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--给实体类起别名-->
<typeAliases>
<package name="com.cy.pojo"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务的类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的四个基本信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.cy.dao.UserMapper"/>
</mappers>
</configuration>
application.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=root
pom.xml
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
工具类
package com.cy.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//SqlSessionFactory --> sqlSession
public class MybatisUtils {
static SqlSessionFactory sqlSessionFactory = null;
static {
try {
//使用Mybatis第一步 :获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
创建一个实体类
package com.cy.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String pwd;
}
创建接口和xml
UserMapper
public interface UserMapper {
//根据id查询用户
User queryUserById(@Param("id") int id);
}
<?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.cy.dao.UserMapper">
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
</mapper>
测试:
package com.cy.dao;
import com.cy.pojo.User;
import com.cy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest {
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
}
测试步骤:
1.开启日志!
2.测试在一个Sesion中查询两次相同记录
3.查看日志输出
缓存失效的情况:
1.查询不同的东西
2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
我们在中间加一条语句,继续测试生效情况
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
public interface UserMapper {
//根据id查询用户
User queryUserById(@Param("id") int id);
int updateUser(User user);
}
<update id="updateUser" parameterType="user">
update user set name=#{name}, pwd=#{pwd} where id = #{id};
</update>
测试:
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
mapper.updateUser(new User(2,"aaaa","bbbbb"));
User user2 = mapper.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
我们可以发现,增删改查操作可能会改变原来的数据,所以必定会刷新缓存!
3.查询不同的Mapper.xml
4.手动清理缓存
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.clearCache();//手动清理缓存
User user2 = mapper.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
sqlSession.close();
}
我们可以发现,手动清理缓存后,sql这次查询了2次
小结:
一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!
一缓存相当于就是一个map。
二级缓存
◆ 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
◆ 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制
◆ 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
◆ 如果会话关闭了,这个会员对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
◆ 新的会话查询信息,就可以从二级缓存中获取内容
◆ 不同的mapper查询出的数据会放在自己对应的缓存(map)中
一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
◆ 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
◆ 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。
步骤:
1.开启全局缓存
<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
2.在Mapper.xml中使用二级缓存
<!--在当前Mapper.xml中使用二级缓存-->
<cache/>
<!--在查询语句中也能使用二级缓存控制-->
<select id="queryUserById" resultType="user" useCache="true">
select * from user where id = #{id}
</select>
3.也可以自定义参数
<!--在当前Mapper.xml中使用二级缓存-->
<!--readOnly为True时是读相同实例, false时是读拷贝值,相对安全,所以会出现比较时出现false
-->
<!--
eviction="FIFO" 使用FIFO输出策略
flushInterval="60000" 每隔60秒刷新缓存
size="512" 最多只能存512个缓存
readOnly="true" 是否只读
-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
小结:
◆ 问题:我们需要将实体类序列化,否则就会报错
◆ 只要开启了二级缓存,在同一个Mapper下就有效
◆ 所有的数据都会放在一级缓存中
◆ 只有当前会话提交,或者关闭的时候,才会提交到二级缓存中
缓存原理
自定义缓存(Ehcache)
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
1.要在程序中使用,需要导包:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
2.在mapper中指定使用我们的ehcache缓存实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
Ehcache 中ehcache.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 path="java.io.tmpdir"/>
<!--默认缓存设置-->
<defaultCache maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="true"
maxElementsOnDisk="10000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="FIFO"
/>
<!--
<cache name 缓存名唯一标识
maxElementsInMemory="1000" 内存中最大缓存对象数
eternal="false" 是否永久缓存
timeToIdleSeconds="3600" 缓存清除时间 默认是0 即永不过期
timeToLiveSeconds="0" 缓存存活时间 默认是0 即永不过期
overflowToDisk="true" 缓存对象达到最大数后,将其写入硬盘
maxElementsOnDisk="10000" 磁盘最大缓存数
diskPersistent="false" 磁盘持久化
diskExpiryThreadIntervalSeconds="120" 磁盘缓存的清理线程运行间隔
memoryStoreEvictionPolicy="FIFO" 缓存清空策略
FIFO 先进先出
LFU less frequently used 最少使用
LRU least recently used 最近最少使用
/>
-->
<cache name="testCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true"
memoryStoreEvictionPolicy="FIFO">
</cache>
</ehcache>
使用方法:
用法
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import java.net.URL;
public class EhcacheUtil {
private static final String default_path = "/ehcache.xml";
private URL url;
private CacheManager manager;
public CacheManager getManager() {
return manager;
}
public void setManager(CacheManager manager) {
this.manager = manager;
}
private EhcacheUtil() {
url = getClass().getResource(default_path);
manager = CacheManager.create(url);
manager.addCacheIfAbsent("default_cache");
}
private EhcacheUtil(String path) {
url = getClass().getResource(path);
System.out.println(url.getPath());
manager = CacheManager.create(url);
manager.addCacheIfAbsent("default_cache");
}
public void put(String cacheName, String key, Object value) {
Cache cache = manager.getCache(cacheName);
Element element = new Element(key, value);
cache.put(element);
}
public Object get(String cacheName, String key) {
Cache cache = manager.getCache(cacheName);
Element element = cache.get(key);
return element == null ? null : element.getObjectValue();
}
public Cache get(String cacheName) {
return manager.getCache(cacheName);
}
public void remove(String cacheName, String key) {
Cache cache = manager.getCache(cacheName);
cache.remove(key);
}
public static void main(String[] args){
EhcacheUtil ehcacheUtil = new EhcacheUtil("/ehcache.xml");
ehcacheUtil.put("default_cache","key","value");
String result = (String)ehcacheUtil.get("default_cache","key");
System.out.println(result);
//System.out.println(EhcacheUtil.class.getResource(""));
}
}