SpringBoot对Redis进行了封装,使用起来十分方便。这里使用的SpringBoot版本为1.5.9.RELEASE。
【1】安装redis并进行配置
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(highavailability)。
Redis中文官网:http://www.redis.cn/。
Centos7下安装Redis:。
【2】SpringBoot中引入Redis并配置
① 在pom文件中引入redis的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
② 在application.properties中配置redis
spring.redis.host=192.168.2.110
spring.redis.port=6379
spring.redis.pool.max-active=50
//根据情况自定义配置
引入Redis的starter,容器中默认保存的是RedisCacheManager。RedisCacheManager帮我们创建RedisCache,RedisCache通过操作Redis缓存数据。默认保存数据k/v 都是Object,使用默认的JDK序列化器。
【3】RedisTemplate和StringRedisTemplate
RedisTemplate
是SpringBoot为我们提供的redis操作Helper,K:V
均为Object。StringRedisTemplate
继承自RedisTemplate,是专为String:String
提供服务。
在RedisAutoConfiguration
中SpringBoot自动注册了RedisTemplate
和StringRedisTemplate
。源码示例如下:
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
* Redis connection configuration.
*/
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {
private final RedisProperties properties;
private final RedisSentinelConfiguration sentinelConfiguration;
private final RedisClusterConfiguration clusterConfiguration;
public RedisConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration) {
this.properties = properties;
this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
this.clusterConfiguration = clusterConfiguration.getIfAvailable();
}
//注册 JedisConnectionFactory
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException {
return applyProperties(createJedisConnectionFactory());
}
protected final JedisConnectionFactory applyProperties(
JedisConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
}
return factory;
}
private void configureConnection(JedisConnectionFactory factory) {
if (StringUtils.hasText(this.properties.getUrl())) {
configureConnectionFromUrl(factory);
}
else {
factory.setHostName(this.properties.getHost());
factory.setPort(this.properties.getPort());
if (this.properties.getPassword() != null) {
factory.setPassword(this.properties.getPassword());
}
}
}
private void configureConnectionFromUrl(JedisConnectionFactory factory) {
String url = this.properties.getUrl();
if (url.startsWith("rediss://")) {
factory.setUseSsl(true);
}
try {
URI uri = new URI(url);
factory.setHostName(uri.getHost());
factory.setPort(uri.getPort());
if (uri.getUserInfo() != null) {
String password = uri.getUserInfo();
int index = password.indexOf(":");
if (index >= 0) {
password = password.substring(index + 1);
}
factory.setPassword(password);
}
}
catch (URISyntaxException ex) {
throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url,
ex);
}
}
protected final RedisSentinelConfiguration getSentinelConfig() {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}
Sentinel sentinelProperties = this.properties.getSentinel();
if (sentinelProperties != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
config.master(sentinelProperties.getMaster());
config.setSentinels(createSentinels(sentinelProperties));
return config;
}
return null;
}
/**
* Create a {@link RedisClusterConfiguration} if necessary.
* @return {@literal null} if no cluster settings are set.
*/
protected final RedisClusterConfiguration getClusterConfiguration() {
if (this.clusterConfiguration != null) {
return this.clusterConfiguration;
}
if (this.properties.getCluster() == null) {
return null;
}
Cluster clusterProperties = this.properties.getCluster();
RedisClusterConfiguration config = new RedisClusterConfiguration(
clusterProperties.getNodes());
if (clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
}
return config;
}
private List<RedisNode> createSentinels(Sentinel sentinel) {
List<RedisNode> nodes = new ArrayList<RedisNode>();
for (String node : StringUtils
.commaDelimitedListToStringArray(sentinel.getNodes())) {
try {
String[] parts = StringUtils.split(node, ":");
Assert.state(parts.length == 2, "Must be defined as 'host:port'");
nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Invalid redis sentinel " + "property '" + node + "'", ex);
}
}
return nodes;
}
private JedisConnectionFactory createJedisConnectionFactory() {
JedisPoolConfig poolConfig = this.properties.getPool() != null
? jedisPoolConfig() : new JedisPoolConfig();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
}
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
}
return new JedisConnectionFactory(poolConfig);
}
private JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig config = new JedisPoolConfig();
RedisProperties.Pool props = this.properties.getPool();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
return config;
}
}
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
//如果容器中没有id为注册redisTemplate的RedisTemplate<Object, Object>,
//就会往容器中注册 RedisTemplate<Object, Object>,id为redisTemplate;
//可以根据这个规则自定义RedisTemplate<Object, Object>,默认使用JDK序列化器
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// 注册 StringRedisTemplate
@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
}
【4】对五大类数据进行操作
Redis中常见的五大类数据类型:String,List,Set,Hash和ZSet。
RedisTemplate封装了方法对五大类数据进行操作,示例如下:
//针对String
RedisTemplate.opsForValue();
//针对List
RedisTemplate.opsForList();
//针对Set
RedisTemplate.opsForSet();
//针对Hash
RedisTemplate.opsForHash();
//针对ZSet
RedisTemplate.opsForZSet();
其他还有,如下图:
每一个方法都会返回一个Operations对象,该对象封装了该类型的一系列操作。
以opsForValue为例:
public ValueOperations<K, V> opsForValue() {
if (valueOps == null) {
valueOps = new DefaultValueOperations<K, V>(this);
}
return valueOps;
}
ValueOperations源码如下:
public interface ValueOperations<K, V> {
/**
* Set {@code value} for {@code key}.
*
* @param key must not be {@literal null}.
* @param value
* @see <a href="http://redis.io/commands/set">Redis Documentation: SET</a>
*/
void set(K key, V value);
/**
* Set the {@code value} and expiration {@code timeout} for {@code key}.
*
* @param key must not be {@literal null}.
* @param value
* @param timeout
* @param unit must not be {@literal null}.
* @see <a href="http://redis.io/commands/setex">Redis Documentation: SETEX</a>
*/
void set(K key, V value, long timeout, TimeUnit unit);
/**
* Set {@code key} to hold the string {@code value} if {@code key} is absent.
*
* @param key must not be {@literal null}.
* @param value
* @see <a href="http://redis.io/commands/setnx">Redis Documentation: SETNX</a>
*/
Boolean setIfAbsent(K key, V value);
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code tuple}.
*
* @param map must not be {@literal null}.
* @see <a href="http://redis.io/commands/mset">Redis Documentation: MSET</a>
*/
void multiSet(Map<? extends K, ? extends V> map);
/**
* Set multiple keys to multiple values using key-value pairs provided in {@code tuple} only if the provided key does
* not exist.
*
* @param map must not be {@literal null}.
* @see <a href="http://redis.io/commands/mset">Redis Documentation: MSET</a>
*/
Boolean multiSetIfAbsent(Map<? extends K, ? extends V> map);
/**
* Get the value of {@code key}.
*
* @param key must not be {@literal null}.
* @see <a href="http://redis.io/commands/get">Redis Documentation: GET</a>
*/
V get(Object key);
/**
* Set {@code value} of {@code key} and return its old value.
*
* @param key must not be {@literal null}.
* @see <a href="http://redis.io/commands/getset">Redis Documentation: GETSET</a>
*/
V getAndSet(K key, V value);
/**
* Get multiple {@code keys}. Values are returned in the order of the requested keys.
*
* @param keys must not be {@literal null}.
* @see <a href="http://redis.io/commands/mget">Redis Documentation: MGET</a>
*/
List<V> multiGet(Collection<K> keys);
/**
* Increment an integer value stored as string value under {@code key} by {@code delta}.
*
* @param key must not be {@literal null}.
* @param delta
* @see <a href="http://redis.io/commands/incr">Redis Documentation: INCR</a>
*/
Long increment(K key, long delta);
/**
* Increment a floating point number value stored as string value under {@code key} by {@code delta}.
*
* @param key must not be {@literal null}.
* @param delta
* @see <a href="http://redis.io/commands/incrbyfloat">Redis Documentation: INCRBYFLOAT</a>
*/
Double increment(K key, double delta);
/**
* Append a {@code value} to {@code key}.
*
* @param key must not be {@literal null}.
* @param value
* @see <a href="http://redis.io/commands/append">Redis Documentation: APPEND</a>
*/
Integer append(K key, String value);
/**
* Get a substring of value of {@code key} between {@code begin} and {@code end}.
*
* @param key must not be {@literal null}.
* @param start
* @param end
* @see <a href="http://redis.io/commands/getrange">Redis Documentation: GETRANGE</a>
*/
String get(K key, long start, long end);
/**
* Overwrite parts of {@code key} starting at the specified {@code offset} with given {@code value}.
*
* @param key must not be {@literal null}.
* @param value
* @param offset
* @see <a href="http://redis.io/commands/setrange">Redis Documentation: SETRANGE</a>
*/
void set(K key, V value, long offset);
/**
* Get the length of the value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @see <a href="http://redis.io/commands/strlen">Redis Documentation: STRLEN</a>
*/
Long size(K key);
/**
* Sets the bit at {@code offset} in value stored at {@code key}.
*
* @param key must not be {@literal null}.
* @param offset
* @param value
* @since 1.5
* @see <a href="http://redis.io/commands/setbit">Redis Documentation: SETBIT</a>
*/
Boolean setBit(K key, long offset, boolean value);
/**
* Get the bit value at {@code offset} of value at {@code key}.
*
* @param key must not be {@literal null}.
* @param offset
* @since 1.5
* @see <a href="http://redis.io/commands/setbit">Redis Documentation: GETBIT</a>
*/
Boolean getBit(K key, long offset);
RedisOperations<K, V> getOperations();
}
其他数据类型类似。
【5】Redis命令接口
如下图所示,Redis提供了许多命令供我们使用,同样在SpringBoot中也封装了对应类型的命令接口。
SpringBoot中RedisCommands 源码示例如下:
/**
* Interface for the commands supported by Redis.
*
* @author Costin Leau
* @author Christoph Strobl
*/
public interface RedisCommands extends RedisKeyCommands, RedisStringCommands, RedisListCommands, RedisSetCommands,
RedisZSetCommands, RedisHashCommands, RedisTxCommands, RedisPubSubCommands, RedisConnectionCommands,
RedisServerCommands, RedisScriptingCommands, RedisGeoCommands, HyperLogLogCommands {
/**
* 'Native' or 'raw' execution of the given command along-side the given arguments. The command is executed as is,
* with as little 'interpretation' as possible - it is up to the caller to take care of any processing of arguments or
* the result.
*
* @param command Command to execute
* @param args Possible command arguments (may be null)
* @return execution result.
*/
Object execute(String command, byte[]... args);
}
基本上,在Linux Redis中能够使用的命令,在接口中都能找到!如RedisTemplate.hasKey()
判断Redis是否存在该key。
【6】测试示例
测试源码如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootCacheApplicationTests {
@Autowired
EmployeeMapper employeeMapper;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate redisTemplate;
@Test
public void test01() {
Boolean aBoolean = stringRedisTemplate.hasKey("msg");
if (aBoolean){
String msg = stringRedisTemplate.opsForValue().get("msg");
System.out.println(msg);
}
}
@Test
public void test02() {
Employee empById = employeeMapper.getEmpById(1);
redisTemplate.opsForValue().set("emp01",empById);
Object emp01 = redisTemplate.opsForValue().get("emp01");
System.out.println(emp01);
}
}
其中test01主要对String进行测试,test02则向redis中保存了一个对象!
使用Redis桌面管理工具查看如下:
注意,Redis中对象的保存是需要进行序列化的,默认使用JdkSerializationRedisSerializer
序列化器,所以图片中看到的是序列化后的二进制。
正常使用是没有问题的,查询出来的时候会自动反序列化,如下所示:
Employee{id=1, lastName='张三', gender=1, email='aa@qq.com', dId=1}
如果习惯于使用String,那么可以将其JSON化,两种方式:使用第三方JSON或者向容器中添加自定义RedisTemplate,改变其默认序列化器。
添加自定义RedisTemplate源码如下:
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object,Employee> empRedisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<Object,Employee> template = new RedisTemplate<Object, Employee>();
template.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
template.setDefaultSerializer(serializer);
return template;
}
}
测试如下:
@Autowired
RedisTemplate redisTemplate;
@Autowired
RedisTemplate<Object,Employee> empRedisTemplate;
@Test
public void test02() {
Employee empById = employeeMapper.getEmpById(1);
empRedisTemplate.opsForValue().set("emp01",empById);
Object emp01 = empRedisTemplate.opsForValue().get("emp01");
System.out.println(emp01);
}
此时桌面管理工具显示如下:
有趣的测试
redisTemplate.opsForValue().set("emp01","123456");
Object emp01 = redisTemplate.opsForValue().get("emp01");
System.out.println(emp01);
empRedisTemplate.opsForValue().set("emp01",empById);
Object emp02 = empRedisTemplate.opsForValue().get("emp01");
System.out.println(emp02);
输出结果如下:
123456
Employee{id=1, lastName='张三', gender=1, email='aa@qq.com', dId=1}
使用不同模板向Redis中存储在我们看来一样的key,不同的value时,Redis存储机制不同,且使用对应的Template从Redis获取同一个key得到对应值!
进入Linux环境查看Redis中的key :
分别使用三种模板存储key"emp01",如下:
stringRedisTemplate.opsForValue().set("emp01","hello world");
redisTemplate.opsForValue().set("emp01","123456");
empRedisTemplate.opsForValue().set("emp01",empById);
其在Redis中key的对应存储形式如下:
1) "emp01"
2) "\xac\xed\x00\x05t\x00\x05emp01"
3) "\"emp01\""
参考博文:SpringBoot2.0整合Redis