什么是Redis?简单来说,就是缓存系统。Redis是一种可以持久化存储的缓存系统,是一个高性能的Key-Value数据库,它使用键_值对的方式来存储数据。参考:https://www.jianshu.com/p/30055a941452
书籍:《深入实践SpringBoot》
1、引入Starter:
org.springframework.boot
spring-boot-starter-redis
com.fasterxml.jackson.core
jackson-annotations
2.9.3
com.fasterxml.jackson.core
jackson-core
2.9.3
com.fasterxml.jackson.core
jackson-databind
2.9.3
2、创建Redis服务类
Redis提供:string、hash、list、set及zset。
Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。RedisTemplate位于spring-data-redis包下。本篇我们将使用string进行存取操作,实体User:
package com.guxf.domain;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 3456232569272497427L;
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
// getter和setter以及toString略
}
Redis没有表结构的概念,所以要实现MySQL数据库中表的数据在Redis中存取,必须做一些转换,使用json格式的文本作为Redis与java普通对象交换数据的存储格式。
package com.guxf.util;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonUtil {
private static ObjectMapper objectMapper = new ObjectMapper();
public static String convertObj2String(Object object) {
String s = null;
try {
s = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return s;
}
public static T convertString2Obj(String s, Class clazz) {
T t = null;
try {
t = objectMapper.readValue(s, clazz);
} catch (IOException e) {
e.printStackTrace();
}
return t;
}
}
因为Redis使用了key-value的方式存储数据,所以存入时要生成一个唯一的key,而要查询或者删除数据时,就可以使用这个唯一的key进行相应的操作。保存在Redis数据库中的数据默认是永久存储的,可以指定一个时限来确定数据的生命周期,超过指定时限的数据将被Redis自动清除。
根据StringRedisTemplate对象命名我们可以知道该对象支持String类型,但是在实际的应用中,我们可能需要存入Object对象,所以我们需要把对象转成json格式字符串。
Redis连接配置文件,application.properties配置如下:
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=6000
RedisTemplate 是 redis 模块的核心类,是对 redis 操作的较高抽象具有丰富的特性。他关注的是序列化和连接管理,线程安全,提供了如下操作接口:
HashOperations
HyperLogLogOperations
ListOperations
SetOperations
ValueOperations
ZSetOperations
所以我们需要实现一个通用的RedisService类完成Redis的读写操作:
package com.guxf.service;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.guxf.util.JsonUtil;
@Service
public class UserRedisService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 一周有多少秒
*/
private static final long WEEK_SECONDS = 7 * 24 * 60 * 60;
/**
* 将 key,value 存放到redis数据库中,默认设置过期时间为一周
*
* @param key
* @param value
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), WEEK_SECONDS, TimeUnit.SECONDS);
}
/**
* 将 key,value 存放到redis数据库中,设置过期时间单位是秒
*
*/
public void set(String key, Object value, long expireTime) {
System.err.println("111111111111111");
redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime,TimeUnit.SECONDS);
System.err.println("肯定执行不到这一句话");
}
/**
* 判断 key 是否在 redis 数据库中
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 获取与 key 对应的对象
*
* @param key
* @param clazz
* 目标对象类型
* @param
* @return
*/
public T get(String key, Class clazz) {
String s = get(key);
if (s == null) {
return null;
}
return JsonUtil.convertString2Obj(s, clazz);
}
/**
* 获取 key 对应的字符串
*
* @param key
* @return
*/
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除 key 对应的 value
*
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
}
采取单元测试,代码如下:
package com.guxf;
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.test.context.junit4.SpringJUnit4ClassRunner;
import com.guxf.domain.User;
import com.guxf.service.UserRedisService;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class UserRedisTest {
@Autowired
private UserRedisService redisService;
@Test
public void testRedisService() {
User user3 = new User(2, "李逍遥", 100);
redisService.set("user3", user3, 1000 * 60l);
System.out.println("22222222222222222");
User userV3 = redisService.get("user3", User.class);
System.out.println("userV3=====" + userV3.toString());
}
}
接下来,我们就需要启动Redis,但是我Redis启动Redis报错:
启动报了 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 错,由于之前是可以启动的,所以百度了一下,重启即可,命令:
redis-cli.exe
127.0.0.1:6379>shutdown
not connected>exit
然后重新运行redis-server.exe redis.windows.conf,启动成功!
运行UserRedisTest,查看执行结果:
通过使用StringRedisTemplate对象完全实现了对Object对象的存储.通过redis-cli.exe可以查看到我们存储的Object对象是json格式字符串,但是当某个对象很大时,这个json字符串会很冗长,那我们有没有其他方式实现呢。如果有使用过spring-data-redis的开发者一定熟悉RedisTemplate
在测试过程中,曾遇到如下问题,连接Redis失败,Junit报错如下:
org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:966)
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getConnection(LettuceConnectionFactory.java:934)
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getSharedConnection(LettuceConnectionFactory.java:786)
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getConnection(LettuceConnectionFactory.java:300)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:95)
at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:82)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:211)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:218)
at com.guxf.service.UserRedisService.set(UserRedisService.java:38)
at com.guxf.UserRedisTest.testRedisService(UserRedisTest.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379
at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:56)
at io.lettuce.core.AbstractRedisClient.getConnection(AbstractRedisClient.java:233)
at io.lettuce.core.RedisClient.connectStandalone(RedisClient.java:253)
at io.lettuce.core.RedisClient.connect(RedisClient.java:202)
at org.springframework.data.redis.connection.lettuce.StandaloneConnectionProvider.getConnection(StandaloneConnectionProvider.java:56)
at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:959)
... 43 more
Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out
at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
at io.lettuce.core.AbstractRedisAsyncCommands.auth(AbstractRedisAsyncCommands.java:81)
at io.lettuce.core.RedisClient.lambda$connectStatefulAsync$2(RedisClient.java:324)
at io.lettuce.core.RedisClient$$Lambda$489/21065289.apply(Unknown Source)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
at java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:442)
at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:66)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:745)
检查无法连接Redis,检查配置文件,没发现什么问题。所以打桩追踪:
public void set(String key, Object value, long expireTime) {
System.err.println("111111111111111");
redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime,TimeUnit.SECONDS);
System.err.println("肯定执行不到这一句话");
}
找度娘,发现这一句话并无任何问题,再次检查配置文件,发现spring.redis.password= 这一项我多打了空格,擦!
》》》》》补充========== StringRedisTemplate常用操作 ============
//向redis里存入数据和设置缓存时间
stringRedisTemplate.opsForValue().set("test", "100",60*10,TimeUnit.SECONDS);
//val做-1操作
stringRedisTemplate.boundValueOps("test").increment(-1);
//根据key获取缓存中的val
stringRedisTemplate.opsForValue().get("test")
//val +1
stringRedisTemplate.boundValueOps("test").increment(1);
//根据key获取过期时间
stringRedisTemplate.getExpire("test")
//根据key获取过期时间并换算成指定单位
stringRedisTemplate.getExpire("test",TimeUnit.SECONDS)
//根据key删除缓存
stringRedisTemplate.delete("test");
//检查key是否存在,返回boolean值
stringRedisTemplate.hasKey("546545");
//向指定key中存放set集合
stringRedisTemplate.opsForSet().add("red_123", "1","2","3");
//设置过期时间
stringRedisTemplate.expire("red_123",1000 , TimeUnit.MILLISECONDS);
//根据key查看集合中是否存在指定数据
stringRedisTemplate.opsForSet().isMember("red_123", "1")
//根据key获取set集合
stringRedisTemplate.opsForSet().members("red_123");