第十六节 SpringBoot集成Redis
Redis系列博客
一、集成Redis
Redis安装与基本操做,可以参考这篇文章:第一次使用Redis
Redis是非常优秀的缓存中间件,我的Redis使用经验主要集中在
1)解决分布式系统中Session共享问题
2)利用Redis缓存制作分布式锁
3)缓解数据库压力,为某些重要的数据提供限时缓存
SpringBoot项目集成Redis,步骤如下。
(1)第一步:导入Redis依赖
<!-- Redis -->
<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>
(2)第二步:在yml中添加Redis的配置。配置Redis的地址与端口。这里着重注意一下超时时间的配置。我这里配置了10s,效果将会在最下面的测试动图中展示。
spring:
redis:
# Redis 数据库索引(默认为 0)
database: 0
# Redis 服务器地址
host: localhost
# Redis 服务器连接端口
port: 6379
password:
timeout: 10s
lettuce:
pool:
#连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 50
#连接池中的最大空闲连接 默认 8
max-idle: 8
#连接池中的最小空闲连接 默认 0
min-idle: 0
(3)第三步:编写Redis的Java配置类。这里我们创建一个RedisTemplate的实体,它是我们用于操作Redis服务器的门面类。当然了,我们实际开发中,不会直接操作RedisTemplate,我们一般会再将RedisTemplate做一层封装。因为大佬总是强调面向接口编程。确实,面向接口编程大大提高了代码的使用效率与代码阅读性。
package com.zhoutianyu.learnspringboot.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Serializable, Serializable>
redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Serializable, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// use jackson to serialize data
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
(4)第四步。我们将RedisTemplate再做一层封装。我们先抽象出一个Redis服务接口。
package com.zhoutianyu.learnspringboot.redis;
import org.springframework.dao.DataAccessException;
import java.util.Set;
public interface RedisService {
/**
* 添加缓存数据(如果给定key已存在,则进行覆盖)
*
* @param key key值
* @param obj 缓存数据
* @throws DataAccessException 数据访问异常
*/
void set(final byte[] key, final byte[] obj) throws DataAccessException;
/**
* 添加缓存数据(如果给定key已存在,则进行覆盖)
*
* @param key key值
* @param obj 缓存数据
* @throws DataAccessException 数据访问异常
*/
<T> void set(final byte[] key, final T obj) throws DataAccessException;
/**
* 添加缓存数据(如果给定key已存在,则进行覆盖)
*
* @param key key值
* @param obj 缓存数据
* @throws DataAccessException 数据访问异常
*/
<T> void set(final String key, final T obj) throws DataAccessException;
/**
* 添加缓存数据(如果给定key已存在,则不进行覆盖,直接返回false)
*
* @param key key值
* @param obj 缓存数据
* @return 执行操作是否成功
* @throws DataAccessException 数据访问异常
*/
<T> Boolean setNX(final String key, final T obj) throws DataAccessException;
/**
* 添加缓存数据,设定缓存失效时间
*
* @param key key值
* @param obj 缓存数据
* @param expireSeconds 失效时间(单位秒)
* @throws DataAccessException 数据访问异常
*/
<T> void setEx(final byte[] key, final T obj, final Long expireSeconds) throws DataAccessException;
/**
* 添加缓存数据,设定缓存失效时间
*
* @param key key值
* @param obj 缓存数据
* @param expireSeconds 失效时间(单位秒)
* @throws DataAccessException 数据访问异常
*/
<T> void setEx(final String key, final T obj, final Long expireSeconds) throws DataAccessException;
/**
* 根据key获取对应的缓存数据
*
* @param key key值
* @return 缓存数据
* @throws DataAccessException 数据访问异常
*/
<T> T get(final byte[] key) throws DataAccessException;
/**
* 根据key获取对应的缓存数据
*
* @param key key值
* @return 缓存数据
* @throws DataAccessException 数据访问异常
*/
<T> T get(final String key) throws DataAccessException;
/**
* 根据key删除对应的缓存数据
*
* @param key key值
* @return 删除的缓存数据条数
* @throws DataAccessException 数据访问异常
*/
Long del(final byte[] key) throws DataAccessException;
/**
* 获取缓存中匹配key值的所有键
*
* @param key key值
* @return 匹配的所有键
* @throws DataAccessException 数据访问异常
*/
Set<byte[]> keys(final String key) throws DataAccessException;
/**
* 检查key是否已经存在
*
* @param key key值
* @return 是否已经存在
* @throws DataAccessException 数据访问异常
*/
Boolean exists(final String key) throws DataAccessException;
/**
* 清空所有缓存数据
*
* @return 执行操作是否成功
* @throws DataAccessException 数据访问异常
*/
Boolean flushDB() throws DataAccessException;
/**
* 查看缓存里有多少数据
*
* @return 缓存数据条数
* @throws DataAccessException 数据访问异常
*/
Long dbSize() throws DataAccessException;
/**
* 检查是否连接成功
*
* @return 是否连接成功
* @throws DataAccessException 数据访问异常
*/
String ping() throws DataAccessException;
}
(5)第五步。为抽象出来的Redis服务接口RedisService提供一个此Redis服务接口的实现类RedisServiceImpl。这是我在实际开发中用的最常用的一套Redis服务接口及其实现类。可谓是身经百战的Redis服务实现类。
package com.zhoutianyu.learnspringboot.redis.impl;
import com.zhoutianyu.learnspringboot.redis.RedisService;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Boolean.TRUE;
import static org.slf4j.LoggerFactory.getLogger;
@Service("redisService")
public final class RedisServiceImpl implements RedisService {
private static final Logger LOGGER = getLogger(RedisServiceImpl.class);
private static final String DEFAULT_CHARSET = "UTF-8";
private final RedisTemplate<Serializable, Serializable> redisTemplate;
/**
* RedisServiceImpl
*
* @param redisTemplate redisTemplate
*/
@Autowired
public RedisServiceImpl(RedisTemplate<Serializable, Serializable> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void set(final byte[] key, final byte[] obj) throws DataAccessException {
redisTemplate.execute((RedisCallback<Long>) connection -> {
connection.set(key, obj);
return 1L;
});
}
@Override
public <T> void set(final byte[] key, T obj) throws DataAccessException {
final byte[] bValue = serialize(obj);
redisTemplate.execute((RedisCallback<Long>) connection -> {
connection.set(key, bValue);
return 1L;
});
}
@Override
public <T> void set(String key, T obj) throws DataAccessException {
final byte[] bKey = serialize(key);
final byte[] bValue = serialize(obj);
redisTemplate.execute((RedisCallback<Long>) connection -> {
connection.set(bKey, bValue);
return 1L;
});
}
@Override
public <T> Boolean setNX(String key, T obj) throws DataAccessException {
final byte[] bKey = serialize(key);
final byte[] bValue = serialize(obj);
return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.setNX(bKey, bValue));
}
@Override
public <T> void setEx(final byte[] key, T obj, final Long expireSeconds)
throws DataAccessException {
final byte[] bValue = serialize(obj);
redisTemplate.execute((RedisCallback<Boolean>) connection -> {
connection.setEx(key, expireSeconds, bValue);
return TRUE;
});
}
@Override
public <T> void setEx(String key, T obj, final Long expireSeconds)
throws DataAccessException {
final byte[] bKey = serialize(key);
final byte[] bValue = serialize(obj);
redisTemplate.execute((RedisCallback<Boolean>) connection -> {
connection.setEx(bKey, expireSeconds, bValue);
return TRUE;
});
}
@Override
public <T> T get(final byte[] key) throws DataAccessException {
final byte[] result = redisTemplate.
execute((RedisCallback<byte[]>) connection -> connection.get(key));
if (null == result || result.length == 0) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("没有读到Key:{}", new String(key, Charset.forName(DEFAULT_CHARSET)));
}
return null;
}
return deserialize(result);
}
@Override
public <T> T get(String key) throws DataAccessException {
final byte[] bKey = serialize(key);
final byte[] result = redisTemplate.
execute((RedisCallback<byte[]>) connection -> connection.get(bKey));
if (null == result || result.length == 0) {
return null;
}
return deserialize(result);
}
@Override
public Long del(final byte[] key) throws DataAccessException {
if (null == key || key.length == 0) {
return 0L;
}
return redisTemplate.execute((RedisCallback<Long>) connection -> {
LOGGER.info("删除Redis数据库Key:{}", new String(key, Charset.forName(DEFAULT_CHARSET)));
return connection.del(key);
});
}
@Override
public Set<byte[]> keys(final String key) throws DataAccessException {
if (isNullOrEmpty(key)) {
return new HashSet<>(0);
}
return redisTemplate.execute((RedisCallback<Set<byte[]>>) connection -> connection.keys(key.getBytes(Charset.forName(DEFAULT_CHARSET))));
}
@Override
public Boolean exists(final String key) throws DataAccessException {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> connection.exists(serialize(key)));
}
@Override
public Boolean flushDB() throws DataAccessException {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
connection.flushDb();
LOGGER.info("清空Redis数据库.");
return TRUE;
});
}
@Override
public Long dbSize() throws DataAccessException {
return (Long) redisTemplate.execute((RedisCallback) connection -> {
LOGGER.info("统计Redis数据库");
return connection.dbSize();
});
}
@Override
public String ping() throws DataAccessException {
return (String) redisTemplate.execute((RedisCallback) connection -> connection.ping());
}
/**
* 序列化对象
*
* @param obj 对象
* @return 序列化对象
*/
@SuppressWarnings("unchecked")
private <T> byte[] serialize(T obj) {
try {
final RedisSerializer<T> redisSerializer = (RedisSerializer<T>) redisTemplate.getValueSerializer();
return redisSerializer.serialize(obj);
} catch (Exception e) {
LOGGER.error("序列化发生异常:{}", e);
return new byte[0];
}
}
/**
* 反序列化对象
*
* @param bytes 字节数组
* @return 对象
*/
@SuppressWarnings("unchecked")
private <T> T deserialize(byte[] bytes) {
try {
final RedisSerializer<T> redisSerializer = (RedisSerializer<T>)
redisTemplate.getValueSerializer();
return redisSerializer.deserialize(bytes);
} catch (Exception e) {
LOGGER.error("反序列化发生异常:{}", e);
return null;
}
}
}
二、测试
经过上面的五步搭建,一套完整的Redis服务接口就已经搭建完毕了。
现在,让我们来验证一下Redis服务器是否有效果。
我们来实现一个基础的Controller与一个实现了序列化接口的(只有实现序列化接口的对象才能存储到Redis中)Bean。
package com.zhoutianyu.learnspringboot.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "redis")
public class RedisTestController {
private final RedisService redisService;
@Autowired
public RedisTestController(RedisService redisService) {
this.redisService = redisService;
}
@GetMapping(value = "/getKey")
public String getKey(String key) {
return redisService.get(key);
}
@GetMapping(value = "/setKey")
public void setKey(String key, String value) {
redisService.set(key, value);
}
@GetMapping(value = "/setEx")
public void setEx(String key, String value, Long seconds) {
redisService.setEx(key, value, seconds);
}
@GetMapping(value = "/setBean")
public void setBean(Long id, String username, String password) {
UserBean userBean = new UserBean(id, username, password);
redisService.set(String.valueOf(id), userBean);
}
@GetMapping(value = "/getBeanById")
public Object getBean(String key) {
return redisService.get(key);
}
@GetMapping(value = "/flushDB")
public void flushDB() {
redisService.flushDB();
}
}
确保Redis服务器开机的情况下
测试:(1)基础setKey 与 getKey 功能
(2)基础setEx 功能。设置缓存数据的存活时间为12秒。12秒后,缓存失效。
(3)关闭Redis服务器,请求任意Redis接口服务超时时间为配置文件中 timeout 配置 的时间(也测试通过了,图片没展示)。
总是有同事问我:"你不是已经非常熟悉XXXX,也知道如何搭建XXXX环境,为什么还要弄这些花里胡哨的呢?"。嘿嘿,其实,如果让我徒手配,我哪里还能记住这些步骤。这篇博客大约花了我一个小时的时间,看起来成本很高。不过一旦在实际工作中需要集成XXXX的时候,要多久才能集成XXXX到正式项目中呢?根本不花时间。平时做一些技术拆解与积累,总是值得。
三、源码下载
本章节项目源码:点我下载源代码