第十六节 SpringBoot集成Redis

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到正式项目中呢?根本不花时间。平时做一些技术拆解与积累,总是值得。 

三、源码下载

        本章节项目源码:点我下载源代码

        目录贴:跟着大宇学SpringBoot-------目录帖

 

posted @ 2022-07-17 12:14  小大宇  阅读(352)  评论(0编辑  收藏  举报