一、Spingboot主要可用的两个redis客户端框架分别为jedis和Lettuce。
在2.0版本以前,start框架默认依赖的是jedis,而2.0及到现在最新的版本则是改为了依赖Lettuce。先说说两个客户端框架的主要区别。Jedis实现上是直连的Redis Server,在多线程环境下是非线程安全的。每个线程都需要拿自己的 Jedis实例,当连接数量增多时,资源消耗成本较高。Lettuce的连接是基于Netty的多线程、事件驱动的 I/O 框架(笔者后续的教程还会详细介绍Netty的用法)。1个redis客户端的连接可以在多个业务线程中共享使用,而且也是现成安全的。具体需要多少个连接实例可以根据并发情况按需增加。下面将主要介绍springboot2.0版本以后redis的先关使用。
二、首先POM文件引入starter依赖
<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>
需要注意的一点,如果要使用redis的池,必须要自己手动添加commons-pool2的池组件依赖,不然会报找不到GenericObjectPoolConfig类的错。
三、YML文件关于redis的配置
spring:
application:
name: demo
#redis lettuce
redis:
host: XX.XX.XX.XX
database: 0
password: XXXXXX
port: XXX #你的redis的端口
#客户端连接工具配置 springboot2.0以后默认使用这个工具。1.0使用Jedis
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
YML文件没有太多好说的,只是要注意池化配置里需要注意2.0以前是jedis,2.0及以后是lettuce
四、调用redis客户端
现在,只要在调用类中注入StringRedisTemplate模板对象,我们就可以通过该模板来直接对redis服务端进行kv操作了。该对象已经针对每种不同的redis数据格式都提供了相应方法,并且对异常进行了处理。
package com.ywcai.demo.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Slf4j
@Service
public class RedisTest {
//StringRedisTemplate RedisTemplate的区别,序列化方式不一样,
//前者是通过String序列化,后者是通过字节进行序列化的。
//建议常用直接使用string的模板即可
@Autowired
private StringRedisTemplate stringRedisTemplate;
//redis查询所有的key
public void getAllKey() {
Set<String> keys = stringRedisTemplate.opsForValue().getOperations().keys("*");
for (String s : keys
) {
log.info("the key {}", s);
}
}
}
五、扩展应用,轻松使用缓存注解,来为数据库查询做缓存
Spingboot框架针对数据查询的缓存需要,结合redis做了3个注解,可以通过注解直接开启方法的数据缓存的功能。
package com.ywcai.demo.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Set;
@Slf4j
@Service
public class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//spel 表达式中,#result 代表返回值
//这里unless的表达式意思。如果返回值不是空 则存入缓存,为空,则不存入缓存
@Cacheable(value = "users", key = "#testParam.id+'-'+#testParam.code", unless = "#result==null")
public String getUser(TestParam testParam) {
log.info("缓存没有被调用!");
return testParam.msg;
}
//这里要删除,注意生成key的规则要和添加缓存的规则保持一致。意思是如果TestParam的数据有改动,则删除原来的缓存。。
@CacheEvict(value = "users", key = "#testParam.id+'-'+#testParam.code")
public void deleteUser(TestParam testParam) {
log.info("删除了缓存的KEY!");
}
//调用@CachePut注解的方法时,返回结果作为value放入缓存并且更新了原来的key value 值。 下一次掉用@Cacheable方法的时候是直接调用缓存,而不需走一次方法@Cacheable注解的方法。
@CachePut(value = "users", key = "#testParam.id+'-'+#testParam.code")
public String updateUser(TestParam testParam) {
testParam.msg = "set a new msg !";
log.info("更新了msg ,并且用返回的值更新了缓存!");
return testParam.msg;
}
}
condition 方法调用前判断,满足条件时,将结果存入缓存, condition 默认为""
unless 方法调用后判断,满足时,不将结果存入缓存 unless 默认为""
需要注意一点,如果是在RedisTest类本身的其他内部方法进行调用,是不会被缓存的,只有被外部类调用可以自动缓存
value用于指定一个缓存的名称,可以理解为key的一个大的分组,后面再是通过keyGenerator方法用来生成具体的key值,也可以用key直接通过方法的名字、参数通过表达式进行指定。
六、keyGenerator的方法可以通过下面自定义配置类来自定义。
package com.ywcai.demo.redis;
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;
@EnableCaching
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
//针对每个请求的类、方法和参数 生成唯一的key,这个不一定非要在这个配置里面加载,可以再需要使用的方法上面单独生成这个Bean也是可以的。
@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() + "-");
//这里要注意,如果添加KEY生成规则包含了 method方法,则使用cacheEvit的时候,插入数据方法和修改或者删除数据方法名字肯定不一样,最终key则肯定找不到。
//因此最好采用 chche名字+调用类名+入参参数值的方式。因此这里注释掉了方法名
// sb.append(method.getName() + "-");
//这里入参的参数也要注意,如果是对象,最好对象要添加@DATA注解,这样重写了toString方法
// toString方法最终使对象序列化的值,否则toString方法是对象在内存中的引用地址,肯定会不一样。
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
七、在Test报里面的测试方法。
首先在src的包中增加了一个简单测试对象
package com.ywcai.demo.redis;
import lombok.Data;
@Data
public class TestParam {
int id;
String msg;
int code;
}
下面是具体测试调用方法
package com.ywcai.demo.redis;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.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.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
class RedisTests {
@Autowired
RedisTest redisTest;
@Test
void testCachePut() {
TestParam testParam1 = new TestParam();
testParam1.setId(1);
testParam1.setCode(1);
testParam1.setMsg("set a init data !");
//这里第一次打印数据,将不会掉缓存
log.info("get user result 1 ===>>> {}", redisTest.getUser(testParam1));
//下面第二次打印数据时,将直接使用缓存,原生方法不会被执行
log.info("get user result 2 ===>>> {}", redisTest.getUser(testParam1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里测试了入参一个新的对象。但值和第一次入参的值是一样的
redisTest.updateUser(testParam1);
//这里更新后第一次查看数据,因为使用了put方法,因此缓存会直接被调用
log.info("after update the user invoke getUser ===>>> {}", redisTest.getUser(testParam1));
//这里调用了删除方法,因为加了cacheEvit注解,因此缓存的key会被删除
redisTest.deleteUser(testParam1);
//再次调用getUser方法,将执行原生方法
log.info("after delete the user invoke getUser ===>>> {}", redisTest.getUser(testParam1));
}
}
运行后的效果截图