redis的安装配置这里直接略过,可直接寻找其他安装教程,下载地址github最新版即可
对于SpringBoot整合redis来说,最重要的莫过于RedisTemplate和StringRedisTemplate了
简单来说:
当你的redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。
在前一篇创建的项目基础上,我们来在config包下创建CommonConfig.java来配置redisTemple的序列化。
package com.zwx.middleware.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class CommonConfig {
//Redis链接工厂
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 缓存redis-redisTemplate
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//TODO:指定大key序列化策略为为String序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
//TODO:指定hashKey序列化策略为String序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// // key采用String的序列化方式
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// // hash的key也采用String的序列化方式
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// // value序列化方式采用jackson
// redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// // hash的value序列化方式采用jackson
// redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
//redisTemplate.setHashValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
/**
* 缓存redis-stringRedisTemplate
* @return
*/
@Bean
public StringRedisTemplate stringRedisTemplate(){
//采用默认配置即可-后续有自定义配置时则在此处添加即可
StringRedisTemplate stringRedisTemplate=new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
return stringRedisTemplate;
}
}
下面简单的用两个案例需求实战测试一下
(1)采用RedisTemplate将-字符串信息写入缓存中
(2)采用RedisTemplate将-对象信息序列化为Json格式
对于这两个需求我们借助RedisTemplate的ValueOperations操作组件进行操作
test下创建测试对象RedisTest
package com.zwx.middleware;
import lombok.extern.slf4j.Slf4j;
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.ValueOperations;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@Slf4j
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void one(){
log.info("----开始RedisTemplate操作组件实战----");
//定义字符串内容以及存入缓存的key
final String content="RedisTemplate实战字符串信息";
final String key="redis:template:one:string";
//Redis通用的操作组件
ValueOperations valueOperations = redisTemplate.opsForValue();
//将字符串信息写入缓存中
log.info("写入缓存中的内容:{}",content);
valueOperations.set(key,content);
//从缓存中读取内容
Object result = valueOperations.get(key);
log.info("读取出来的内容:{}",result);
}
}
对于第二个案例,我们需要构建一个对象,并且采用Jackson序列化框架进行序列化反序列化,为此需要借助ObjectMapper组件提供的Jackson序列化框架(Json序列化框架的一种)。
@Test
public void two() throws JsonProcessingException {
log.info("----开始RedisTemplate操作组件实战----");
//构造对象信息
User user = new User(1, "debug", "zwx");
//Redis通用的操作组件
ValueOperations valueOperations = redisTemplate.opsForValue();
//定义字符串内容以及存入缓存的key
final String key="redis:template:two:object";
final String content=objectMapper.writeValueAsString(user);
valueOperations.set(key,content);
log.info("写入缓存对象的信息:{}",user);
//从缓存中读取内容
Object result = valueOperations.get(key);
if (result!=null){
User resultUser = objectMapper.readValue(result.toString(), User.class);
log.info("读取缓存内容并反序列化后的结果:{}",resultUser);
}
}
@Autowired
private StringRedisTemplate stringRedisTemplate;
//采用StringRedisTemplate将一字符串信息写入缓存中,并读取出来
@Test
public void three(){
log.info("------开始StringRedisTemplate操作组件实战----");
//定义字符串内容以及存入缓存的key
final String content="StringRedisTemplate实战字符串信息";
final String key="redis:three";
//Redis通用的操作组件
ValueOperations valueOperations=stringRedisTemplate.opsForValue();
//将字符串信息写入缓存中
log.info("写入缓存中的内容:{} ",content);
valueOperations.set(key,content);
//从缓存中读取内容
Object result=valueOperations.get(key);
log.info("读取出来的内容:{} ",result);
}
//采用StringRedisTemplate将一对象信息序列化为Json格式字符串后写入缓存中,
//然后将其读取出来,最后反序列化解析其中的内容并展示在控制台
@Test
public void four() throws Exception{
log.info("------开始StringRedisTemplate操作组件实战----");
//构造对象信息
User user=new User(2,"SteadyJack","zwx");
//Redis通用的操作组件
ValueOperations valueOperations=redisTemplate.opsForValue();
//将序列化后的信息写入缓存中
final String key="redis:four";
final String content=objectMapper.writeValueAsString(user);
valueOperations.set(key,content);
log.info("写入缓存对象的信息:{} ",user);
//从缓存中读取内容
Object result=valueOperations.get(key);
if (result!=null){
User resultUser=objectMapper.readValue(result.toString(),User.class);
log.info("读取缓存内容并反序列化后的结果:{} ",resultUser);
}
}
package com.zwx.middleware.entity;
import lombok.Data;
@Data
public class Book {
private Integer bookNo;
private String name;
}
package com.zwx.middleware.entity;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
public class Fruit implements Serializable{
private String name;
private String color;
public Fruit() {
}
public Fruit(String name, String color) {
this.name = name;
this.color = color;
}
}
package com.zwx.middleware.entity;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* 用户个人信息实体
**/
@Data
@ToString
public class Person implements Serializable{
private Integer id;
private Integer age;
private String name;
private String userName;
private String location;
public Person() {
}
public Person(Integer id, Integer age, String name, String userName, String location) {
this.id = id;
this.age = age;
this.name = name;
this.userName = userName;
this.location = location;
}
}
package com.zwx.middleware.entity;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* 用户充值记录
**/
@Data
@ToString
public class PhoneUser implements Serializable{
private String phone;
private Double fare;
public PhoneUser() {
}
public PhoneUser(String phone, Double fare) {
this.phone = phone;
this.fare = fare;
}
//手机号相同,代表充值记录重复(只适用于特殊的排名需要)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneUser phoneUser = (PhoneUser) o;
return phone != null ? phone.equals(phoneUser.phone) : phoneUser.phone == null;
}
@Override
public int hashCode() {
return phone != null ? phone.hashCode() : 0;
}
}
package com.zwx.middleware.entity;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
@Data
@ToString
public class Student implements Serializable{
private String id;
private String userName;
private String name;
public Student() {
}
public Student(String id, String userName, String name) {
this.id = id;
this.userName = userName;
this.name = name;
}
}
创建一个RedisTest2
//定义RedisTemplate操作
@Autowired
private RedisTemplate redisTemplate;
//Json序列化与反序列化框架类
@Autowired
private ObjectMapper objectMapper;
@Test
public void one() throws JsonProcessingException {
//构造用户实体对象
Person person = new Person(10013,23,"zwx","debug","厦门");
//定义key与即将存入缓存的value
final String key="redis:test:1";
String value=objectMapper.writeValueAsString(person);
//写入缓存
log.info("存入缓存中的用户实体对象信息:{}",person);
redisTemplate.opsForValue().set(key,value);
//从缓存中获取用户实体信息
Object res = redisTemplate.opsForValue().get(key);
if (res!=null){
Person resP = objectMapper.readValue(res.toString(), Person.class);
log.info("从缓存中读取信息:{}",resP);
}
}
Redis的列表类和java的List很类似,都用于储存一系列具有相同类型的数据,其底层对于数据的存储和读取可以理解为一个“数据队列。
@Test
public void two() {
//构造已经排好序的用户对象列表
List<Person> list=new ArrayList<>();
list.add(new Person(1,21,"zwx","debug","厦门1"));
list.add(new Person(2,22,"zjn","debug","厦门2"));
list.add(new Person(3,23,"zx","debug","厦门3"));
log.info("构造已经排好序的用户对象列表:{}",list);
//将列表数据存储至Redis的List中
final String key="redis:test:2";
ListOperations listOperations = redisTemplate.opsForList();
for (Person person : list) {
listOperations.leftPush(key,person);
}
//获取Redis中List的数据(从队头中遍历获取,直到没有元素为止)
log.info("--获取Redis中List的数据-从对头中获取--");
Object o = listOperations.rightPop(key);
Person resP;
while (o!=null){
resP= (Person) o;
log.info("当前数据:{}" ,resP);
o=listOperations.rightPop(key);
}
}
用于储存不重复的数据,其底层是通过哈希表来实现的
@Test
public void three(){
//构造一组用户姓名列表
List<String> useList=new ArrayList<>();
useList.add("debug");
useList.add("debug1");
useList.add("debug2");
useList.add("debug");
useList.add("debug4");
useList.add("debug2");
useList.add("debug6");
useList.add("debug6");
log.info("待处理的用户姓名列表:{}",useList);
//遍历访问,提出相同姓名的用户写入集合,存入缓存
final String key="redis:test:3";
SetOperations setOperations = redisTemplate.opsForSet();
for (String s : useList) {
setOperations.add(key,s);
}
Object o = setOperations.pop(key);
while (o!=null){
log.info("当前数据:{}" ,o);
o=setOperations.pop(key);
}
}
@Test
public void four(){
//构造一组无序的用户手机充值对象列表
List<PhoneUser> list=new ArrayList<>();
list.add(new PhoneUser("103",130.0));
list.add(new PhoneUser("101",120.0));
list.add(new PhoneUser("102",80.0));
list.add(new PhoneUser("105",70.0));
list.add(new PhoneUser("106",50.0));
list.add(new PhoneUser("104",150.0));
log.info("构造一组无序的用户手机充值对象列表:{}",list);
final String key="redis:test:4";
//因为ZSet在add元素进入缓存后,下次就不能更新了,故而为了测试方便
//进行操作之前先清空该缓存(当然生产环境中不建议这么使用)
redisTemplate.delete(key);
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
for (PhoneUser phoneUser : list) {
zSetOperations.add(key,phoneUser,phoneUser.getFare());
}
//获取排名靠前的用户列表
Long size = zSetOperations.size(key);
//从小到大排序
Set range = zSetOperations.range(key, 0L, size);
//从大到小
// Set range2 = zSetOperations.range(key, size, 0L);
for (Object o : range) {
log.info("从小到大排序:{}",o);
}
// for (Object o : range2) {
// log.info("从大到小排序:{}",o);
// }
}
Redis的哈希存储和java的HashMap数据类型有点像,他的底层数据类型是由Key-Value组成的映射表。
@Test
public void five() {
ArrayList<Student> students = new ArrayList<>();
ArrayList<Fruit> fruits = new ArrayList<>();
students.add(new Student("10010", "debug", "aaa"));
students.add(new Student("10011", "jack", "bbb"));
students.add(new Student("10012", "sam", "ccc"));
fruits.add(new Fruit("apple", "红色"));
fruits.add(new Fruit("orange", "橙色"));
fruits.add(new Fruit("banner", "黄色"));
final String sKey = "redis:test:5";
final String fKey = "redis:test:6";
HashOperations hashOperations = redisTemplate.opsForHash();
for (Student student : students) {
hashOperations.put(sKey, student.getId(), student);
}
for (Fruit fruit : fruits) {
hashOperations.put(fKey, fruit.getName(), fruit);
}
Map sMap = hashOperations.entries(sKey);
log.info("获取学生对象列表:{}",sMap);
Map fMap = hashOperations.entries(fKey);
log.info("获取水果对象列表:{}",fMap);
String sFiled="10012";
Student s = (Student) hashOperations.get(sKey, sFiled);
log.info("获取指定的学生对象:{} -> {}",sFiled,s);
String fFiled="orange";
Fruit f = (Fruit) hashOperations.get(fKey, fFiled);
log.info("获取指定的水果对象:{} -> {}",fFiled,f);
}
由于Redis本质上一个基于内存、Key-Value存储的数据库,故而不管采用何种数据类型存储数据,都需要提供一个Key。
然而在某些业务场景下,缓存中的Key对应的数据信息并不需要永久保留,这个时候我们就需要对缓存中的这些key进行清理。在Redis缓存体系结构中,Delete与Expire操作都可以用于清理缓存中的key,这两者不同之处在于Delete操作需要手动触发,而Expire需要提供过期时间。
首先是第一种方法:在调用SETEX()方法中指定Key的过期时间,代码如下。
@Test
public void six() throws InterruptedException {
final String key1 = "redis:test:6";
ValueOperations valueOperations = redisTemplate.opsForValue();
//第一种方法:在向缓存中放置数据时,提供一个TTL,表示TTL一到,缓存中的Key将自动失效,在这里是10s
valueOperations.set(key1,"expire操作",10L,TimeUnit.SECONDS);
//等待5s,判断key是否还存在
Thread.sleep(5000);
Boolean existKey1 = redisTemplate.hasKey(key1);
Object value = valueOperations.get(key1);
log.info("等待5s-判断key是否还存在:{}对应值:{}",existKey1,value);
//再等待5s
Thread.sleep(5000);
existKey1 = redisTemplate.hasKey(key1);
value = valueOperations.get(key1);
log.info("再等待5s-判断key是否还存在:{}对应值:{}",existKey1,value);
}
上述代码可以看出当缓存中key失效的时候,对应的值将不存在,获取的是null
另外一种是采用RedisTemplate操作组件中的Expire()方法:
@Test
public void seven() throws InterruptedException {
final String key2="redis:test:7";
ValueOperations valueOperations = redisTemplate.opsForValue();
//第二种方法:在往缓存中放置数据后,采用RedisTemplate的Expire()方法
valueOperations.set(key2,"expire操作 -2");
redisTemplate.expire(key2,10L,TimeUnit.SECONDS);
//等待5s,判断key是否还存在
Thread.sleep(5000);
Boolean existKey2 = redisTemplate.hasKey(key2);
Object value = valueOperations.get(key2);
log.info("等待5s-判断key是否还存在:{}对应值:{}",existKey2,value);
//再等待5s
Thread.sleep(5000);
existKey2 = redisTemplate.hasKey(key2);
value = valueOperations.get(key2);
log.info("再等待5s-判断key是否还存在:{}对应值:{}",existKey2,value);
}
"判断失效缓存中的key是否存在"在实际业务场景下是很常用的,最常见的方法包括以下两种。
(1)将数据库查询得到的数据缓存一定的TTL,在TTL内前端查询访问数据列表的时候,只需要在缓存中查询即可,从而减轻数据库的查询压力。
(2)将数据压入缓存队列中,并设置一定的TTL,当触发监听时间,将处理相应的业务逻辑。