一、Spring Boot与缓存
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
- CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider
- CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache 存在于CacheManager的上下文中,一个CacheManager仅被一个CachingProvider所拥有。
- Cache:是一个类似Map的数据结构并临时存储以Key为索引的值,一个Cache仅被一个 CacheManager所拥有。
- Entry:是一个存储在Cache中的key-value对。
- Expiry :每一个存储在Cache中的条目有一个定义的有效期,一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除,缓存有效期可以通过ExpiryPolicy设置。
Spring从3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发:
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等
每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否 已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点:
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
1.1 缓存注解
名称 | 含义 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存,主要在删除数据后清理之前缓存的值 |
@CachePut | 保证方法被调用,又希望结果被缓存,主要用于数据修改后对缓存的数据进行刷新 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
1.2 注解主要参数
参数名 | 解释 | 示例 |
---|---|---|
value | cacheNames | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个,二选一 | @Cacheable(value=”mycache”) 或者 @Cacheable(cacheNames={”cache1”,”cache2”} |
key | keyGenerator | 缓存的 key,可以为空,如果指定要按照 SpEL 表达 式编写,如果不指定,则缺省按照方法的所有参数进行组合,二选一 | @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”, beforeInvocation=true) |
unless (@CachePut) (@Cacheable) | 用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 | @Cacheable(value=”testcache”,unless=”#result == null”) |
1.3 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 |
result | evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如 ‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false) | #result |
1.4 基本使用
包结构:
-
pom文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <!--缓存依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> </dependencies>
-
yml配置文件,配置数据源连接信息和mybatis的配置
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql:///test?serverTimezone=UTC username: root password: root123456 mybatis: configuration: # 开启mybatis-plus的驼峰命名 map-underscore-to-camel-case: true # 开启sql打印 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl debug: true
-
在启动类上开启Cache的注解扫描,并配置Mapper接口的扫描路径
/** * 1.开启基于注解的缓存 @EnableCaching */ @EnableCaching @MapperScan("com.veo.mapper") @SpringBootApplication public class SpringbootCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringbootCacheApplication.class, args); } }
-
创建对象
@Data @NoArgsConstructor @AllArgsConstructor public class Department implements Serializable { private Integer id; private String departmentName; } //------------------------------------------------ @Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private Integer id; private String lastName; private String email; private Integer gender; private Integer dId; }
-
创建对象的Mapper接口
@Repository public interface EmployeeMapper { @Select("select * from employee") List<Employee> getAllEmployees(); @Select("select * from employee where id=#{id}") Employee getEmployeeById(Integer id); @Update("update employee set last_name=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}") void updateEmployeeById(Employee employee); @Delete("delete from employee where id=#{id}") void deleteEmployeeById(Integer id); @Select("select * from employee wehre last_name=#{lastName}") Employee getEmployeeByLastName(String lastName); @Insert("insert into employee values(null,#{lastName},#{email},#{gender},#{dId})") void saveEmployee(Employee employee); }
@Repository public interface DeptMapper { @Select("select * from department where id=#{id}") Department getDepartmentById(Integer id); }
-
创建Service接口
public interface EmployeeService { Employee getEmployeeById(Integer id); List<Employee> getAllEmployees(); Employee updateEmployeeById(Employee employee); void deleteEmployeeById(Integer id); Employee getEmployeeByLastName(String lastName); }
public interface DeptService {
Department getDepartmentById(Integer id);
}
-
创建接口的实现类,在接口的实现类中使用Cache注解
@Service public class EmployeeServiceImpl implements EmployeeService { @Autowired EmployeeMapper employeeMapper; /** * cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存; * key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值 * 编写SpEL; #id;参数id的值 #a0 #p0 #root.args[0] * key = "#methodName+'['+#id+']'" 等价与 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,结果不缓存,返回结果为true不缓存,为false才缓存 * sync:是否使用异步模式,使用异步模式后unless不可以使用 * @param id * @return */ @Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#id>1 and #id<5",unless = "#id==2 and #result==null") @Override public Employee getEmployeeById(Integer id) { System.out.println("getEmployeeById" + id); return employeeMapper.getEmployeeById(id); } @Override public List<Employee> getAllEmployees() { System.out.println("getAllEmployees"); return employeeMapper.getAllEmployees(); } @Override public Employee updateEmployeeById(Employee employee) { System.out.println("updateEmployeeById" + employee.getId()); employeeMapper.updateEmployeeById(employee); } @Override public void deleteEmployeeById(Integer id) { System.out.println("deleteEmployeeById" + id); employeeMapper.deleteEmployeeById(id); } @Override public Employee getEmployeeByLastName(String lastName) { System.out.println("getEmployeeByLastName" + lastName); return employeeMapper.getEmployeeByLastName(lastName); } }
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
DeptMapper deptMapper;
@Override
public Department getDepartmentById(Integer id) {
return deptMapper.getDepartmentById(id);
}
}
-
编写Controller
@RestController public class EmployeeController { @Autowired EmployeeService employeeService; @GetMapping("/getAllEmp") public List<Employee> getAllEmployee(){ return employeeService.getAllEmployees(); } @GetMapping("/emp/{id}") public Employee getEmployeeById(@PathVariable("id")Integer id){ return employeeService.getEmployeeById(id); } @GetMapping("/emp") public Employee updateEmployee(Employee employee){ return employeeService.updateEmployeeById(employee);; } @GetMapping("/delEmp/{id}") public String deleteEmployee(@PathVariable("id") Integer id){ employeeService.deleteEmployeeById(id); return "success"; } @GetMapping("/getEmpByName/{name}") public Employee getEmpByName(@PathVariable("name") String lastName){ return employeeService.getEmployeeByLastName(lastName); } }
-
配置自定义的keyGenerator
@Configuration public class MyCacheConfig { @Bean public SimpleKeyGenerator keyGenerator(){ return new SimpleKeyGenerator(){ @Override public Object generate(Object target, Method method, Object... params) { //自定key返回格式getEmployeeById[id] return method.getName() + "[" + Arrays.asList(params) + "]"; } }; } }
结果:调用getEmployeeById(Integer id)方法后会缓存id为2,3,4并且返回结果不为空的对象到缓存中。
使用@CachePut、@CacheEvict、@Caching注解
@Cacheable(cacheNames = "emp",key = "#id")
@Override
public Employee getEmployeeById(Integer id) {
System.out.println("getEmployeeById" + id);
return employeeMapper.getEmployeeById(id);
}
/**
* @CachePut:既调用方法,又更新缓存数据;同步更新缓存
* 修改了数据库的某个数据,同时更新缓存;
* 运行时机:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
* 3、可以使用spEl的result来获取方法返回的结果
*
* 测试步骤:
* 1、查询1号员工;查到的结果会放在缓存中;
* key:1 value:lastName:张三
* 2、以后查询还是之前的结果,从缓存中拿到的数据
* 3、更新1号员工;【lastName:zhangsan;gender:0】
* 将方法的返回值也放进缓存了;
* key:为传入的employee对象 值:返回的employee对象;
* 4、查询1号员工?为什么是没更新前的?【1号员工没有在缓存中更新】
* 应该是更新后的员工,但是查询发现还是第一次缓存的结果,并不是更新后的数据,主要为修改和查询时的key不同
* 查询时key 为传入的参数的员工id,查询时key为传入的employee对象,此时缓存中有查询id的缓存所以没有更新
* 解决方法:统一指定存入缓存中的key
* key = "#employee.id":使用传入的参数的员工id;
* key = "#result.id":使用返回后的id
* @Cacheable的key是不能用#result 方法执行之前先查缓存
* @param employee
*/
@CachePut(value = "emp",key = "#result.id")
@Override
public Employee updateEmployeeById(Employee employee) {
System.out.println("updateEmployeeById" + employee.getId());
employeeMapper.updateEmployeeById(employee);
return employee;
}
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
* beforeInvocation = true:
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*/
@CacheEvict(value = "emp",key = "#id",beforeInvocation = true,allEntries = true)
@Override
public void deleteEmployeeById(Integer id) {
System.out.println("deleteEmployeeById" + id);
// employeeMapper.deleteEmployeeById(id);
}
/**
* @Caching 定义复杂的缓存规则
* 这要定义的复杂规则中有@CachePut注解,则该方法一定会被执行
* 所以根据名字查询方法一定会被调用,不会从缓存中拿数据
* @param lastName
* @return
*/
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
@Override
public Employee getEmployeeByLastName(String lastName) {
System.out.println("getEmployeeByLastName" + lastName);
return employeeMapper.getEmployeeByLastName(lastName);
}
抽取公共配置到@CacheConfig中
@CacheConfig(cacheNames = "emp",keyGenerator = "myKeyGenerator")
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable()
@Override
public Employee getEmployeeById(Integer id) {
System.out.println("getEmployeeById" + id);
return employeeMapper.getEmployeeById(id);
}
@CachePut()
@Override
public Employee updateEmployeeById(Employee employee) {
System.out.println("updateEmployeeById" + employee.getId());
employeeMapper.updateEmployeeById(employee);
return employee;
}
@Override
public List<Employee> getAllEmployees() {
System.out.println("getAllEmployees");
return employeeMapper.getAllEmployees();
}
@CacheEvict(beforeInvocation = true,allEntries = true)
@Override
public void deleteEmployeeById(Integer id) {
System.out.println("deleteEmployeeById" + id);
// employeeMapper.deleteEmployeeById(id);
int i = 10/0;
}
}
使用小结:
- @Cacheable
- 先去缓存中根据key查询数据,查到数据就返回,查不到执行目标方法后将返回的结果缓存,默认key为方法的参数
- 不可以使用spEl的result来获取返回的结果
- 可以指定缓存的添加和key的生成方式
- @CachePut
- 先调用目标方法将方法的返回结果缓存,并根据key更新缓存中的数据,和@Cacheable使用的是要指定一致的key,否则会根据默认方法的参数进行缓存,查询的时候就不是更新之后的数据而还是先前的数据
- 目标方法一定会得到执行
- @CacheEvict
- 和上面注解一起使用时,也要配置一致的key,否则key默认为方法的参数值,默认是在方法执行之后清除对应key的缓存,方法执行有异常就不清除缓存
- allEntries = true:指定清除这个缓存中所有的数据
- beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
- @Caching
- 定义复杂的缓存规则
- 其中有@CachePut注解时,目标方法一定会得到执行
- @CacheConfig
- 抽取公共的配置属性到类上面
1.5 基本原理
查看缓存自动配置类CacheAutoConfiguration,自动配置类导入了CacheConfigurationImportSelector类,查看此类发现该类导入了
- 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.SimpleCacheConfiguration 默认生效
- org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
十个CacheConfiguration配置类对象,默认生效的是SimpleCacheConfiguration查看该类发现该类型向容器中注入了一个ConcurrentMapCacheManager的缓存CacheManager,查看该类主要实现了CacheManager接口中的getCache(),方法返回Cache对象,在ConcurrentMapCacheManager中重写了该方法并返回一个ConcurrentMapCache对象来对Cache进行操作,在该ConcurrentMapCache中使用ConcurrentMap来存放Cache。
debug查看@Cacheable注解的运行流程如下:
- 方法运行之前,先去查询ConcurrentMapCache(缓存组件),根据cacheNames来获取Cache对象,由于第一次获取Cache组件还没有创建,此时Cache会自动创建,之后会去Cache中使用一个key(默认就是方法的参数),去缓存中查找,没有查到缓存就调用目标方法,并将目标方法返回的结果,放进缓存中。
- key默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key,生成策略如下:
- 如果没有参数:key=new SimpleKey();
- 如果有一个参数:key=参数的值
- 如果有多个参数:key=new SimpleKey(params);
总结:@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据,核心组件为ConcurrentMapCacheManager和ConcurrentMapCache。
1.6 整合redis实现缓存
-
导入Redis依赖后默认的CacheManager为RedisCacheManager,之后就使用Redis来作为缓存工具
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <!--缓存依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> </dependencies>
-
yml配置Redis的连接信息
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql:///ssm?serverTimezone=UTC username: root password: root redis: host: 127.0.0.1 port: 6379 mybatis: configuration: # 开启mybatis-plus的驼峰命名 map-underscore-to-camel-case: true # 开启sql打印 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl debug: true
-
默认RedisTemplate保存对象时使用jdk序列化机制来将对象序列化后保存到Redis中,这时去Redis中查看对象为乱码的格式,可以在容器中注入自定义的RedisTemplate使用json方法来序列化对象,方式如下,乱码如图:
- 对象实现序列化接口
@Data @NoArgsConstructor @AllArgsConstructor public class Employee implements Serializable { private Integer id; private String lastName; private String email; private Integer gender; private Integer dId; } //------------------------------------------------ @Data @NoArgsConstructor @AllArgsConstructor public class Department implements Serializable { private Integer id; private String departmentName; }
- 配置序列化为json的自定义RedisTemplate对象并注入容器中
@Configuration public class MyRedisConfig { //Employee的Redis序列化器 @Bean("empRedisTemplate") public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Employee> template = new RedisTemplate<>(); Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class); //根据源码查看可以自定义设置默认的序列化器,参考源码配置 template.setDefaultSerializer(serializer); template.setConnectionFactory(redisConnectionFactory); return template; } //Department的Redis序列化器 @Bean("deptRedisTemplate") public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Department> template = new RedisTemplate<>(); Jackson2JsonRedisSerializer<Department> serializer = new Jackson2JsonRedisSerializer<Department>(Department.class); //根据源码查看可以自定义设置默认的序列化器 template.setDefaultSerializer(serializer); template.setConnectionFactory(redisConnectionFactory); return template; } }
- 测试
@SpringBootTest class SpringbootCacheApplicationTests { @Autowired EmployeeService employeeService; @Autowired DeptService deptService; /** * 操作k-v都为object */ @Autowired RedisTemplate redisTemplate; /** * 操作k-v都为字符串 */ @Autowired StringRedisTemplate stringRedisTemplate; @Autowired RedisTemplate<Object, Employee> employeeRedisTemplate; @Autowired RedisTemplate<Object, Department> departmentRedisTemplate; /** * Redis常见的五大数据类型 * String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合) * stringRedisTemplate.opsForValue()[String(字符串)] * stringRedisTemplate.opsForList()[List(列表)] * stringRedisTemplate.opsForSet()[Set(集合)] * stringRedisTemplate.opsForHash()[Hash(散列)] * stringRedisTemplate.opsForZSet()[ZSet(有序集合)] */ @Test void contextLoads2() { Employee employee = employeeService.getEmployeeById(2); employeeRedisTemplate.opsForValue().set("emp-01",employee); System.out.println(employeeRedisTemplate.opsForValue().get("emp-01")); Department department = deptService.getDepartmentById(1); departmentRedisTemplate.opsForValue().set("dept-01",department); System.out.println(departmentRedisTemplate.opsForValue().get("dept-01")); } }
-
使用RedisCacheManager缓存数据时,RedisCacheManager内部使用jdk的序列化方法进行缓存,所以在redis中会出入之前的乱码,这时可以自定义配置RedisCacheManager,配置如下:
@Configuration public class MyRedisConfig { //Employee的Redis序列化器 @Bean public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Employee> template = new RedisTemplate<>(); Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class); //根据源码查看可以自定义设置默认的序列化器 template.setDefaultSerializer(serializer); template.setConnectionFactory(redisConnectionFactory); return template; } //Department的Redis序列化器 @Bean public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Department> template = new RedisTemplate<>(); Jackson2JsonRedisSerializer<Department> serializer = new Jackson2JsonRedisSerializer<Department>(Department.class); //根据源码查看可以自定义设置默认的序列化器 template.setDefaultSerializer(serializer); template.setConnectionFactory(redisConnectionFactory); return template; } /** * 默认的序列化器 * 有多个序列化器是要使用@Primay指定一个默认的序列化器 * 要使用指定的序列化器是要在其缓存注解上面配置 * @param redisConnectionFactory * @return */ @Primary @Bean public RedisCacheManager defaultRedisCacheManager(RedisConnectionFactory redisConnectionFactory){ RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() //配置对key的序列化,这里使用string来序列化 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //配置对value的序列化,使用jsong来序列化 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) //不保存null值的缓存 .disableCachingNullValues() //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key .prefixCacheNameWith("->"); RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory) //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步 .cacheDefaults(configuration).transactionAware().build(); return cacheManager; } /** * 针对于Employee序列化的RedisCacheManager * @Bean 默认使用方法名作为bean的id * @param redisConnectionFactory * @return */ @Bean public RedisCacheManager empRedisCacheManager(RedisConnectionFactory redisConnectionFactory){ RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() //配置对key的序列化,这里使用string来序列化 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //配置对value的序列化,使用jsong来序列化 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class))) //不保存null值的缓存 .disableCachingNullValues() //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key .prefixCacheNameWith("->"); RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory) //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步 .cacheDefaults(configuration).transactionAware().build(); return cacheManager; } /** * 针对于Department序列化的RedisCacheManager * @Bean 默认使用方法名作为bean的id * @param redisConnectionFactory * @return */ @Bean public RedisCacheManager deptRedisCacheManager(RedisConnectionFactory redisConnectionFactory){ RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig() //配置对key的序列化,这里使用string来序列化 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //配置对value的序列化,使用jsong来序列化 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Department>(Department.class))) //不保存null值的缓存 .disableCachingNullValues() //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key .prefixCacheNameWith("->"); RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory) //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步 .cacheDefaults(configuration).transactionAware().build(); return cacheManager; } }
-
应用自定义配置的RedisCacheManager于缓存注解上面
@CacheConfig(cacheNames = "dept",cacheManager = "deptRedisCacheManager") @Service public class DeptServiceImpl implements DeptService { @Autowired DeptMapper deptMapper; @Cacheable @Override public Department getDepartmentById(Integer id) { return deptMapper.getDepartmentById(id); } }
-
也可以编码方式使用缓存
@Service public class DeptServiceImpl implements DeptService { @Autowired DeptMapper deptMapper; @Autowired @Qualifier("deptRedisCacheManager") RedisCacheManager redisCacheManager; @Override public Department getDepartmentById(Integer id) { Department department = deptMapper.getDepartmentById(id); Cache deptCache = redisCacheManager.getCache("dept"); deptCache.put("->dept:" + id, department); return department; } }
-
redis数据如下: