内容概览:JSR-107、Spring缓存抽象、整合Redis
1、JSR-107简介
Java Caching定义五个核心接口: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设置;
如果要使用,需要导入下面的依赖:
<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.1</version>
</dependency>
2、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
- 1、确定方法需要被缓存以及他们的缓存策略;
- 2、从缓存中读取之前缓存存储的数据。
3、几个重要概念&缓存注解
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
3.1 @Cacheable/@CachePut/@CacheEvict 主要的参数
属性名称 | 作用 | 举例 |
---|---|---|
value | 缓存的名称,在spring 配置文件中定义,必须指定至少一个 | 例如:@Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”} |
key | 缓存的key,可以为空,如果指定 要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如:@Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用SpEL 编写,返回true 或者false,只有为true 才进行缓存/清除缓存 | 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries(@CacheEvict ) | 是否清空所有缓存内容,缺省为false,如果指定为true,则方法调用后将立即清空所有缓存 | 例如:@CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation(@CacheEvict) | 是否在方法执行前就清空,缺省为false,如果指定为true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如:@CachEvict(value=”testcache”,beforeInvocation=true) |
#4、演示缓存
4.1 环境搭建
使用SpringBoot的向导创建一个工程:springboot-01-cache,选中web(为了测试)和Spring cache abstraction ,还有mysql和mybatis模块。
4.1.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.cache</groupId>
<artifactId>springboot-01-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-01-cache</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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>
<!--mybatis模块-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql模块-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- json 在使用StringRedisTemplate存储的时候,可以把对象转成json存入redis,下次取出的时候再把json转成对象 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.60</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.1.2 创建数据库环境
在本地的mysql创建cache数据库,执行下面创建表和添加数据的sql:
CREATE TABLE `department` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`departmentName` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO department (departmentName) VALUES ('开发部');
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;
INSERT INTO employee (lastName,email,gender,d_id) VALUES ('张三','zhangsan@qq.com',1,1);
4.1.3 整合MyBatis
1、application.yml配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/cache?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true #开启驼峰命名
logging:
level:
com.springboot.cache.mapper: debug #控制台打印sql
2、主程序类配置扫描Mapper
@MapperScan指定需要扫描的mapper接口所在的包
package com.springboot.cache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@MapperScan指定需要扫描的mapper接口所在的包
@MapperScan("com.springboot.cache.mapper")
@SpringBootApplication
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
3、创建Mapper,Service和Controller
EmployeeMapper:因为配置了@MapperScan(“com.springboot.cache.mapper”),所以不需要单独添加@Mapper注解:
package com.springboot.cache.mapper;
import com.springboot.cache.bean.Employee;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
public Employee getEmpById(Integer id);
@Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
public void updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
EmployeeSrivice:
package com.springboot.cache.service;
import com.springboot.cache.bean.Employee;
import com.springboot.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id){
System.out.println("从数据库查询"+id+"号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
}
EmployeeController:
package com.springboot.cache.controller;
import com.springboot.cache.bean.Employee;
import com.springboot.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getById(@PathVariable("id") Integer id){
return employeeService.getEmp(id);
}
}
4.1.4 测试
启动工程,浏览器访问:http://localhost:8080/emp/1,能正常的查询到数据说明环境整合成功。
4.2 演示缓存
缓存使用,只需要简单的两步:
1、开启基于注解的缓存 :在主程序类上标注:@EnableCaching
2、在对应的的方法上加上缓存注解:@Cacheable、@CacheEvict、@CachePut
4.2.1 开启基于注解的缓存
在主程序类上添加注解 @EnableCaching:
package com.springboot.cache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
//@MapperScan指定需要扫描的mapper接口所在的包
@MapperScan("com.springboot.cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
4.2.2 @Cacheable
在service的方法上添加注解@Cacheable
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
System.out.println("从数据库查询"+id+"号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
@Cacheable 的属性:
属性名 | 说明 |
---|---|
cacheNames/value | 指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存; |
key | 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值,编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0] |
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 | 是否使用异步模式,设置为true的时候,unless就会失效。 |
4.3 缓存原理分析
-
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
4.4 自定义缓存Key的生成策略
可以定义一个缓存配置类,往容器中注册一个Bean,里面定义Key的生成规则,到时候就会使用我们的:
package com.springboot.cache.config;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 缓存的配置类
*/
@Configuration
public class MyCacheConfig {
//自定义缓存key的生成策略
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
在@Cacheable注解中使用:
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
4.5 @CachePut
@CachePut:既调用方法,又更新缓存数据;同步更新缓存;修改了数据库的某个数据,同时更新缓存;
运行时机:1、先调用目标方法;2、将目标方法的结果缓存起来
测试步骤:
1、查询1号员工;查到的结果会放在缓存中
2、以后查询还是之前的结果
3、更新1号员工;【lastName:zhangsan;gender:0】,将方法的返回值也放进缓存了;key:传入的employee对象 值:返回的employee对象;
4、查询1号员工?应该是更新后的员工;key = “#employee.id”:使用传入的参数的员工id;key = “#result.id”:使用返回后的id
要保证查询和更新员工,缓存的key要相同,否则,不同key取不到缓存的数据。
@Cacheable的key是不能用#result
keyGenerator = "myKeyGenerator 要注释掉
@Cacheable(cacheNames = "emp"/*,keyGenerator = "myKeyGenerator"*/)
public Employee getEmp(Integer id){
System.out.println("从数据库查询"+id+"号员工");
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
@GetMapping("/emp")
public Employee update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
4.6 @CacheEvict
清楚缓存的作用:
key:指定要清除的数据;
allEntries = true:指定清除这个缓存中所有的数据;
beforeInvocation = false:缓存的清除是否在方法之前执行,默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
/**
* @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;
}
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
4.7 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);
}
@GetMapping("/emp/lastname/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
4.8 @CacheConfig
@CacheConfig(cacheNames=“emp”/,cacheManager = “employeeCacheManager”/) //抽取缓存的公共配置
可以在类上加上该注解:
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {
5、整合Redis
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中;
开发中使用缓存中间件;redis、memcached、ehcache;
我们要想使用Redis作为缓存,只需要导入Redis坐标就可以了;
<!--redis模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
5.1 安装redis
1、安装redis:使用docker;
执行命令:docker pull redis (也可以使用:docker pull registry.docker-cn.com/library/redis,registry.docker-cn.com/library 是docker中国的镜像加速)
查看镜像:docker images
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/redis latest 4cdbec704e47 6 days ago 98.2 MB
docker.io/tomcat jdk8-adoptopenjdk-hotspot afc0ee76aa56 2 weeks ago 324 MB
docker.io/mysql 5.6 c8078e8ab06d 4 weeks ago 303 MB
启动redis,执行命令:docker run -d -p 6379:6379 --name myredis 4cdbec704e47
[root@localhost ~]# docker run -d -p 6379:6379 --name myredis 4cdbec704e47
507e7ec030fb99092c130d93cc5aa559f9107edf34f1270c090e93f7a0412566
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
507e7ec030fb 4cdbec704e47 "docker-entrypoint..." 25 seconds ago Up 24 seconds 0.0.0.0:6379->6379/tcp myredis
[root@localhost ~]#
启动成功了,就可以使用redis的客户端连接工具连接:
5.2 引入redis组件
在pom文件中,添加redis的启动器坐标
<!--redis模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
5.3 配置redis
5.3.1 application.yml
在application.yml中配置redis的主机和端口,192.168.0.108 是我们的虚拟机的ip;
spring:
redis:
host: 192.168.0.108
port: 6379
# Redis数据库索引(默认为0)
database: 1
jedis:
pool:
#连接池最大连接数
max-active: 8
#最小空闲连接
min-idle: 0
#最大阻塞等待时间,负值表示没有限制
max-wait: -1ms
#最大空闲连接
max-idle: 8
#连接超时时间(毫秒)
timeout: 20ms
# 无密码可不写
# password:
5.3.2 自定义RedisTemplate
当我们引入Redis组件之后,SpringBoot会自动帮我们配置RedisTemplate(k-v都是对象的)和StringRedisTemplate(操作k-v都是字符串的),但是RedisTemplate默认使用的是JdkSerializationRedisSerializer,这样序列化的数据就是编码之后的,不易读。所以我们需要自定义RedisTemplate,使用Jackson2JsonRedisSerializer,这样保存对象就不是编码的,是json类型的数据。
package com.springboot.cache.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
* 缓存配置类
* 用于注解的缓存配置,如 @Cacheable 的序列化配置
*
* 说明:
* 我们 redisUtils 序列化方式采用 json序列化
* @Cacheable 默认序列化方式为 二进制的
* 两个不能混用,为了解决这个问题,这里设置 @Cacheable 默认序列化方式为 json
*/
@Configuration
public class MyRedisConfig extends CachingConfigurerSupport {
@Bean("myRedisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//默认是使用JDK的序列化,我们这里要设置使用Jackson的序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
/**
* 设置 redis 数据默认过期时间
* 设置@cacheable 序列化方式,默认是以jdk的二进制序列化的,我们设置成是以json进行序列化的
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
Jackson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(30));
return configuration;
}
}
5.4 redis的自动配置原理
我们引入了redis的启动器之后,Redis的自动配置就生效了,SpringBoot就自动为我们配置RedisTemplate(k-v都是对象的)和StringRedisTemplate(操作k-v都是字符串的);
RedisAutoConfiguration的源码如下:
import java.net.UnknownHostException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean( name = {"redisTemplate"})
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
因为SpringBoot 约定大于配置的特点,只要我们加入了 spring-data-redis 依赖包并配置 Redis 数据库,SpringBoot 就会帮我们自动配置一个 RedisTemplate ,利用它我们就可以按照以下方式操作对应的数据类型,在下面实战中我将会对这五种数据进行操作。
\1. redisTemplate.opsForValue(); //操作字符串
\2. redisTemplate.opsForHash(); //操作hash
\3. redisTemplate.opsForList(); //操作list
\4. redisTemplate.opsForSet(); //操作set
\5. redisTemplate.opsForZSet(); //操作有序set
5.5 EmployeeController
package com.springboot.cache.controller;
import com.springboot.cache.bean.Employee;
import com.springboot.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getById(@PathVariable("id") Integer id){
return employeeService.getEmp(id);
}
@GetMapping("/emp")
public Employee update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
@GetMapping("/emp/lastname/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
}
5.6 测试
启动工程,浏览器访问:http://localhost:8080/emp/1 可以访问到数据,再去redis的客户端连接工具查看,发现员工信息也存入了redis中,并且是以json进行序列化的:
也可以通过测试类,手动的往redis中存取数据:
package com.springboot.cache;
import com.springboot.cache.bean.Employee;
import com.springboot.cache.mapper.EmployeeMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot01CacheApplicationTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
EmployeeMapper employeeMapper;
@Autowired
RedisTemplate myRedisTemplate;
@Test
public void contextLoads() {
myRedisTemplate.opsForValue().set("zhoujie","中介家里就可怜见");
}
//测试stringRedisTemplate保存对象
@Test
public void test01(){
// Employee empById = employeeMapper.getEmpById(1);
// String empStr = JSONObject.toJSONString(empById);
// stringRedisTemplate.opsForValue().append("empstr",empStr);
String empstr = stringRedisTemplate.opsForValue().get("empstr");
Employee employee = JSON.parseObject(empstr, Employee.class);
System.out.println(employee);
}
//测试保存对象
@Test
public void test02(){
Employee empById = employeeMapper.getEmpById(1);
Employee empById2 = employeeMapper.getEmpById(2);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
List<Employee> list = new ArrayList<>();
list.add(empById);
list.add(empById2);
//myRedisTemplate.opsForValue().set("emp-02",empById);
//myRedisTemplate.opsForList().leftPush("empList2",list);
Object empList2 = myRedisTemplate.opsForList().leftPop("empList2");
System.out.println(empList2);
}
}