Redis 介绍
Redis 是目前业界使用最广泛的内存数据存储。相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets ,String等,
同时支持数据持久化。除此之外,Redis 还提供一些类数据库的特性,比如事务,HA,主从库。可以说 Redis 兼具了缓存系统和数据库的一些特性,
因此有着丰富的应用场景。本文介绍 Redis 在 Spring Boot 中两个典型的应用场景。
搭建Redis服务端
下载redis文件 我这里用的是 Redis-x64-3.2.100
下载成功解压进入文件后,在路径框上输入cmd 进入命令行操作
输入redis-server.exe redis.windows.conf 即启动成功
springboot整合Redis
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 开启web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,spring-boot-starter-data-redis依赖于spring-data-redis 和 lettuce 。
Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。
Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。
2、添加配置
#Redis
#Redis数据库索引(默认为0)
redis:
database: 0
# Redis服务器地址
host: localhost
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
lettuce:
pool:
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
3、添加 cache 的配置类
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
注意我们使用了注解:@EnableCaching来开启缓存。
4、实体类省略 自行创建 此处我用的实体类是Style
@Entity
public class Style implements Serializable {
@Id
@GeneratedValue(strategy=IDENTITY)
private int id;
@Column(length = 32)
private String style;
private Double money;
private String facilities;
private String image;
private String message;
.....get()+set()方法..........
此处实体类要继承implements Serializable 因为redis保存对象需要把对象数据转换成字节数组才可操作
5、单元测试代码
package com.hotel.demo;
import com.hotel.demo.bean.Style;
import org.junit.Assert;
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.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.*;
import java.util.concurrent.TimeUnit;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest()
public class DemoApplicationTests {
// RedisTemplate是操作实体对象存储到redis,即操作的对象要转换成字节数组
// StringRedisTemplate操作的是字符串然后数据在redis中可读的数据,操作非字节数组
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
@Test
public void redisTest() throws InterruptedException {
/* TimeUnit.SECONDS(5)线程等待五秒
TimeUnit.MILLISECONDS(5000)线程等待五秒.
1 表示时间基数 表示缓存有效时间
*/
stringRedisTemplate.opsForValue().set("aaa", "111", 5, TimeUnit.SECONDS);
//判断key=aaa的数据是否存在,存在继续执行,不存在报异常
Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
System.out.println("缓存存在");
/* Thread.sleep(1000);*/
/* Assert.assertEquals("111",stringRedisTemplate.opsForValue().get("aaa"));
System.out.println("缓存不存在");*/
}
@Test
//缓存实体类对象 切记实体类必须implements Serializable
public void testObj() throws Exception {
Style style = new Style();
style.setStyle("大房");
style.setMoney(125.0);
style.setFacilities("卫生间、健生房");
style.setMessage("可住两人");
//创建一个指定的缓存对象
ValueOperations<String, Style> operations = redisTemplate.opsForValue();
operations.set("com.neox", style);
operations.set("com.neo.f", style, 1, TimeUnit.SECONDS);
//主线程睡眠1秒
Thread.sleep(1000);
//删除缓存 by Key
// redisTemplate.delete("com.neox");
//判断指定内容是否存在缓存中
boolean exists = redisTemplate.hasKey("com.neo.f");
if (exists) {
System.out.println("exists is true");
} else {
System.out.println("exists is false");
}
}
@Test
//缓存集合对象 因为List中的元素是一个实体对象 所以使用的是RedisTemplate存储
public void redisTestList() throws InterruptedException {
List<String> list1 = new ArrayList<String>();
list1.add("a1");
list1.add("a2");
list1.add("a3");
//通常存储数据会先判断数据是否存在,不存在再存储
if (redisTemplate.hasKey("listkey1") != true) {
/* 不管是leftPush还是rightPush都可以用leftPop或者rightPoP任意一种获取到其中的值,
不过就是获取的遍历方向不一样。有学过数据结构的人都知道里面循环链表是可以前后遍历的,
就和这里的场景是一样的。如果还有不懂的话可以去看看这部分的源代码,
其实就是遍历方向不同,所以效率也不同。所以最好leftPush用leftPoP遍历,rightPush用rightPoP遍历*/
redisTemplate.opsForList().rightPush("listkey1", list1);
// redisTemplate.opsForList().leftPush("listkey1",list1);
List<String> resultList1 = (List<String>) redisTemplate.opsForList().rightPop("listkey1");
// List<String> resultList2=(List<String>)redisTemplate.opsForList().leftPop("listkey1");
System.out.println("resultList1:" + resultList1);
} else {
System.out.println("数据已存在缓存中");
}
}
//缓存集合对象 因为List中的元素是一个实体对象 所以使用的是RedisTemplate存储
@Test
public void redisTestSet() throws InterruptedException {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
setOperations.add("set1", "1");
setOperations.add("set1", "2");
setOperations.add("set1", "3");
Set<String> newset = redisTemplate.opsForSet().members("set1");
System.out.println(newset);
}
//缓存集合对象 因为List中的元素是一个实体对象 所以使用的是RedisTemplate存储
@Test
public void redisTestHash() throws InterruptedException {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "1");
map.put("key2", "2");
map.put("key3", "3");
redisTemplate.opsForHash().putAll("map", map);
//返回值格式为键值对 resultMap{key1=1, key2=2, key3=3}
Map<String, String> resultMap = redisTemplate.opsForHash().entries("map");
//返回值为所有value resulreslutMapListtMap:[1, 2, 3]
List<String> reslutMapList = redisTemplate.opsForHash().values("map");
//返回值为所有键 resultMapSet:[key1, key2, key3]
Set<String> resultMapSet = redisTemplate.opsForHash().keys("map");
//返回值为指定键的值
String value = (String) redisTemplate.opsForHash().get("map", "key1");
//list ——其中的值允许重复,因为它是有自己的排序规则的数据结构,所以可用来接受value值
// Set——其中的值不允许重复,无序的数据结构 只能用来接受键值,因为键值不可重复
System.out.println("resultMap" + resultMap);
System.out.println("resulreslutMapListtMap:" + reslutMapList);
System.out.println("resultMapSet:" + resultMapSet);
System.out.println("value:" + value);
}
}
6、自动根据方法生成缓存
使用注解@Cacheable() @CacheEvict() @CachePut()
至于使用方式可参考我另一篇文章https://blog.youkuaiyun.com/weixin_43784880/article/details/104282372
代码 此处仅展示一个用法
使用注解@Cacheable(value = “style”) 注解于要实现缓存的方法上,返回值会自动存储至redis,value表示数据的key值
如果不指定value 则报错At least one cache should be provided per cache operation.(每个缓存操作至少应该提供一个缓存。)
因为redis存储键不能为空,执行效果 发送两次findById请求,仅打印一次时间差,因为第一次缓存后,第二次程序直接从缓存中读取数据,不执行方法
//按照ID查找数据
@ApiOperation(value = "按照ID查找数据", notes = "按照ID查找数据")
@ApiImplicitParam(name = "id", value = "类型ID", required = true, dataType = "int", paramType = "path")
@RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
//打开该方法的缓存
@Cacheable(value = "style")
public Style findById(@PathVariable int id) {
long startTime = System.currentTimeMillis();
Style style = styleService.findById(id);
long endTime = System.currentTimeMillis();
logger.info("start -->end时间差" + (endTime - startTime));
return style;
}