SpringBoot 从入门到光头 —— 第十一章 SpringBoot 与缓存
1. JSR107 缓存规范
Java Caching 定义了 5 个接口,分别是 CachingProvider
、CacheManager
、Cache
、Entry
以及 Expiry
。
CachingProvider
定义了常创建、配置、获取、管理和控制多个CacheManager
。一个应用可以在运行期间访问多个CacheProvider
CacheManager
定义了创建、配置、获取、管理和控制多个唯一命名的Cache
, 这些Cache
存在于CacheManager
上下文中。一个CacheManager
仅被一个CachingProvider
所拥有Cache
是一个类似 Map 的数据结构并临时存储以 Key 为索引的值。一个Cache
仅能被一个CacheManager
所拥有。Entry
是一个存储在Cache
中的 key-value 对。Expiry
每一个存储在Cache
中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy
设置
2. Spring 缓存抽象
Spring 从3.1开始定义了 org.springframework.chache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用 JCache (JSR107) 注解 简化我们的开发。
Cache
接口为缓存的组件规范定义,包含缓存的各种操作组合Cache
接口下的 Spring 提佛难过了各种xxxCache
的实现,如RedisCache
,EhCacheCache
,ConcurrentMapCache
等- 每次调用需要缓存的功能时,Spring 会检查制定参数的制定目标方法是否已经被缓存调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法,并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用 Spring 缓存抽象是需要关注以下两点:
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
3. 几个重要的概念 & 缓存注解
概念 | 内容 |
---|---|
Cache |
缓存接口,定义缓存操作。实现有:RedisCache 、EhCacheCache 、ConcurrentMapCache 等 |
CacheManager |
缓存管理器,管理各种缓存(Cache )组件 |
@Cacheable |
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict |
清空缓存 |
@CachePut |
保证方法被调用,又希望结果被缓存(常用于缓存更新) |
@EnableCaching |
开启基于注解的缓存 |
keyGenerator |
缓存数据时 key 的生成策略 |
serialize |
缓存数据时 value 序列化策略 |
4. 缓存 @Cacheable
初体验
4.1. 示例代码
department
数据表
create table department
(
id int auto_increment
primary key,
department_name varchar(50) not null,
constraint department_id_uindex
unique (id)
);
employee
数据表
create table employee
(
id int auto_increment
primary key,
last_name varchar(50) not null,
email varchar(50) null,
gender int null,
d_id int null,
constraint employee_id_uindex
unique (id)
);
pom.xml
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.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-web</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yaml
spring:
application:
name: cache
datasource:
url: jdbc:mysql://localhost:3306/spring_cache
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
logging:
level:
com:
gregperlinli:
cache:
mapper:
debug
com.yourname.cache.CacheApplicaton
/**
* Build basic environment<br/>
* 1. Import database file<br/>
* 2. Create JavaBean to encapsulate data<br/>
* 3. Integrate mybatis to operate database<br/>
* 1. Configure data sources<br/>
* 2. Configure mybatis using annotated version<br/>
* 1) use {@code @MapperScan} to specify the package where the mapper interface to be scanned is located<br/>
* Quick experience cache<br/>
* 1. Enable annotation based caching {@code @EnableCaching}>br/>
* 2. Annotate cache annotation<br/>
* 1. {@code @Cacheable}<br/>
* 2. {@code @CacheEvict}<br/>
* 3. {@code @CachePut}<br/>
* @author gregperlinli
*/
@SpringBootApplication
@MapperScan("com.gregperlinli.cache.mapper")
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
com.yourname.cache.bean.Department
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public class Department {
private Integer id;
private String departmentName;
public Department() {
super();
}
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 + '\'' +
'}';
}
}
com.yourname.cache.bean.Employee
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public class Employee {
private Integer id;
private String lastName;
private String email;
/**
* 1. Male 2. Female
*/
private Integer gender;
private Integer dId;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
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 +
'}';
}
}
com.yourname.cache.mapper.DepartmentMapper
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Mapper
public interface EmployeeMapper {
/**
* Get employee by id
*
* @param id id
* @return employee
*/
@Select(value = "SELECT * FROM employee WHERE id = #{id}")
Employee getEmployeeById(Integer id);
/**
* Update employee
*
* @param employee employee
*/
@Update(value = "UPDATE employee SET last_name = #{lastName}, email = #{email}, gender = #{gender}, d_id = #{dId} WHERE id = #{id}")
void updateEmployee(Employee employee);
/**
* Delete employee
*
* @param id id
*/
@Delete(value = "DELETE FROM employee WHERE id = #{id}")
void deleteEmployee(Integer id);
/**
* Insert employee
*
* @param employee employee
*/
@Insert(value = "INSERT INTO employee(last_name, email, gender, d_id) VALUES(#{lastName}, #{email}, #{gender}, #{dId})")
void insertEmployee(Employee employee);
}
com.yourname.cache.service.EmployeeService
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public interface EmployeeService {
/**
* Get employee
*
* @param id id
* @return employee
*/
Employee getEmployee(Integer id);
}
com.yourname.cache.service.impl.EmployeeServiceImpl
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* {@code @Cacheable} Cache the running results of the method, when you need the same data in the future, you can get it directly from the cache<br/>
* {@code CacheManager} manage multiple cache components. The real CRUD operation on the cache is in the cache component, and each cache component has its own unique name<br/>
* The attribute of {@code @Cacheable}<br/>
* {@code cacheNames/value}: Specifies the name of the cache<br/>
* {@code key}: The key used to cache data, The default is to use the value of the method parameter, 1-The return value of method<br/>
* use SpEL expression: {@code #id}; Value of parameter ID equals to {@code #a0, #p0, #root.args[0]}<br/>
* {@code keyGenerator}: The generator of key, you can specify the ID of the key generator<br/>
* You can only choose one of the {@code Key} or {@code KeyGenerator}<br/>
* {@code cacheManager}: Specify cache manager<br/>
* {@code cacheResolver}: Specify cache resolver<br/>
* {@code condition}: Specifies that the cache is only cached if the conditions are met<br/>
* {@code unless}: Negative caching. When the condition specified by unless is true, the return value of the method will not be cached, the results can be obtained for judgment <br/>
* {@code sync}: Use asynchronous mode
*/
@Override
@Cacheable(cacheNames = {"employee"}, key = "#id", condition = "#id > 0", unless = "#result == null")
public Employee getEmployee(Integer id) {
System.out.println("Query " + id + " employee.");
return employeeMapper.getEmployeeById(id);
}
}
com.yourname.cache.controller.EmployeeController
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping(value = "/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id) {
return employeeService.getEmployee(id);
}
}
4.2. @Cachable
注解的具体用法
@Cacheable
注解将方法的运行结果进行缓存,当以后需要用到相同的数据时,可以直接从缓存中获取,而不需要调用方法
CacheManager
管理多个 Cache
组件,对缓存的真正 CRUD 操作在 Cache
组件中,每一个缓存组件都有自己唯一的名字
几个属性:
cacheNames
:指定缓存组件的名字key
:缓存数据使用的 Key,可以用它来指定,默认是使用方法参数的值,通常需要编写 SpEL 表达式来实现,例如:#id
→ 参数 ID 的值,也可以是#a0
、#p0
、#root.args[0]
,详细的 SpEL 表达式后面会讲到keyGenerator
:Key 的生成器;可以自己指定 Key 的生成器组件 ID(注意⚠️:key
/keyGenerator
只能二选一使用)cacheManager
:指定缓存管理器cacheResolver
:指定缓存解析器condition
:指定符合条件的情况下获取缓存unless
:否定缓存,当unless
指定条件为true
,方法的返回值就不会被缓存,可以获取到结果进行判断sync
:是否使用异步模式
4.3. Spring 的基本缓存架构
5. 缓存的工作原理
自动配置类:
CacheAutoConfiguration
缓存的配置类:
默认生效的配置类:
SimpleCacheConfiguration
SimpleCacheConfiguration
的作用是给容器中注册了一个CacheManager
:ConcurrentMapCacheManager
CacheManager
可以获取和创建ConcurrentMapCache
类型的缓存组件,其作用是将数据保存到ConcurrentMap
中
6. 缓存的运行流程(以 @Cacheable
为例)
- 方法运行之前,先去查询
Cache
(缓存组件),按照cacheNames
指定的名字获取(CacheManager
先获取相应的缓存),第一次获取缓存的时候如果没有Cache
组件会自动创建。 - 去
Cache
中查找缓存的内容,使用一个 Key,默认是方法的参数;Key 是按照某种策略生成的,默认是使用SimpleKeyGenerator
生成的SimpleKeyGenerator
生成 Key 的默认策略:- 如果没有参数:
key = new SimpleKey
- 如果有一个参数:
key = paramValue
- 如果有多个参数:
key = SimpleKey(params)
- 如果没有参数:
- 没有查到缓存,就调用目标方法
- 将目标方法返回的结果,放进缓存中
@Cachable
标注的方法执行之前先来检查缓存中有没有数据,默认按照参数的值作为 Key 去查询缓存,如果没有就运行方法,并将结果放入缓存中,以后再来调用的时候就可以直接使用缓存中的数据。
核心:
- 使用
cacheManager
「ConcurrentMapCacheManager
」按照名字得到Cache
「ConcurrentMapCache
」组件 - Key 使用
KeyGenerator
「SimpleKeyGenerator
」生成的
7. 缓存 @Cacheable
的其他属性
cacheNames
:指定缓存组件的名字,将方法返回结果放在哪个缓存中,使用数组的方式,可以指定多个缓存key
:缓存数据使用的 Key,可以用它来指定,默认是使用方法参数的值,通常需要编写 SpEL 表达式来实现,例如:#id
→ 参数 ID 的值,也可以是#a0
、#p0
、#root.args[0]
,详细的 SpEL 表达式后面会讲到 示例:
@Cacheable(cacheNames = {"employee"}, key = "#root.methodName + '[' + #id + ']'")
keyGenerator
:Key 的生成器;可以自己指定 Key 的生成器组件 ID(注意⚠️:key
/keyGenerator
只能二选一使用) 示例:
com.yourname.cache.config.MyCacheConfig
/** * @author gregPerlinLi * @since 2022-01-17 */ @Configuration public class MyCacheConfig { @Bean(value = "myKeyGenerator") public KeyGenerator keyGenerator() { return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]"; } }
@Cacheable(cacheNames = {"employee"}, keyGenerator = "myKeyGenerator")
cacheManager
:指定缓存管理器cacheResolver
:指定缓存解析器condition
:指定符合条件的情况下获取缓存 示例:
@Cacheable(cacheNames = {"employee"}, keyGenerator = "myKeyGenerator", condition = "#id > 1 and #root.methodName eq 'emp'")
unless
:否定缓存,当unless
指定条件为true
,方法的返回值就不会被缓存,可以获取到结果进行判断 示例:
@Cacheable(cacheNames = {"employee"}, keyGenerator = "myKeyGenerator", ccondition = "#id > 1 and #root.methodName eq 'emp'", unless = "#a0 == 2")
sync
:是否使用异步模式
8. 缓存 SpEL 表达式的使用
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName |
Root object | 当前被调用的方法名 | #root.methodName |
mathod |
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"}) ,则有两个 cahce |
#root.caches[0].name |
argumentName |
Evaluation context | 方法参数的名字,可以直接 #paramName ,也可以使用 #p0 、#a0 的形式,0 代表参数的索引 |
#iban 、#a0 、#p0 |
result |
Evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如 unless ,cache put 的表达式 cache evict 的表达式 beforeInvocation = false ) |
#result |
9. 缓存 @CachePut
的使用
@CatchPut
既调用方法,又更新缓存数据,达到同步更新缓存的效果(取缓存和放缓存部分应该使用同一个 Key)
运行时机:
- 先调用目标方法
- 将目标方法的结果缓存起来
示例代码:
com.yourname.cache.service.EmployeeService
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public interface EmployeeService {
/**
* Update employee
*
* @param employee employee
* @return employee
*/
Employee updateEmployee(Employee employee);
}
com.yourname.cache.service.impl.EmployeeServiceImpl
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* {@code @CatchPut}: Both method calls and cache updates<br/>
* A data in the database is modified and the cache is updated at the same time<br/>
* Running time:<br/>
* 1. Call the target method<br/>
* 2. Cache the results of the target method<br/>
* Testing procedure:<br/>
* 1. Query employee 1, the query results will be put into the cache<br/>
* key: 1; value: XiaoMing<br/>
* 2. Future query or previous results<br/>
* 3. Update employee 1「lastName:gregPerlinLi; gender:0」<br/>
* The return value of the method is also put into the cache<br/>
* key: Incoming employee object; value: Returned employee object<br/>
* 4. Query employee 1 again<br/>
* Employee 1 is not updated in the cache<br/>
* {@code key = "#employee.id"}: Employee ID using the incoming in parameter<br/>
* {@code key = "#result.id"}: Employee ID using the returned parameter (The key of {@code @Cacheable} cannot use this method)<br/>
*/
@Override
@CachePut(value = "employee", key = "#result.id")
public Employee updateEmployee(Employee employee) {
System.out.println("Update employee: " + employee);
employeeMapper.updateEmployee(employee);
return employee;
}
}
com.yourname.cache.controller.EmpoloyeeController
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping(value = "/emp")
public Employee updateEmployee(Employee employee) {
return employeeService.updateEmployee(employee);
}
}
10. 缓存 @CacheEvict
的使用
@CacheEvict
用于清除缓存,有以下的属性
value
:使用的缓存key
:指定要清除的 KeyallEntries
:是否要清除缓存内的所有 Key(默认是false
)beforeInvocation
:是否在方法之前执行清除缓存(默认是false
,即缓存清除操作实在方法执行之后执行,如果出现异常,缓存将不会清除)
示例代码:
com.yourname.cache.service.EmployeeService
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public interface EmployeeService {
/**
* Delete employee
*
* @param id id
*/
void deleteEmployee(Integer id);
}
com.yourname.cache.service.impl.EmployeeServiceImpl
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* {@code @CatchEvict}: Clear the cache<br/>
* {@code key}: Specify the data to purge<br/>
* {@code allEntries = true}: Specifies to clear all data<br/>
* {@code beforeInvocation}: Execute before method (Default is {@code false})<br/>
*/
@Override
@CacheEvict(value = "employee", key = "#id")
public void deleteEmployee(Integer id){
System.out.println("Delete employee id: " + id);
// employeeMapper.deleteEmployee(id);
}
}
com.yourname.cache.controller.EmployeeController
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@RestController
public class EmployeeController {
@GetMapping(value = "/del-emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id) {
employeeService.deleteEmployee(id);
return "success";
}
}
11. 缓存 @Caching
& @CacheConfig
的使用
@Caching
为组合注解,可以用来定义复杂的缓存规则
@CacheConfig
用于抽取缓存的公共配置
示例代码:
com.yourname.cache.mapper.EmployeeMapper
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Mapper
public interface EmployeeMapper {
/**
* Get employee by last name
*
* @param lastName last name
* @return employee
*/
@Select(value = "SELECT * FROM employee WHERE last_name = #{lastName}")
Employee getEmployeeByLastName(String lastName);
}
com.yourname.cache.service.EmployeeService
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
public interface EmployeeService {
/**
* Get employee by last name
* @param lastName last name
* @return employee
*/
Employee getEmployeeByLastName(String lastName);
}
com.yourname.cache.service.impl.EmployeeServiceImpl
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Service
@CacheConfig(cacheNames = "employee")
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* {@code @Caching}: Define complex caching rules
*/
@Override
@Caching(
cacheable = {
@Cacheable(/*value = "employee",*/ key = "#lastName")
},
put = {
@CachePut(/*value = "employee",*/ key = "#result.id"),
@CachePut(/*value = "employee",*/ key = "#result.email")
}
)
public Employee getEmployeeByLastName(String lastName) {
return employeeMapper.getEmployeeByLastName(lastName);
}
}
com.yourname.cache.controller.EmployeeController
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@RestController
public class EmployeeController {
@GetMapping(value = "/emp/last-name/{lastName}")
public Employee getEmployeeByLastName(@PathVariable("lastName") String lastName) {
return employeeService.getEmployeeByLastName(lastName);
}
}
12. 搭建 Redis 环境 & 测试
Spring 缓存抽象默认使用的是 ConcurrentMapCacheManager
,其将数据保存在一个 ConcurrentMap<Object, Object>
中。而在实际开发中,我们通常会使用缓存中间件,如:Redis、Memcached、Ehcache等,这里以整合 Redis 作为缓存为例
Redis 是一个开源(BSD 许可)的,内存中的数据结构储存系统,它可以用作数据库、缓存和消息中间件。
搭建 Redis 环境:
安装 Redis(使用 Docker 安装)
使用客户端连接 Redis 数据库(这里推荐使用 RedisInsight)
引入 Redis 的
starter
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置 Redis
application.yaml
spring: redis: host: 127.0.0.1 port: 16379
测试缓存
原理:
CacheManager
→Cache
缓存组件来实际给缓存中存取数据- 引入 Redis 的
starte
之后,容器中保存的是RedisCacheManager
RedisCacheManager
创建RedisCache
作为缓存组件,RedisCache
通过操作 Redis 还书数据的- 默认保存数据 K-V 均是对象时,利用序列化来保存
- 引入了 Redis 的
starter
,cacheManager
变为RedisCacheManager
- 默认创建的
RedisCacheManager
,在操作 Redis 的时候,使用的是RedisTemplateObject<Object, Object>
RedisTemplate<Object, Object>
是默认使用 JDK 的序列化机制
- 引入了 Redis 的
- 引入 Redis 的
示例代码:
com.yourname.cache.config.MyRedisConfig
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
}
com.yourname.cache.test.RedisTest
/**
* @author gregperlinli
*/
@SpringBootTest
class RedisTest {
@Autowired
EmployeeMapper employeeMapper;
/**
* Operating string
*/
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* Operating object
*/
@Autowired
RedisTemplate redisTemplate;
@Autowired
RedisTemplate<Object, Employee> employeeRedisTemplate;
/**
* Five common data types of Redis:<br/>
* String, List, Set, Hash, ZSet(Ordered set)<br/>
* {@code stringRedisTemplate.opsForValue()}[String]<br/>
* {@code stringRedisTemplate.opsForList()}[List]<br/>
* {@code stringRedisTemplate.opsForSet()}[Set]<br/>
* {@code stringRedisTemplate.opsForHash()}[Hash]<br/>
* {@code stringRedisTemplate.opsForZSet()}[ZSet]<br/>
*/
@Test
public void test01() {
// Save data to Redis
// stringRedisTemplate.opsForValue().append("msg", "hello");
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
stringRedisTemplate.opsForList().leftPush("my_list", "1");
stringRedisTemplate.opsForList().leftPush("my_list", "2");
stringRedisTemplate.opsForList().leftPush("my_list", "3");
}
/**
* Test save object
*/
@Test
public void test02() {
// By default, if the object is saved, the JDK serialization mechanism is used to save the serialized data to Redis
// redisTemplate.opsForValue().set("Employee01", employeeMapper.getEmployeeById(1));
// 1. Save data in JSON
// 2. Default serialization rule of RedisTemplate
employeeRedisTemplate.opsForValue().set("Employee01", employeeMapper.getEmployeeById(1));
}
}
13. 自定义 CacheManager
由于 RedisTemplate<Object, Object>
默认使用的是 JDK 的序列化机制,要想实现 JSON 序列化机制,需要自定义 CacheManager
来实现
示例代码:
com.yourname.cache.config.MyRedisConfig
/**
* @author gregPerlinLi
* @since 2022-01-17
*/
@Configuration
public class MyRedisConfig {
/**
* Configure a universal cache manager
*
* @param redisConnectionFactory redis connection factory
* @return redis cache manager
*/
@Bean
public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory) {
// Use the default configuration of the cache
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
// Use GenericJackson2JsonRedisSerializer as serializer
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(configuration);
return builder.build();
}
}