1.Redis与SpringBoot搭建
1.1 介绍
Redis 是开源免费, key-value 内存数据库,主要解决高并发、大数据场景下,热点数据访问的性能问题,提供高性能的数据快速访问。项目中部分数据访问比较频繁,对下游 DB(例如 MySQL)造成服务压力,这时候可以使用缓存来提高效率。
Redis 的主要特点包括:
- Redis数据存储在内存中,可以提高热点数据的访问效率
- Redis 除了支持 key-value 类型的数据,同时还支持其他多种数据结构的存储;
- Redis 支持数据持久化存储,可以将数据存储在磁盘中,机器重启数据将从磁盘重新加载数据;
Redis 作为缓存数据库和 MySQL 这种结构化数据库进行对比。
- 从数据库类型上,Redis 是 NoSQL 半结构化缓存数据库, MySQL 是结构化关系型数据库;
- 从读写性能上,MySQL 是持久化硬盘存储,读写速度较慢, Redis 数据存储读取都在内存,同时也可以持久化到磁盘,读写速度较快;
- 从使用场景上,Redis 一般作为 MySQL 数据读取性能优化的技术选型,彼此配合使用。Redis用于存储热数据或者缓存数据,并不存在相互替换的关系。
1.2 环境搭建
本地启动一个docker环境
1.本地启动一个
2.用工具连接查看
springboot配置
1.pom主要配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.yml配置
server:
port: 8887
spring:
redis:
database: 0
host: 192.168.232.130
port: 6379
timeout: 2000s
password: 123456
3.代码
@RunWith(SpringJUnit4ClassRunner.class)
//需要指明当前启动类,加载上下文环境
@SpringBootTest(classes = AppMain.class)
@Slf4j
public class PersonRedisTest {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Test
public void testRedis1(){
String helloRedis = (String)redisTemplate.opsForValue().get("helloRedis");
if(StringUtils.isEmpty(helloRedis)){
helloRedis = "helloRedis";
redisTemplate.opsForValue().set(helloRedis,"你好 ,redis!");
log.info("第一次访问,缓存没有值,则直接存进去");
}
}
}
4.运行
2.Redis数据类型
2.1 String
2.2 Hash
2.3 List
2.4 Set
2.5 zSet
2.6 Sorted set
3.Redis模板类
RedisTemplate和StringRedisTemplate的区别:
- 两者的关系是StringRedisTemplate
继承
RedisTemplate。 - 两者的
数据是不共通
的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。 - SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
StringRedisTemplate默认采用的是String的序列化策略(StringRedisSerializer)
,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略(JdkSerializationRedisSerializer )
,保存的key和value都是采用此策略序列化保存的。
redisTemplate有两个方法经常用到,一个是opsForXXX一个是boundXXXOps,XXX是value的类型,前者获取到一个Opercation,但是没有指定操作的key,可以在一个连接(事务)内操作多个key以及对应的value;后者会获取到一个指定了key的operation,在一个连接内只操作这个key对应的value.
3.1 StringRedisTemplate
3.1.1 简单例子
@Resource
private StringRedisTemplate stringRedisTemplate;
@Test
public void testRedisStr(){
stringRedisTemplate.opsForValue().set("player:str强","str demo烈",60, TimeUnit.SECONDS);
log.info("suc!");
}
其他用法参考下面RedisTemplate
3.2 RedisTemplate
RedisTemplate 的封装使我们能够更方便的进行redis数据操作,比直接使用Jedis或者Lettuce的java SDK要方便很多。
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作 hash
redisTemplate.opsForList();//操作 list
redisTemplate.opsForSet();//操作 set
redisTemplate.opsForZSet();//操作有序 set
3.2.1 简单举例
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@Builder
public class Person implements Serializable {
private static final long serialVersionUID = 5919073407158198332L;
private Integer pid;
private String pName;
}
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Test
public void testRedisObj(){
Person person = Person.builder().pid(1).pName("xiaoming啊").build();
redisTemplate.opsForValue().set("player:obj啊",person,60, TimeUnit.SECONDS);
log.info("suc!");
}
注意,Person要保存到redis里,一定要实现序列化接口,不然会报
org.springframework.data.redis.serializer.SerializationException
错误
其实这个不是严格意义上的乱码,是JDK的二进制序列化之后的存储方式。人看不懂,但是程序是能看懂的。
3.2.2 解决“乱码”
- 采用StringRedisSerializer对key进行序列化(字符串格式)
- 采用Jackson2JsonRedisSerializer对value将进行序列化(JSON格式)
乱码问题的症结在于对象的序列化问题:RedisTemplate默认使用的是JdkSerializationRedisSerializer(二进制存储),StringRedisTemplate默认使用的是StringRedisSerializer(redis字符串格式存储)。
序列化方式对比:
JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。而且是以二进制形式保存,自然人无法理解。
Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。似乎没啥缺点。
StringRedisSerializer序列化之后的结果,自然人也是可以理解,但是value只能是String类型,不能是Object。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//重点在这四行代码
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
重新执行保存对象的代码,得到结果是:JSON对象
3.2.3 使用redisTemplate存取redis各种数据类型
下面的各种数据类型操作的api和redis命令行api的含义几乎是一致的。
@Resource(name = "redisTemplate")
private ValueOperations<String,Object> valueOperations; //以redis string类型存取Java Object(序列化反序列化)
@Resource(name = "redisTemplate")
private HashOperations<String, String, Object> hashOperations; //以redis的hash类型存储java Object
@Resource(name = "redisTemplate")
private ListOperations<String, Object> listOperations; //以redis的list类型存储java Object
@Resource(name = "redisTemplate")
private SetOperations<String, Object> setOperations; //以redis的set类型存储java Object
@Resource(name = "redisTemplate")
private ZSetOperations<String, Object> zSetOperations; //以redis的zset类型存储java Object
@Test
public void testValueObj() {
Person person = new Person("boke","byrant");
person.setAddress(new Address("南京","中国"));
//向redis数据库保存数据(key,value),数据有效期20秒
valueOperations.set("player:1",person,20, TimeUnit.SECONDS); //20秒之后数据消失
//根据key把数据取出来
Person getBack = (Person)valueOperations.get("player:1");
System.out.println(getBack);
}
@Test
public void testSetOperation() {
Person person = new Person("kobe","byrant");
Person person2 = new Person("curry","stephen");
setOperations.add("playerset",person,person2); //向Set中添加数据项
//members获取Redis Set中的所有记录
Set<Object> result = setOperations.members("playerset");
System.out.println(result); //包含kobe和curry的数组
}
@Test
public void HashOperations() {
Person person = new Person("kobe","byrant");
//使用hash的方法存储对象数据(一个属性一个属性的存,下节教大家简单的方法)
hashOperations.put("hash:player","firstname",person.getFirstname());
hashOperations.put("hash:player","lastname",person.getLastname());
hashOperations.put("hash:player","address",person.getAddress());
//取出一个对象的属性值,有没有办法一次将整个对象取出来?有,下节介绍
String firstName = (String)hashOperations.get("hash:player","firstname");
System.out.println(firstName); //kobe
}
@Test
public void ListOperations() {
//将数据对象放入队列
listOperations.leftPush("list:player",new Person("kobe","byrant"));
listOperations.leftPush("list:player",new Person("Jordan","Mikel"));
listOperations.leftPush("list:player",new Person("curry","stephen"));
//从左侧存,再从左侧取,所以取出来的数据是后放入的curry
Person person = (Person) listOperations.leftPop("list:player");
System.out.println(person); //curry对象
}
3.2.4 String操作
1.赋值方式
//1、通过redisTemplate设置值
redisTemplate.boundValueOps("StringKey11").set("StringValue");
redisTemplate.boundValueOps("StringKey12").set("StringValue",30, TimeUnit.SECONDS);
//2、通过BoundValueOperations设置值
BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey22");
stringKey.set("StringVaule");
stringKey.set("StringValue",30, TimeUnit.SECONDS);
//3、通过ValueOperations设置值
ValueOperations ops = redisTemplate.opsForValue();
ops.set("StringKey31", "StringVaule");
ops.set("StringKey32","StringVaule",30, TimeUnit.SECONDS);
2.设置过期时间(单独设置)
redisTemplate.boundValueOps("StringKey11").expire(20,TimeUnit.SECONDS);
redisTemplate.expire("StringKey31",20,TimeUnit.SECONDS);
3.获取值
//1、通过redisTemplate设置值
String str1 = (String) redisTemplate.boundValueOps("StringKey").get();
System.out.println("str1 = " + str1);
//2、通过BoundValueOperations获取值
BoundValueOperations stringKey = redisTemplate.boundValueOps("StringKey");
String str2 = (String) stringKey.get();
System.out.println("str2 = " + str2);
//3、通过ValueOperations获取值
ValueOperations ops = redisTemplate.opsForValue();
String str3 = (String) ops.get("StringKey");
System.out.println("str3 = " + str3);
4.删除值
Boolean result = redisTemplate.delete("StringKey");
5.递增or递减
ValueOperations ops = redisTemplate.opsForValue();
ops.set("StringKey1",1);
//递增 递减(-6L)
redisTemplate.boundValueOps("StringKey1").increment(6L);
3.2.5 hash操作
1.新增
//1、通过redisTemplate设置值
redisTemplate.boundHashOps("HashKey11").put("SmallKey", "HashVaue");
//2、通过BoundValueOperations设置值
BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey21");
hashKey.put("SmallKey", "HashVaue");
//3、通过ValueOperations设置值
HashOperations hashOps = redisTemplate.opsForHash();
hashOps.put("HashKey31", "SmallKey", "HashVaue");
hashOps.put("HashKey31", "SmallKey2", "HashVaue2");
//4.添加集合方式
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("SmallKey1","HashVaue1");
hashMap.put("SmallKey2","HashVaue2");
hashMap.put("SmallKey3","HashVaue3");
redisTemplate.boundHashOps("HashKey41").putAll(hashMap );
2.查询key
//1、通过redisTemplate获取值
Set keys1 = redisTemplate.boundHashOps("HashKey41").keys();
//2、通过BoundValueOperations获取值
BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
Set keys2 = hashKey.keys();
//3、通过ValueOperations获取值
HashOperations hashOps = redisTemplate.opsForHash();
Set keys3 = hashOps.keys("HashKey41");
3.查询value
//1、通过redisTemplate获取值
List values1 = redisTemplate.boundHashOps("HashKey41").values();
//2、通过BoundValueOperations获取值
BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
List values2 = hashKey.values();
//3、通过ValueOperations获取值
HashOperations hashOps = redisTemplate.opsForHash();
List values3 = hashOps.values("HashKey41");
4.根据key获取value
//1、通过redisTemplate获取
String value1 = (String) redisTemplate.boundHashOps("HashKey41").get("SmallKey3");
//2、通过BoundValueOperations获取值
BoundHashOperations hashKey = redisTemplate.boundHashOps("HashKey41");
String value2 = (String) hashKey.get("SmallKey3");
//3、通过ValueOperations获取值
HashOperations hashOps = redisTemplate.opsForHash();
String value3 = (String) hashOps.get("HashKey41", "SmallKey3");
其他方式,后续自己可以再试试
3.2.6 set操作
1.新增
//1、通过redisTemplate设置值
redisTemplate.boundSetOps("setKey").add("setValue1", "setValue2");
//2、通过BoundValueOperations设置值
BoundSetOperations setKey = redisTemplate.boundSetOps("setKey");
setKey.add("setValue3","setValue2");
//3、通过ValueOperations设置值
SetOperations setOps = redisTemplate.opsForSet();
setOps.add("setKey", "SetValue4", "setValue5", "setValue6");
2.查询
//1.取出一个元素值
SetOperations setOps = redisTemplate.opsForSet();
Object setKey = setOps.pop("setKey");
System.out.println("setKey = " + setKey);
//2.是否存在
Boolean isEmpty = redisTemplate.boundSetOps("setKey").isMember("setValue2");
System.out.println(isEmpty);
其他操作后续介绍
3.2.7 list操作
1.新增
//1、通过redisTemplate设置值
redisTemplate.boundListOps("listKey").leftPush("listLeftValue1");
redisTemplate.boundListOps("listKey").rightPush("listRightValue2");
//2、通过BoundValueOperations设置值
BoundListOperations listKey = redisTemplate.boundListOps("listKey");
listKey.leftPush("listLeftValue3");
listKey.rightPush("listRightValue4");
//3、通过ValueOperations设置值
ListOperations opsList = redisTemplate.opsForList();
opsList.leftPush("listKey", "listLeftValue5");
opsList.rightPush("listKey", "listRightValue6");
//4.集合插入
ArrayList<String> list = new ArrayList<>();
list.add("listLeftValue7");
list.add("listLeftValue8");
redisTemplate.boundListOps("listKey").rightPushAll(list);
redisTemplate.boundListOps("listKey").leftPushAll(list);
2.查询
//获取List缓存全部内容(起始索引,结束索引)
List listKey1 = redisTemplate.boundListOps("listKey").range(0, 3);
Object listKey = redisTemplate.boundListOps("listKey").leftPop();//从左侧弹出一个元素
Object listKey2 = redisTemplate.boundListOps("listKey").rightPop();//从右侧弹出一个元素
3.2.8 zset操作
后续再看
4.Redis与缓存
4.1 为什么要做缓存
-
提升性能
绝大多数情况下,关系型数据库select查询是出现性能问题最大的地方。一方面,select 会有很多像 join、group、order、like 等这样丰富的语义,而这些语义是非常耗性能的;另一方面,大多数应用都是读多写少,所以加剧了慢查询的问题。
分布式系统中远程调用也会耗很多性能,因为有网络开销,会导致整体的响应时间下降。为了挽救这样的性能开销,在业务允许的情况(不需要太实时的数据)下,使用缓存是非常必要的事情。 -
缓解数据库压力
当用户请求增多时,数据库的压力将大大增加,通过缓存能够大大降低数据库的压力。
4.2 缓存一致性操作
- 更新写数据:先把数据存到数据库中,然后再让缓存失效或更新。缓存操作失败,数据库事务回滚。
- 删除写数据: 先从数据库里面删掉数据,再从缓存里面删掉。缓存操作失败,数据库事务回滚。
- 查询读数据
– 缓存命中:先去缓存 cache 中取数据,取到后返回结果。
– 缓存失效:应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,在将数据放到缓存中。
如果上面的这些更新、删除、查询操作流程全都由程序员通过编码来完成的话
- 因为加入缓存层,程序员的编码量大大增多
- 缓存层代码和业务代码耦合,造成难以维护的问题。
4.3 整合Spring Cache
Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。关于 Jedis 跟 Lettuce 的区别如下:
- Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
- Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
这里重新进行配置一遍
1.添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--spring data jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.添加入口启动类 @EnableCaching 注解开启 Caching(在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager)
),Redis(因为我们之前引入了Redis,所以使用redis作为缓存),实例如下。
@SpringBootApplication
@EnableCaching
public class AppMain {
public static void main(String[] args) {
SpringApplication.run(AppMain.class,args);
}
}
3.yml配置
server:
port: 8887
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.232.131:6033/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# JPA配置
jpa:
hibernate:
ddl-auto: update
# show-sql: true
# formatSQL得这样写
# properties:
# hibernate:
# format_sql: true
redis:
database: 0
host: 192.168.232.131
port: 6379
timeout: 2000s
password: 123456
4.redis配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//重点在这四行代码
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//本节的重点配置,让Redis缓存的序列化方式使用redisTemplate.getValueSerializer()
//不在使用JDK默认的序列化方式
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
}
5.主要类
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@ToString
@Builder
@Entity
@Table(name="tb_person")
public class Person {
@Id
@GeneratedValue
private Integer pId;
private String pName;
}
public interface PersonRepository extends JpaRepository<Person,Integer> {
}
@Service
@Slf4j
public class PersonReidsServiceImpl implements PersonReidsService {
@Autowired
PersonRepository personRepository;
@Cacheable(value="person")
@Override
public Person getPerson(Integer pid) {
log.info("*************getPerson**************");
return personRepository.findById(pid).orElse(null);
}
@Override
public void addPerson(Person person) {
log.info("*************addPerson**************");
personRepository.save(person);
}
@Override
public void updatePerson(Person person) {
log.info("*************updatePerson**************");
personRepository.save(person);
}
@Override
public void deletePerson(Integer pid) {
log.info("*************deletePerson**************");
personRepository.deleteById(pid);
}
}
@RestController
@RequestMapping("/demo")
public class PersonController {
@Autowired
PersonReidsService personReidsService;
@RequestMapping("/person/{pid}")
public Person getPerson(@PathVariable Integer pid) {
return personReidsService.getPerson(pid);
}
@PostMapping("/person")
public String addPerson(@RequestBody Person person) {
personReidsService.addPerson(person);
return "addPerson suc!";
}
@PutMapping("/person")
public String updatePerson(@RequestBody Person person) {
personReidsService.updatePerson(person);
return "updatePerson suc!";
}
@DeleteMapping("/person/{pid}")
public String delPerson(@PathVariable Integer pid) {
personReidsService.deletePerson(pid);
return "delPerson suc!";
}
}
6.执行
查询第一次,会从数据库里读取数据,第二次后,直接从缓存里读取,因为第一次查询后,就会把结果存放到缓存里,第二次直接从缓存里获取
下面把其他注解用上
/**
* Cacheable:表示第一次查询的时候,先从数据库查询,然后再把数据放到
* 缓存中,第二次就直接从缓存中获取
* @param pid
* @return
*/
@Cacheable(value="person")
@Override
public Person getPerson(Integer pid) {
log.info("*************getPerson**************");
return personRepository.findById(pid).orElse(null);
}
/**
* CachePut:新增和修改,都会调用数据库,然后把数据更新到缓存
* 当再次查询的时候,只要缓存中有,则直接从缓存中拿取
* @param person
*/
@CachePut(value = "person",key="#person.pId")
@Override
public void addPerson(Person person) {
log.info("*************addPerson**************");
personRepository.save(person);
}
@CachePut(value = "person",key="#person.pId")
@Override
public void updatePerson(Person person) {
log.info("*************updatePerson**************");
personRepository.save(person);
}
/**
* CacheEvict:当删除一个数据时,这个会把数据库的那条数据删除掉
* 同时,也把缓存中的那条数据也给删除掉
* @param pid
*/
@CacheEvict(value = "person",key = "#pid")
@Override
public void deletePerson(Integer pid) {
log.info("*************deletePerson**************");
personRepository.deleteById(pid);
}
4.4 缓存注解说明
4.4.1 @Cacheable
通常应用到读取数据的方法上,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据查询结果添加到缓存中。如果缓存中查找到数据,被注解的方法将不会执行。
- value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
- key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。我们这里先来看看自定义策略,至于默认策略会在后文单独介绍。
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。
@Cacheable(value=“users”, key="#id")
public User find(Integer id)
@Cacheable(value=“users”, key="#p0")
public User find(Integer id)
4.4.2 @CachePut
通常应用于保存和修改方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发被注解方法的调用。
4.4.3 @CachEvict
通常应用于删除方法配置,能够根据一定的条件对缓存进行清空。可以清除一条或多条缓存。
4.4.4 @Caching
如果有两种不同的需求,都是放在同一个方法上,这种需求如果只是使用@CacheEvict或者@CachePut是无法实现,因为他们不能多样化的作用在同一个方法上。可以使用@Caching(evict={@CacheEvict(“a1”),@CacheEvict(“a2”,allEntries=true)})
@Caching(
cacheable = {
@Cacheable(value = "emp", key = "#lastName")
},
put = { //更新缓存可以通过id,email或者lastName进行key值查找。
@CachePut(value = "emp", key = "#result.id"),
@CachePut(value = "emp", key = "#result.email"),
@CachePut(value = "emp", key = "#result.lastName"),
}
)
public Employee getEmpByLastName(String lastName) {
System.out.println("要查询的用户名为:" + lastName);
return employeeMapper.getEmpBylastName(lastName);
}
4.4.5 失效时间配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//重点在这四行代码
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//本节的重点配置,让Redis缓存的序列化方式使用redisTemplate.getValueSerializer()
//不在使用JDK默认的序列化方式
// @Bean
// public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
// RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
// RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
// return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
// }
//自定义redisCacheManager
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,
this.buildRedisCacheConfigurationWithTTL(redisTemplate,RedisCacheConfiguration.defaultCacheConfig().getTtl().getSeconds()), //默认的redis缓存配置
this.getRedisCacheConfigurationMap(redisTemplate)); //针对每一个cache做个性化缓存配置
return redisCacheManager;
}
//配置注入,key是缓存名称,value是缓存有效期
private Map<String,Long> ttlmap;
//根据ttlmap的属性装配结果,个性化RedisCacheConfiguration
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(RedisTemplate redisTemplate) {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
ttlmap = new HashMap<>();
ttlmap.put("person",20L);
for(Map.Entry<String, Long> entry : ttlmap.entrySet()){
String cacheName = entry.getKey();
Long ttl = entry.getValue();
redisCacheConfigurationMap.put(cacheName,this.buildRedisCacheConfigurationWithTTL(redisTemplate,ttl));
}
return redisCacheConfigurationMap;
}
//让缓存的序列化方式使用redisTemplate.getValueSerializer(),并未每一个缓存分别设置ttl
private RedisCacheConfiguration buildRedisCacheConfigurationWithTTL(RedisTemplate redisTemplate,Long ttl){
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
.entryTtl(Duration.ofSeconds(ttl));
}
}
5.其他
5.1 参考
完整SpringBoot Cache整合redis缓存
springboot整合spring @Cache和Redis