Spring Boot与缓存
JSR-107、Spring缓存抽象、整合Redis
缓存:加速系统访问,提升系统性能
热点数据、临时数据(如验证码)
1. JSR-107
1.1 背景
统一缓存的开发规范及提升系统的扩展性,J2EE发布了JSR-107
缓存规范
1.2 JSR107简介
CacheManager与Cache的关系,类比连接池与连接
涉及的包
javax.cache.cache-api
Cache中定义了缓存的CRUD操作
用的很少
2. Spring缓存抽象
2.1 Spring缓存抽象简介
为了简化开发,Spring提供了自己的缓存抽象
Spring 3.1 以后
支持使用JCache(JSR-107)注解
真正要操作缓存的是Cache组件
Cache接口的实现不同,缓存技术就不一样【RedisCache、EhCacheCache、ConcurrentMapCache等】
@Cacheable(数据存在缓存中就不会再调用方法)、@CacheEvict、@CachePut(方法总会被调用):作用在方法上,简化常见的缓存操作
缓存过程中会涉及到两个问题:key生成策略、value序列化策略【对象序列化、对象转JSON等】
2.2 基本环境搭建
2.2.1 导入数据库文件
这里以代码的形式给出
/*springboot_cache.sql*/
/*
Navicat MySQL Data Transfer
Source Server : 本地
Source Server Version : 50528
Source Host : 127.0.0.1:3306
Source Database : springboot_cache
Target Server Type : MYSQL
Target Server Version : 50528
File Encoding : 65001
Date: 2018-04-27 14:54:04
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`departmentName` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lastName` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2.2 创建maven项目并引入坐标
1)创建项目过程略过
2)创建主配置类
@SpringBootApplication
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
3)resources目录下创建application.yml
4)pom中引入坐标【pom关于项目部分的内容没有给出】
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.2.3 创建JavaBean封装数据
//Department
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
// TODO Auto-generated constructor stub
}
public Department(Integer id, String departmentName) {
super();
this.id = id;
this.departmentName = departmentName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department [id=" + id + ", departmentName=" + departmentName + "]";
}
}
//Employee
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性别 1男 0女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
注:由于JavaBean属性(dId)和数据库表字段(d_id)不是一一对应的,需要开启驼峰命名匹配规则
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_cache
username: root
password: 123456
mybatis:
configuration:
map-underscore-to-camel-case: true
2.2.4 整合MyBatis操作数据库
2.2.4.1 配置数据源信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/spring_cache
username: root
password: 123456
# driver-class-name: com.mysql.jdbc.Driver
注:spring.datasource.driver-class-name可以不写,根据url可以自动判断
2.2.4.2 使用注解版的MyBatis
1)@MapperScan指定需要扫描的mapper接口所在的包
//主配置类
@MapperScan("com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootCacheApplication.class, args);
}
}
2)编写Mapper
@Mapper
public interface EmployeeMapper {
@Insert("insert into employee(lastName, email, gender, d_id) values(#{lastName}, #{email}, #{gender}, #{dId})")
public void insertEmployee(Employee employee);
@Select("select * from employee where id = #{id}")
public Employee getEmployeeById(Integer id);
@Update("update employee set lastName=#{lastName}, email=#{email}, gender=#{gender} where id=#{dId}")
public void updateEmployeeById(Employee employee);
@Delete("delete from employee where id=#{id}")
public void deleteEmployeeById(Integer id);
}
3)编写Service
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public void insertEmployee(Employee employee){
employeeMapper.insertEmployee(employee);
}
public Employee getEmployeeById(Integer id){
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
public void updateEmployee(Employee employee){
employeeMapper.updateEmployeeById(employee);
}
public void deleteEmployeeById(Integer id){
employeeMapper.deleteEmployeeById(id);
}
}
4)编写Controller
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmployeeById(id);
return employee;
}
}
测试成功!
2.3 快速体验缓存
2.3.1 使用步骤
- 开启基于注解的缓存【@EnableCaching】
- 标注缓存注解即可【@Cacheable、@CacheEvict、@CachePut】,缓存一般用在Service层
2.3.2 @Cacheable
将方法的返回值进行缓存,以后再要相同的数据,直接从缓存中取,不用再调用方法,一般用于查询方法
2.3.2.1 属性
1)cacheName/value:指定缓存组件的名字,将方法的返回值放在哪个缓存中,是数组的方式,可以指定多个缓存;【必填】
CacheManager管理多个Cache组件,对缓存的真正CRUD操作是在Cache组件中,每一个缓存组件有自己唯一一个名字
2)key:缓存数据时使用的key,默认是使用方法的参数值
支持SpEL,如#id,使用参数id的值作为key,取出参数id的值,SpEL支持的写法见下表
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root object | 当前被调用的方法名 | #root.methodName |
method | root object | 当前被调用的方法 | #root.method.name |
target | root object | 当前被调用的目标对象 | #root.target |
targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
args | root object | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache | #root.caches[0].name |
argument name | evaluation context | 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; | #iban 、 #a0 、 #p0 |
@Cacheable的key是不能用#result的
3)keyGenerator:key的生成器,可以自己指定key的生成器的组件id
注:key与keyGenerator二选一使用即可
4)cacheManager:指定缓存管理器
5)cacheResolver:指定缓存解析器
6)condition:指定符合条件的情况下才从缓存中取和放入缓存
使用SpEL进行指定
7)unless:否定缓存,条件为true不会进行缓存,可以通过#result
获取到方法返回值进行条件判断
如:unless = “#result == null”,缓存结果如果为null就不进行缓存
注:当同时存在condition和unless,如果满足condition条件同时又满足unless条件,那么依然不会进行缓存
8)sync:是否使用异步模式,默认不使用异步模式,如果使用异步模式,就不支持unless了
2.3.2.2 运行流程
1)@Cacheable标注的方法运行之前,CacheManager按照cacheNames指定的名字获取Cache(缓存组件),第一次获取Cache如果没有会自动创建Cache组件。
2)去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成的key
SimpleKeyGenerator生成key的默认策略:
- 如果没有参数:key=new SimpleKey();
- 如果有一个参数:key=参数的值
- 如果有多个参数:key=new SimpleKey(params);|
3)没有查到缓存就调用目标方法(@Cacheable标注的方法)
4)将目标方法的返回值放进缓存中
总结如下:
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存:以后再来调用就可以直接使用缓存中的数据不执行标注方法
核心:
- 使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
- key使用keyGenerator生成的,默认是SimpleKeyGenerator
2.3.2.3 实验
实验是基于2.2.4.2构建的注解版Mybatis项目
1)keyGenerator属性练习
首先我们需要在容器中放一个自定义的KeyGenerator
自定义的CacheConfig如下:
@Configuration
public class CacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator myKeyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName() + "[" + Arrays.asList(params) + "]";
}
};
}
}
使用自定义的KeyGenerator
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(value = "emp", keyGenerator = "myKeyGenerator")
public Employee getEmployeeById(Integer id){
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
}
2)condition属性练习
使用condition属性
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
//当@Cacheable标注方法的第一个参数大于1并且方法名为getEmployeeById时才对返回值进行缓存
@Cacheable(value = "emp", condition = "#a0>1 and #root.methodName eq 'getEmployeeById'")
public Employee getEmployeeById(Integer id){
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
}
2.3.3 @CachePut
@CachePut: 既调用方法,又更新缓存数据;同步更新缓存
取缓存用的key和更新用的key必须是同一个key,才能达到修改数据库同步缓存的效果
修改了数据库的某个数据,同时更新缓存;
2.3.3.1 属性
同@Cacheable
2.3.3.2 运行流程
- 先调用目标方法
- 将目标方法的结果缓存起来
更新的值对应的键和查询方法的键不是一个
2.3.3.3 实验
实验步骤:
1、查询1号员工;查到的结果会放在缓存中,
key: 1 value: id为1的employee对象
2、以后查询还是之前的结果
3、更新1号员工: 【lastName :zhangsan; gender:0】
将方法的返回值也放进缓存了;
key:传入的employee对象 值:返回的employee对象;
4、查询1号员工?
应该是更新后的员工,但实际却是更新前的【1号员工没有在缓存中更新】,这是因为更新的值对应的键和查询方法的键不是一个
key = "#employee. id" :使用传入参数的employee对象id;
key = "#result. id":使用返回后的employee对象id
@Cacheable的key不能用#result
是不是删掉emp缓存中的所有数据
制定了allEntries,就不用指定key了
2.3.4 @CacheEvict
缓存清除
2.3.4.1 属性
除@Cacheable内说明的属性外,还有以下属性:
1)allEntries
指定清除时是否要清除这个缓存中所有的数据,默认为true,指定该参数时,就不需要再指定key了
2)beforeInvocation
缓存的清除是否在方法之前执行
beforeInvocation = false:默认代表缓存清除操作是在方法执行之后执行;如果方法内出现异常缓存就不会清除
beforeInvocation =true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
2.3.5 @Caching
定义复杂的缓存规则
2.3.5.1 属性
可以设置@Cacheable、@CachePut、@CacheEvict数组
2.3.5.2 实验
Mapper
@Select("select * from employee where lastName=#{lastName}")
public Employee getEmployeeByLastName(String lastName);
@Select("select * from employee where id = #{id}")
public Employee getEmployeeById(Integer id);
Service
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Caching(cacheable = {
@Cacheable(key = "#lastName")
},put = {
@CachePut(key = "#result.id"),
@CachePut(key = "#result.email")
})
public Employee getEmployeeByLastName(String lastName){
return employeeMapper.getEmployeeByLastName(lastName);
}
@Cacheable
public Employee getEmployeeById(Integer id){
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
}
Controller
@RestController
public class EmployeeController {
@GetMapping("/emp/lastname/{lastname}")
public Employee getEmployeeByName(@PathVariable("lastname") String lastname){
Employee employee = employeeService.getEmployeeByLastName(lastname);
return employee;
}
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmployeeById(id);
return employee;
}
}
首先访问http://localhost:8080/emp/lastname/giegie
,执行后emp缓存中会同时存在lastName和employee对象、id和employee对象、email和employee对象三组键值对,再次访问http://localhost:8080/emp/lastname/giegie
,仍然会访问数据库,这是因为@Caching中同时设置了@Cacheable和@CachePut,由于存在@CachePut,所以即使缓存中有lastName和employee对象的键值对,也会先执行方法,查询数据库。然后访问http://localhost:8080/emp/2
,不会访问数据库,因为缓存中已经存在了id和employee对象的键值对。
2.3.6 @CacheConfig
标注在类上,抽取缓存的公共配置,在@CacheConfig设置过的属性,当前类内其他的缓存注解就不需要再次设置了,所有的缓存注解都会生效。
2.3.6.1 属性
参考@Cacheable
2.4 缓存的工作原理
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
注:通过开启自动配置报告,可以看到哪些配置类生效了
# application.yml
debug: true
4)SimpleCacheConfiguration给容器中注册了一个CacheManager【ConcurrentMapCacheManager】
5)ConcurrentMapCacheManager可以获取和创建ConcurrentMapCache类型的缓存组件
6)ConcurrentMapCache的作用是将数据保存在ConcurrentMap中
3. 整合redis
默认使用的是ConcurrentMapCacheManager的ConcurrentMapCache作为缓存组件,将数据保存在ConcurrentMap<Object, Object>中
开发中一般使用缓存中间件,如redis、memecached、ehcache。
对于SpringBoot支持的缓存配置,导入相应的依赖后相应的配置就会生效。
jedis:redis的客户端
3.1 安装redis
通过Docker或本地安装
3.2 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.3 配置redis
如果redis不在本地,需要在全局中添加如下配置
spring:
redis:
host: localhost
# 默认为本地
3.4 redis的自动配置
自动配置类:RedisAutoConfiguration
自动配置类中提供了Spring用来简化操作redis的RedisTemplate
(操作k-v都是字符串的)和StringRedisTemplate
(操作k-v都是对象的)
3.5 操作redis
3.5.1 操作五大数据类型
redis常见的五大数据类型
String (字符串)、List (列表)、Set (集合)、Hash (散列)、ZSet (有序集合)
stringRedisTemplate. opsForValue()[简化操作String (字符串) ]
stringRedisTemplate . opsForlist()[简化操作List (列表) ]
stringRedisTemplate. opsForSet()[简化操作Set (无序集合) ]
stringRedisTemplate. opsForHash()[简化操作Hash (散列) ]
stringRedisTemplate. opsForZSet()[简化操作ZSet (有序集合) ]
RedisTemplate对这些操作也是支持的。
注:StringRedisTemplate和RedisTemplate中的方法就相当于redis中的命令,虽然操作redis的方式不同,但实现的功能是相同的
3.5.2 操作对象
要求对象必须是可序列化的(实现了Serializable接口)
1)使用默认序列化机制存储对象
默认如果保存对象,使用jdk序列化机制,将序列化后的数据保存到redis中
redisTemplate. opsForValue(). set( "emp-01 ", emp);
2)以json的方式保存对象
(1)自己将对象转为json;
(2)改变redisTemplate默认的序列化规则;
//定义一个我们自己的配置类,用于向容器中放组件
@Configuration
public class RedisConfig {
//向容器中放一个自定义的RedisTemplate,指定泛型,专门用于序列化Employee的对象
@Bean
public RedisTemplate<Object, Employee> empredisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
//更改默认的序列化规则
Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class CRUDTest {
@Autowired
RedisTemplate<Object, Employee> empredisTemplate;
@Test
public void testRedis(){
ValueOperations<Object, Employee> operations = empredisTemplate.opsForValue();
operations.set("emp", new Employee(1, "hello redis", "[email protected]", 2, null));
}
}
这样序列化为JSON就成功了
3.6 缓存原理
通过配置类给容器中添加CacheManager,CacheManager创建Cache缓存组件,Cache来实际给缓存中存取数据。
对应到redis,引入redis的starter后,容器中保存的是RedisCacheManager,RedisCacheManager帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据。
3.7 测试
这里的测试调用了一个查询数据的方法
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable
public Employee getEmployeeById(Integer id){
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
}
测试发现存入redis的数据如下
默认保存数据k-v都是object,是利用序列化保存的,整个缓存流程如下:
1)引入了redis的starter后,RedisCacheConfiguration会生效
2)RedisCacheConfiguration会首先加载RedisAutoConfiguration
3)RedisAutoConfiguration会向容器中放入RedisTemplate<Object, Object>
4)RedisAutoConfiguration加载完成后,RedisCacheConfiguration进行加载,将上一步放入的RedisTemplate<Object, Object>作为参数,调用下面的方法,向容器中放入RedisCacheManager
5)从下图中可以看到,初始化RedisCacheManager的时候传入了RedisTemplate<Object, Object>
6)RedisTemplate<object, object> 默认使用的是jdk的序列化机制
那么如何保存为json呢,通过自定义CacheManager
实现,因为redis提供的CacheManager入参已经写死了泛型,RedisTemplate<Object, Object>,即使我们自定义RedisTemplate<Object, Employee>放入容器,创建RedisCacheManager时也不会传入我们自定义的RedisTemplate<Object, Employee>
自定义CacheManager
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> empredisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
存入redis的数据如下
这里还有个问题,存入的数据有个前缀
这是因为我们在创建RedisCacheManager后,设置了使用前缀,默认将cachename作为key的前缀
cacheManager.setUsePrefix(true);
当多个实体类共用同一个RedisCacheManager时,就会出现问题。
比如,还是按照上面的RedisCacheManager,这个RedisCacheManager是单独针对Employee的,现在又多了一个实体类,Department,操作Department时也需要用到缓存
@Service
public class DepartmentService {
@Autowired
DepartmentMapper departmentMapper;
@Cacheable("dept")
public Department getDepartmentById(Integer id){
return departmentMapper.getDepartmentById(id);
}
}
@RestController
public class DepartmentController {
@Autowired
DepartmentService departmentService;
@GetMapping("/dept/{id}")
public Department getDepartment(@PathVariable("id") Integer id){
return departmentService.getDepartmentById(id);
}
}
向缓存中存数据的时候是没有问题的
但是再次刷新页面,从缓存中取数据就会报错,查询到的数据不能反序列化回来
这是因为存的是dept的json数据,CacheManager使用RedisTemplate<Object, Employee>操作redis,没有办法反序列化Department。
解决方法是定义两套RedisCacheManager和RedisTemplate,我们自己的配置类如下:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Employee> empredisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
// @Primary:设置默认缓存管理器
@Primary
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Bean
public RedisTemplate<Object, Department> departmentRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Department> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
return template;
}
@Bean
public RedisCacheManager deptcacheManager(RedisTemplate<Object, Department> departmentRedisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(departmentRedisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
}
由于有两个RedisCacheManager,我们需要用@Primary
注解设置其中一个为默认缓存管理器,同时需要在用到缓存注解的地方通过cacheManager
属性指定要使用的缓存管理器,如果使用的是默认的缓存管理器,就不需要指定。
@Service
public class DepartmentService {
@Autowired
DepartmentMapper departmentMapper;
@Cacheable(value = "dept",cacheManager = "deptcacheManager")
public Department getDepartmentById(Integer id){
return departmentMapper.getDepartmentById(id);
}
}
通过编码的方式使用缓存
以上都是通过注解的方式使用缓存的,那么如何通过编码的方式使用缓存呢
@Service
public class DepartmentService {
@Autowired
DepartmentMapper departmentMapper;
@Qualifier("deptcacheManager")
@Autowired
RedisCacheManager deptcacheManager;
public Department getDepartmentById(Integer id){
Department department = departmentMapper.getDepartmentById(id);
//使用缓存管理器得到缓存组件,进行API调用即可
deptcacheManager.getCache("dept").put("12", department);
return department;
}
}
这样就可以通过编码使用缓存了
这里遇到一个没有解决的问题,我在容器中是放了两个RedisCacheManager的
@Bean
public RedisCacheManager deptcacheManager(RedisTemplate<Object, Department> departmentRedisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(departmentRedisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
@Primary
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager;
}
如果我通过如下的编码方式操作缓存
@Service
public class DepartmentService {
@Autowired
DepartmentMapper departmentMapper;
@Autowired
RedisCacheManager deptcacheManagers;
public Department getDepartmentById(Integer id){
Department department = departmentMapper.getDepartmentById(id);
deptcacheManagers.getCache("dept").put("12", department);
return department;
}
}
这里的deptcacheManagers仍然能获取到值,非常奇怪???
标签:缓存,return,SpringBoot,1x,Boot,id,Employee,employee,public From: https://www.cnblogs.com/wzzzj/p/18039117