本篇介绍如何在Spring Boot
中使用Redis
。
需要准备一下东西:
Spring Boot
项目Redis
服务器本篇目录如下:
Spring Boot
集成Redis
Redis
的三种加载配置方式Redis
并进行测试<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-redisartifactId>
dependency>
# Redis数据库索引(默认为0 redis有16个库)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379 (取自意大利歌女Alessia Merz的名字)
# 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=0
因为上面依赖了spring-boot-starter-data-redis
,可以使用默认的RedisAutoConfiguration
类加载properties
文件的配置。
打开RedisAutoConfiguration
可以看到自动帮我们注入了两个bean
:
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
此种方式会默认加载applicaiton
中的properties文件的前缀为“spring.redis”的属性redis
配置,spring-boot-autoconfigure的源代码中是使用RedisAutoConfiguration来加载Redis的配置的。 其中RedisAutoConfiguration会加载properties文件的前缀为“spring.redis”的属性。提供了以下两种bean
RedisTemplate
可以对Redis
中key
和value
都为object
类型的数据进行操作,默认会将对象使用JdkSerializationRedisSerializer
进行序列化StringRedisTemplate
可以对Redis
中key
和value
都是String
类型的数据进行操作@Configuration
@EnableCaching
public class RedisConfig{
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.min-idle}")
private int minIdle;
/**
* 注解@Cache key生成规则
*/
@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();
}
};
}
/**
* 注解@Cache的管理器,设置过期时间的单位是秒
* @Description:
* @param redisTemplate
* @return
*/
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
Map expires=new HashMap();
expires.put("user", 6000L);
expires.put("city", 600L);
cacheManager.setExpires(expires);
// Number of seconds before expiration. Defaults to unlimited (0)
cacheManager.setDefaultExpiration(600); //设置key-value超时时间
return cacheManager;
}
/**
* redis模板,存储关键字是字符串,值是Jdk序列化
* @Description:
* @param factory
* @return
*/
@Bean
public RedisTemplate,?> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate,?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//key序列化方式;但是如果方法上有Long等非String类型的话,会报类型转换错误;
RedisSerializer redisSerializer = new StringRedisSerializer();//Long类型不可以会出现异常信息;
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
//JdkSerializationRedisSerializer序列化方式;
JdkSerializationRedisSerializer jdkRedisSerializer=new JdkSerializationRedisSerializer();
redisTemplate.setValueSerializer(jdkRedisSerializer);
redisTemplate.setHashValueSerializer(jdkRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* redis连接的基础设置
* @Description:
* @return
*/
@Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host);
factory.setPort(port);
factory.setPassword(password);
//存储的库
factory.setDatabase(database);
//设置连接超时时间
factory.setTimeout(timeout);
factory.setUsePool(true);
factory.setPoolConfig(jedisPoolConfig());
return factory;
}
/**
* 连接池配置
* @Description:
* @return
*/
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
// jedisPoolConfig.set ...
return jedisPoolConfig;
}
}
在程序入口添加如下代码:
@ImportResource(locations={"classpath:spring-redis.xml"})
在resource
文件夹下新建文件spring-redis.xml
"http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="minIdle" value="${redis.pool.minIdle}" />
<property name="maxIdle" value="${redis.pool.maxIdle}" />
<property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}" />
id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="usePool" value="true">property>
<property name="hostName" value="${redis.ip}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
<property name="database" value="${redis.default.db}">property>
ref="jedisPoolConfig" />
id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="KeySerializer">
class="org.springframework.data.redis.serializer.StringRedisSerializer">
property>
<property name="ValueSerializer">
class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer">
property>
<property name="HashKeySerializer">
class="org.springframework.data.redis.serializer.StringRedisSerializer">
property>
<property name="HashValueSerializer">
class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer">
property>
Redis
并进行测试这里我们使用自动配置的方式(添加依赖即可,不需要其他配置)
@Repository
public class RedisService {
@Autowired
StringRedisTemplate stringRedisTemplate;
public void add(String key, User user, Long time) {
Gson gson = new Gson();
stringRedisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit.MINUTES);
}
public void add(String key, List users, Long time) {
Gson gson = new Gson();
String src = gson.toJson(users);
stringRedisTemplate.opsForValue().set(key, src, time, TimeUnit.MINUTES);
}
public User get(String key) {
String source = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(source)) {
return new Gson().fromJson(source, User.class);
}
return null;
}
public List getUserList(String key) {
String source = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(source)) {
return new Gson().fromJson(source, new TypeToken>() {
}.getType());
}
return null;
}
public void delete(String key) {
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
}
在test
下编写测试文件:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
RedisService redisService;
@Before
public void setUp() {
}
@Test
public void get() {
User user = new User();
user.setName("wangjianfeng");
user.setAge(22);
redisService.add("userByName:" + user.getName(), user, 10L);
List list = new ArrayList<>();
list.add(user);
redisService.add("list", list, 10L);
User user1 = redisService.get("userByName:wangjianfeng");
Assert.notNull(user1, "user is null");
List list2 = redisService.getUserList("list");
Assert.notNull(list2, "list is null");
}
}
测试通过。
上面内容了解了Redis
的基本存取使用,这里介绍Spring
使用Redis
缓存。
Spring
提供了很多缓存管理器,例如
SimpleCacheManager
EhCacheManager
CaffeineCacheManager
GuavaCacheManager
CompositeCacheManager
在Spring Boot
中除了核心的Spring
缓存之外,Spring Data
还提供了缓存管理器:RedisCacheManager
在Spring Boot
中通过@EnableCaching
注解自动化配置适合的缓存管理器。
所以在程序入口问题加入:@EnableCaching
注解
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
然后我们使用自己写代码配置的方式,修改RedisConfig
添加@EnableCaching
注解,并继承CachingCongigurerSupport
Spring提供了如下注解来声明缓存规则
注解 | 描述 |
---|---|
@Cacheable |
表明在Spring 调用之前,首先应该在缓存中查找方法的返回值,如果这个值能够找到,就会返回缓存的值,否则这个方法会被调用,返回值会放到缓存中 |
@CachePut |
表明Spring 应该将该方法返回值放到缓存中,在方法调用前不会检查缓存,方法始终会被调用 |
@CacheEvict |
表明Spring 应该在缓存中清楚一个或多个条目 |
@Caching |
分组注解,能够同时应用多个其他的缓存注解 |
@CacheConfig |
可以在类层级配置一些共有的缓存配置 |
@Cacheable
和@CachePut
有一些共有的属性:
属性 | 类型 | 描述 |
---|---|---|
value |
String[] |
缓存名称 |
condition |
SpEL 表达式,如果得到的值是false ,则不会应用缓存在该方法 |
|
key |
String |
SpEl 表达式,用来计算自定义的缓存key |
unless |
String |
SpEl 表达式,如果得到的值为true ,返回值不会放到缓存中 |
在一个请求方法上加上@Cacheable
注解,测试效果:
@Cacheable(value = "testCache")
@GetMapping("/{id}")
public User getUserById(@PathVariable Integer id) {
return userService.findUser(id);
}
访问这个接口后报错:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33)
at org.springframework.data.redis.cache.RedisCacheKey.serializeKeyElement(RedisCacheKey.java:74)
at org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes(RedisCacheKey.java:49)
at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:176)
at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:172)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:207)
原因如下: Redis
缓存的key
生成策略:
If no params are given,return SimpleKey.EMPTY.
If only one params is given,return that instance.
If more the one param is given,return a SimpleKey containing all parameters.
从上面的策略可以看出,上面缓存testCache
中使用的key
是整形的id
参数,但是在设置RedisTemplate
的时候设置了template.setKeySerializer(new StringRedisSerializer());
所以导致类型转换错误,所以需要重写keyGenerator
定制key
的生成策略
修改RedisConfig
类,添加keyGenerator
的方法:
@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())
.append(":")
.append(method.getName());
for (Object obj : params) {
sb.append(":")
.append(obj.toString());
}
return sb.toString();
}
};
}
这里的策略很简单,使用类名 + 方法名 + 参数
作为缓存key。
再次访问两次这个接口:
http://localhost:8080/demo/users/3
http://localhost:8080/demo/users/4
Redis
内容:127.0.0.1:6379> keys *
1) "testCache~keys"
2) "com.example.demo.controller.UserController:getUserById:3"
3) "com.example.demo.controller.UserController:getUserById:4"
127.0.0.1:6379> get com.example.demo.controller.UserController:getUserById:3
"[\"com.example.demo.model.User\",{\"id\":3,\"name\":\"\xe7\x8e\x8b\xe5\x89\x91\xe9\x94\x8b\",\"age\":12}]"
127.0.0.1:6379> zrange testCache~keys 0 10
1) "com.example.demo.controller.UserController:getUserById:3"
2) "com.example.demo.controller.UserController:getUserById:4"
127.0.0.1:6379>
可以看到Redis
里面保存了一下内容:
String
类型的键值对,key
就是生成的key
,value
就是user
对象序列化之后的结果。key
为 @Cacheable
中的value + ~keys
.内容为String
类型的键值对的key
.更新和删除缓存使用到@CachePut
和@CacheEvict
。这时候发现用原来的key
生成策略无法保证增删查改的key
一致(因为参数不同,方法名也不同),所以需要修改一下KeyGenerator
,改为缓存key
按照 缓存名称 + id
的方式
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
String[] value = new String[1];
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable != null) {
value = cacheable.value();
}
CachePut cachePut = method.getAnnotation(CachePut.class);
if (cachePut != null) {
value = cachePut.value();
}
CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
if (cacheEvict != null) {
value = cacheEvict.value();
}
sb.append(value[0]);
for (Object obj : params) {
sb.append(":")
.append(obj.toString());
}
return sb.toString();
}
};
}
然后修改Controller
:
@Cacheable(value = "user")
@GetMapping("/{id}")
public User getUserById(@PathVariable Integer id) {
return userService.findUser(id);
}
@CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
@PostMapping("/")
public User addUser(User user) {
userService.add(user);
//返回增加后的id
return user;
}
@CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
@PutMapping("/{id}")
public User updateUser(@PathVariable Integer id, User user) {
user.setId(id);
userService.update(user);
return user;
}
@CacheEvict(value = "user")
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Integer id) {
int result = userService.delete(id);
return result == 1 ? "删除成功" : "删除失败";
}
如果@CachePut
指定了key
属性之后,则不会再调用keygenerator
的方法。此时可以看到,增删查改的方法缓存的key
都是user:id
这样的格式。
然后进行测试,测试过程为测试某个方法然后到redis
中查看缓存是否正确。
下面贴出所有相关的代码:
RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
Logger logger = LoggerFactory.getLogger(RedisConfig.class);
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
// 设置缓存过期时间,秒
rcm.setDefaultExpiration(60 * 10);
return rcm;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
String[] value = new String[1];
Cacheable cacheable = method.getAnnotation(Cacheable.class);
if (cacheable != null) {
value = cacheable.value();
}
CachePut cachePut = method.getAnnotation(CachePut.class);
if (cachePut != null) {
value = cachePut.value();
}
CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
if (cacheEvict != null) {
value = cacheEvict.value();
}
sb.append(value[0]);
for (Object obj : params) {
sb.append(":")
.append(obj.toString());
}
return sb.toString();
}
};
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
UserService userService;
@GetMapping(value = "/")
public List getUsers() {
return userService.findUsers();
}
@Cacheable(value = "user")
@GetMapping("/{id}")
public User getUserById(@PathVariable Integer id) {
return userService.findUser(id);
}
@CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
@PostMapping("/")
public User addUser(User user) {
userService.add(user);
//返回增加后的id
return user;
}
@CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.id")
@PutMapping("/{id}")
public User updateUser(@PathVariable Integer id, User user) {
user.setId(id);
userService.update(user);
return user;
}
@CacheEvict(value = "user")
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Integer id) {
int result = userService.delete(id);
return result == 1 ? "删除成功" : "删除失败";
}
}