Springboot 高级开发缓存 Cache
一、 Spring Boot与缓存
Java Caching定义了5个核心接口
-
CachingProvider
定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可
以在运行期访问多个CachingProvider。 -
CacheManager
定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache
存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。 -
Cache
一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个
CacheManager所拥有。 -
Entry
一个存储在Cache中的key-value对。
-
Expiry
每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
二、 重要缓存注解及概念
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 更新缓存 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
三、搭建基本环境
1、导入数据库文件,创建出employee和department
2、创建javaBean封装数据
3、整合Mybatis操作数据库
1.配置数据源application.properties
2.使用注解版的Mybatis;
1)、@MapperScan指定需要扫描的mapper接口所在的包
2)、创建具体的Mapper接口及Mybatis注解
4、创建Service层
5、创建Controller
配置属性
spring.datasource.url=jdbc:mysql://localhost:33307/mybatis
spring.datasource.username=root
spring.datasource.password=123456
# 开启驼峰命名法(否则部分字段封装不了,如d_id 映射bean的 dId)
mybatis.configuration.map-underscore-to-camel-case=true
#打印sql
# logging.level.cn.edu.ustc.springboot.mapper=debug
# debug=true
# com.quantsmart.springboot.mapper 为包名
logging.level.com.quantsmart.springboot.mapper=debug
四、快速体验缓存
步骤:
1、开启基于注解的缓存@EnableCaching
2、标注缓存注解即可 @Cacheable,@CacheEvict,@CachePut
启动类增加缓存注解 @EnableCaching:
package com.quantsmart.springboot;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* 搭建基本环境
* 1、导入数据库文件,创建出employee和department
* 2、创建javaBean封装数据
* 3、整合Mybatis操作数据库
* 1.配置数据源application.properties
* 2.使用注解版的Mybatis;
* 1)、@MapperScan指定需要扫描的mapper接口所在的包
* 2)、创建具体的Mapper接口及Mybatis注解
* 3)、创建Service层
* 4)、创建Controller
*
*/
@MapperScan("com.quantsmart.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
简单使用:
/service/EmployeeService.java
package com.quantsmart.springboot.service;
import com.quantsmart.springboot.bean.Employee;
import com.quantsmart.springboot.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* @author: kaiyi
* @create: 2020-08-07 01:01
*/
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
*
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0]
* getEmp[2]
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用;
*
*
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
*
* condition:指定符合条件的情况下才缓存;
* ,condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
*/
@Cacheable(cacheNames={"emp"})
public Employee getEmp(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
控制台测试打印:
第一次访问 http://localhost:8080/emp/3 查数据库,第二次访问直接从缓存中获取
查询3号员工
2020-08-07 10:55:23.786 DEBUG 50445 --- [nio-8080-exec-9] c.q.s.mapper.EmployeeMapper.getEmpById : ==> Preparing: SELECT * FROM employee WHERE id = ?
2020-08-07 10:55:23.791 DEBUG 50445 --- [nio-8080-exec-9] c.q.s.mapper.EmployeeMapper.getEmpById : ==> Parameters: 3(Integer)
2020-08-07 10:55:23.796 DEBUG 50445 --- [nio-8080-exec-9] c.q.s.mapper.EmployeeMapper.getEmpById : <== Total: 0
五、CachePut和CacheEvict
package com.atguigu.cache.service;
import com.atguigu.cache.bean.Employee;
import com.atguigu.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
*
*
* 原理:
* 1、自动配置类;CacheAutoConfiguration
* 2、缓存的配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3、哪个配置类默认生效:SimpleCacheConfiguration;
*
* 4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
* 5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
*
* 运行流程:
* @Cacheable:
* 1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
* 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
* SimpleKeyGenerator生成key的默认策略;
* 如果没有参数;key=new SimpleKey();
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params);
* 3、没有查到缓存就调用目标方法;
* 4、将目标方法返回的结果,放进缓存中
*
* @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
* 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
*
* 核心:
* 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
* 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
*
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
*
* key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值
* 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0]
* getEmp[2]
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用;
*
*
* cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
*
* condition:指定符合条件的情况下才缓存;
* ,condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
* @param id
* @return
*
*/
@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
/**
* @CachePut:既调用方法,又更新缓存数据;同步更新缓存
* 修改了数据库的某个数据,同时更新缓存;
* 运行时机:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
*
* 测试步骤:
* 1、查询1号员工;查到的结果会放在缓存中;
* key:1 value:lastName:张三
* 2、以后查询还是之前的结果
* 3、更新1号员工;【lastName:zhangsan;gender:0】
* 将方法的返回值也放进缓存了;
* key:传入的employee对象 值:返回的employee对象;
* 4、查询1号员工?
* 应该是更新后的员工;
* key = "#employee.id":使用传入的参数的员工id;
* key = "#result.id":使用返回后的id
* @Cacheable的key是不能用#result
* 为什么是没更新前的?【1号员工没有在缓存中更新】
*
*/
// value = "emp" 已被抽取到该类公共区域,所以,这里的就不需要再写了。
// @CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@CachePut(/*value = "emp",*/key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
*
* beforeInvocation = true:
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*
*
*/
@CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
// @Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
}
六、整合redis作为缓存
默认使用的是 ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在ConcurrentMap<Object,Object>,开发中使用缓存中间件:redis,memcached,ehcache;
安装redis使用docker进行安装,可以在 https://hub.docker.com/ 中查找相关镜像。
# 1、拉取redis镜像
docker pull redis
# 2、运行(-d 后端运行)
docker run -d -p 6379:6379 --name myredis redis
运行结果:
➜ ~ docker run -d -p 6379:6379 --name myredis redis
532fb52e1ad2067a028088221e40467505be92961be981f00d742baeed8dc4e2
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
532fb52e1ad2 redis "docker-entrypoint.s…" 23 seconds ago Up 21 seconds 0.0.0.0:6379->6379/tcp myredis
821198a31ab0 centos/mysql-57-centos7 "container-entrypoin…" 2 days ago Up 6 hours 0.0.0.0:33307->3306/tcp mysql02
整合redis
整合步骤:
- 1、引入依赖
spring-boot-starter-cache,spring-boot-starter-data-redis - 2、写配置
2.1 自动配置了那些 (CacheAutoConfiguration
会导入RedisCacheConfiguration
,自动配好了缓存管理器)
2.2 配置使用Redis作为缓存spring.cache.type=redis
- 3、使用缓存
pom.xml
<!-- spring-cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<!-- lettuce的bug导致netty堆外内存溢出, 所以,这里排除掉lettuce,切换使用jedis -->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<!-- 这里无需写版本号,Springboot 的 spring-boot-parent 会默认控制-->
<!--
<version>2.9.0</version>
-->
</dependency>
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)