Spring Boot缓存
概述
Spring提供了对应用程序添加缓存的支持。从本质上讲,将缓存应用于方法上,从而根据缓存中的信息减少执行次数。当开发者调用一个方法时,将方法的参数和返回值作为Key/Value缓存起来,当再次调用这个方法时,如果缓存中存在对应数据,就从缓存中获取数据,否则再去执行该方法。
支持
Spring并没有提供缓存的实现,而是提供了一套Api,可以自由选择缓存的实现。目前Spring Boot支持的缓存有如下几种(Spring Boot会按顺序检测以下提供的程序):
- Generic
- JCache(JSR-107)(EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Readis
- Caffeine
- Simple
可以通过设置属性来指定提供缓存的程序。
无论使用哪种缓存实现不同的只是缓存配置,使用的缓存注解是一致的
注解 | 介绍 |
---|---|
@EnableCaching | 启用Spring的注释驱动的缓存管理功能 |
@CacheConfig | 当此批注出现在给定的类上时,它为该类中定义的任何缓存操作提供一组默认设置。 |
@Cacheable | 表示可以缓存调用方法(或类中的所有方法)结果的注释。每次调用指定方法时,将进行缓存行为,检查是否已缓存该方法参数和结果。如果在缓存中找不到键的值,则将调用目标方法并将返回的值存储在关联的缓存中。 |
@CacheEvict | 表示方法(或类上的所有方法)触发缓存逐出操作的注解。 |
@CachePut | 表示方法(或类上的所有方法)触发缓存放置操作的注释。 |
@Caching | 此批注可用作元批注,以创建具自定义组合批注。 |
一、Ehcache 2.x 缓存
在项目的pom.xml文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
添加缓存配置文件
如果存在Ehcache的依赖,并且在classpath下有名为ehcache.xml的文件,那么EhCacheCacheManager将会自动作为缓存的提供者。因此,在resources目录下创建ehcache.xml文件作为Ehcach缓存的配置文件。
如果未配置ehcache.xml,则Ehcache依赖包下的ehcache-failsafe.xml是ehcache的默认配置。
<ehcache>
<!--diskStore元素是可选的。-->
<!--如果为任何缓存启用了overflowToDisk或diskPersistent,则必须对其进行配置。-->
<!--diskStore只有一个属性 - path。这是将在其中创建.data和.index文件的目录路径。-->
<!--如果路径是Java系统属性,则将其替换为正在运行的VM中的值-->
<!--user.home - 用户的主目录-->
<!--user.dir - 用户的当前工作目录-->
<!--java.io.tmpdir - 默认临时文件路径-->
<!--ehcache.disk.store.dir - 在命令行上指定的系统属性(例:java -Dehcache.disk.store.dir=/u01/myapp/diskdir)-->
<!--子目录可以在属性后指定 例:java.io.tmpdir/cache-->
<diskStore path="java.io.tmpdir/cache"/>
<!--强制性默认缓存配置-->
<!--这些设置将应用于使用CacheManager.add(String cacheName)创建的缓存。-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<!--localTempSwap - 该策略允许缓存在缓存操作期间使用本地磁盘。磁盘存储是临时的,并在重新启动后清除。-->
<!--当策略为"none"时,所有缓存都保留在内存中(磁盘无溢出,磁盘无持久性)。-->
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="student"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
</ehcache>
缓存配置 <cache/>:
属性 | 介绍 |
---|---|
name | 设置缓存的名称,用于标识缓存。 |
maxElementsInMemory | 设置在内存中创建的最大对象数(0 ==无限制)。 |
maxElementsOnDisk | 设置将在DiskStore中维护的最大对象数。默认值为零,表示无限制。 |
eternal | 设置元素是否一直存在。如果为true,超时将被忽略。 |
overflowToDisk | 设置当内存中的缓存达到maxInMemory限制时元素是否可以溢出到磁盘。 |
timeToIdleSeconds | 设置元素过期之前的空闲时间,即缓存创建以后最后一次访问缓存的时间到超时失效时的时间间隔。值为0表示无穷大,默认值为0。 |
timeToLiveSeconds | 设置元素过期之前的生存时间,即从创建时间到元素过期之间的间隔。值为0表示一直存在,默认值为0。 |
diskPersistent | 磁盘存储在虚拟机重新启动后是否仍然存在,默认值为false。 |
diskExpiryThreadIntervalSeconds | 做元素失效监测以及清除工作的线程运行间隔时间,默认值为120秒。 |
diskSpoolBufferSizeMB | 磁盘缓冲区大小,默认30MB(内部以字节为单位,设置的值转换为字节后不可超过正整数表示范围)。 |
memoryStoreEvictionPolicy | 达到maxElementsInMemory限制后,将强制执行清除策略。默认策略是最近最少使用(LRU),其他可用策略先进先出(指定为FIFO)和不常用(指定为LFU)。 |
开启缓存
在项目的入口类上添加@EnableCaching注解开启缓存。
@EnableCaching
@SpringBootApplication
public class EhcacheApplication {
public static void main(String[] args) {
SpringApplication.run(EhcacheApplication.class, args);
}
}
创建数据接口
@Service
@CacheConfig(cacheNames = "student")
public class StudentService {
@Resource
private StudentMapper studentMapper;
@Cacheable
public Student findById(Integer id) {
Student student = studentMapper.findById(id);
System.out.println("查询 :" + JSON.toJSONString(student));
return student;
}
@CachePut(key = "#student.id")
public Student insert(Student student) throws Exception {
int status = studentMapper.insert(student);
if (status == 0) {
throw new Exception("Insert failed");
}
System.out.println("插入 :" + JSON.toJSONString(student));
return student;
}
@CacheEvict(key = "#student.id")
public Student delete(Student student) throws Exception {
int status = studentMapper.delete(student);
if (status == 0) {
throw new Exception("Delete failed");
}
System.out.println("删除 :" + JSON.toJSONString(student));
return student;
}
@CachePut(key = "#student.id")
public Student update(Student student) throws Exception {
int status = studentMapper.update(student);
if (status == 0) {
throw new Exception("Update failed");
}
System.out.println("更新 :" + JSON.toJSONString(student));
return student;
}
}
创建请求接口
@RestController
@RequestMapping("/std")
public class StudentController {
@Resource
private StudentService studentService;
/**
* 查询
*/
@RequestMapping("/sel")
public String findById(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
student = studentService.findById(student.getId());
msg.put("status", 200);
msg.put("result", student);
} catch (Exception e) {
e.printStackTrace();
}
return JSON.toJSONString(msg);
}
/**
* 插入
*/
@RequestMapping("/ins")
public String insert(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.insert(student);
msg.put("status", 200);
msg.put("message", "插入成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "插入失败");
}
return JSON.toJSONString(msg);
}
/**
* 删除
*/
@RequestMapping("/del")
public String delete(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.delete(student);
msg.put("status", 200);
msg.put("message", "删除成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "删除失败");
}
return JSON.toJSONString(msg);
}
/**
* 更新
*/
@RequestMapping("/upd")
public String update(Student student) {
Map<String, Object> msg = new HashMap<>();
try {
studentService.update(student);
msg.put("status", 200);
msg.put("message", "更新成功");
} catch (Exception e) {
msg.put("status", 400);
msg.put("message", "更新失败");
}
return JSON.toJSONString(msg);
}
}
测试
向数据库中添加数据:
(一) 查询
连续访问查询接口页面并观察输出信息:
查询 控制台根据输出信息发现,在浏览器中多次获取数据,数据查询方法只执行了一遍。
(二) 更新
访问更新接口:
更新再次访问查询接口:
查询观察控制台:
控制台
发现访问更新接口后再次访问查询接口,查询接口并没有再次从数据库获取数据,而是从缓存中获取,所以更新接口返回的结果会覆盖指定参数键的缓存。
(三) 插入
访问插入接口:
插入访问查询接口:
查询观察控制台:
控制台发现插入数据后再次查询并没有从数据库中获取数据,而是从缓存中获取。因为插入时对参数和返回的结果进行了缓存。
(四) 删除
访问删除接口:
删除访问查询接口:
查询观察控制台:
控制台发现访问删除接口后数据库中的数据被删除,再次访问查询接口进行查询发现触发了查询方法,说明删除数据时缓存同时也被删除了。
二、Redis 单机缓存
和Ehcache一样,如果在classpath下存在Redis并且Redis已经配置好,此时会默认使用RedisCacheManager作为缓存的提供者。
在项目的pom.xml文件中添加以下依赖(与之前的依赖相比不同的只是缓存的提供者):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
添加项目配置:
spring:
datasource:
url: jdbc:mysql://xxx.xxx.xxx.xxx/user
username: xxxx
password: xxxxxx
# 缓存配置
cache:
# 配置缓存名,多个缓存可使用逗号分隔(one,two)
cache-names: student
# 缓存存在时间
redis:
time-to-live: 120s
redis:
host: xxx.xxx.xxx.xxx
port: xxxx
之后的流程与之前一致就不在阐述了。