Spring Boot整合Redis

JJian發表於2019-05-08

一、Spring Boot對Redis的支援

Spring對Redis的支援是使用Spring Data Redis來實現的,一般使用Jedis或者lettuce(預設),Java客戶端在 org.springframework.boot.autoconfigure.data.redis(Spring Boot 2.x) 中redis的自動配置 AutoConfigureDataRedis 

 

                  

RedisAutoConfiguration提供了RedisTemplate與StringRedisTemplate(只針對鍵值都是字元型的資料)模板,其中註解 @ConditionalOnMissingBean 是關鍵,表明該Bean如果在Spring中已經存在,則忽略,如果沒有存在則在此處註冊由Spring管理,也就是說我們可以“重寫”該bean,實現自己的RedisTemplate與StringRedisTemplate,事實上,是要需要重寫的,理由如下:

  • 沒有實現我們所需要的序列化;
  • 泛型總是<Object, Object>,大部分場景我們更需要<String, Object>。
 @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

二、實戰

 

1、新增依賴

1)需要spring-boot-starter-cache依賴,管理快取

<!-- Spring Boot Cache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2)需要spring-boot-starter-data-redis依賴(注:spring boot 2.x改為在data下),支援redis:主要以為Jedis客戶端為主,排除預設的lettuce作為客戶端的依賴

<!-- Redis Cache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!-- 排除lettuce包,使用jedis代替-->
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3)需要jedis-client依賴(注:Redis Client 3版本以上會報錯與spring-boot-starter-data-redis衝突,最好使用2.9.x),使用jedis作為客戶端

<!-- Redis Client 3版本以上會報錯與spring-boot-starter-data-redis衝突 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

2、redis配置

建立RedisConfig配置類,增加@Configuration註解,同時開啟快取管理支援(新增註解@EnableCaching),繼承CachingConfigurerSupport重寫key生成策略

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
     /**
     * 生成key的策略:根據類名+方法名+所有引數的值生成唯一的一個key
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (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();
        };
    }

}

之後使用的application.yml配置檔案,其中這裡已經選擇jedis作為客戶端。

# redis 配置
  redis:
      port: 6379
      # Redis伺服器連線密碼(預設為空)
      password:
      host: xxx.xxx.xxx.xxx
      database: 0
      jedis:
        pool:
          #連線池最大連線數(使用負值表示沒有限制)
          max-active: 300
          # 連線池中的最小空閒連線
          max-idle: 100
           # 連線池最大阻塞等待時間(使用負值表示沒有限制)
          max-wait: 10000
          # 連線超時時間(毫秒)
          timeout: 5000

同時讀取配置屬性,注入JedisPoolConfig

  /**
     * redis配置屬性讀取
     */
    @Value("${spring.redis.host}")
    private  String host;
    @Value("${spring.redis.port}")
    private  int port;
    @Value("${spring.redis.database}")
    private  int database;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;


    /**
     * JedisPoolConfig配置
     * @return
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        log.info("初始化JedisPoolConfig");
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setMaxIdle(maxIdle);
        return jedisPoolConfig;
    }

3、實現序列化

針對RedisTemplate或StringRedisTemplate進行序列化,同時重寫註冊Bean

RedisTemplate預設使用JdkSerializationRedisSerializer,StringRedisTmeplate預設使用的是StringRedisSerializer。但都是不符合實際要求的

 /**
     * 重新實現RedisTemplate:解決序列化問題
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @SuppressWarnings({"rawtype", "unchecked"})
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 設定任何欄位可見
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 設定不是final的屬性可以轉換
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        log.info("objectMapper: {}", om);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        template.setEnableTransactionSupport(true);
        return template;
    }

    /**
     * 重新實現StringRedisTmeplate:鍵值都是String的的資料
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }
View Code

 

4、建立Redis連線工廠,同時註冊Bean

 注意Spring Boot 1.x與Spring Boot 2.x的區別,已在程式碼中註釋表明,Spring Boot 1.x使用的是JedisConnectionFactory 。而Spring Boot 2.x使用的是RedisStandaloneConfiguration ,之後傳入JedisConnectionFactory返回Bean

  /**
     * 注入RedisConnectionFactory
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        log.info("初始化JedisConnectionFactory");
        /* 在Spring Boot 1.x中已經過時,採用RedisStandaloneConfiguration配置
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setDatabase(database);*/

        // JedisConnectionFactory配置hsot、database、password等引數
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setDatabase(database);
        // JedisConnectionFactory配置jedisPoolConfig
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisPoolConfigBuilder =
                (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
        jedisPoolConfigBuilder.poolConfig(jedisPoolConfig);
        return new JedisConnectionFactory(redisStandaloneConfiguration);

    }

5、完整的RedisConfig配置類

 

/**
 *
 * @author jian
 * @date 2019/4/14
 * @description
 *  1) RedisTemplate(或StringRedisTemplate)雖然已經自動配置,但是不靈活(第一沒有序列化,第二泛型為<Object, Object>不是我們想要的型別)
 * 所以自己實現RedisTemplate或StringRedisTemplate)
 *  2) 採用RedisCacheManager作為快取管理器
 *
 */

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * redis配置屬性讀取
     */
    @Value("${spring.redis.host}")
    private  String host;
    @Value("${spring.redis.port}")
    private  int port;
    @Value("${spring.redis.database}")
    private  int database;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;


    /**
     * JedisPoolConfig配置
     * @return
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        log.info("初始化JedisPoolConfig");
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setMaxIdle(maxIdle);
        return jedisPoolConfig;
    }

    /**
     * 注入RedisConnectionFactory
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        log.info("初始化JedisConnectionFactory");
        /* 在Spring Boot 1.x中已經過時,採用RedisStandaloneConfiguration配置
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setDatabase(database);*/

        // JedisConnectionFactory配置hsot、database、password等引數
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setDatabase(database);
        // JedisConnectionFactory配置jedisPoolConfig
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jedisPoolConfigBuilder =
                (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
        jedisPoolConfigBuilder.poolConfig(jedisPoolConfig);
        return new JedisConnectionFactory(redisStandaloneConfiguration);

    }

    /**
     * 採用RedisCacheManager作為快取管理器
     * @param connectionFactory
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager redisCacheManager = RedisCacheManager.create(connectionFactory);
        return  redisCacheManager;
    }


    /**
     * 生成key的策略:根據類名+方法名+所有引數的值生成唯一的一個key
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (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();
        };
    }

    /**
     * 重新實現RedisTemplate:解決序列化問題
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @SuppressWarnings({"rawtype", "unchecked"})
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 設定任何欄位可見
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 設定不是final的屬性可以轉換
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        log.info("objectMapper: {}", om);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        template.setEnableTransactionSupport(true);
        return template;
    }

    /**
     * 重新實現StringRedisTmeplate:鍵值都是String的的資料
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

}
View Code

 

三、測試

1、編寫redis工具類

雖然RedisTemplate與StringRedisTemplate模板有提供的主要資料訪問方法:

  • opsForValue():操作只有簡單屬性的資料
  • opsForList():操作含有List的資料
  • opsForSet():操作含有set的資料
  • opsForHash():操作含有hash的資料
  • opsForZSet():操作含有有序set型別ZSet的資料

但是相關比較抽象,實現起來比較複雜,有必要進一步封裝,比如使用redisTmeplate中的簡單value的get操作:

Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);

但是封裝之後,相對客戶端使用者來說比較明瞭

   /**
     * 讀取快取
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

完整的簡單工具類如下:

@Component
public class RedisUtils {


    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 批量刪除對應的value
     *
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量刪除key
     *
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0) {
            redisTemplate.delete(keys);
        }
    }

    /**
     * 刪除對應的value
     *
     * @param key
     */
    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }

    /**
     * 判斷快取中是否有對應的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 讀取快取
     *
     * @param key
     * @return
     */
    public Object get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 寫入快取
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 寫入快取
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
View Code

 

2、Person實體類

需要注意的是一定要實現序列化,並且有序列化版本ID

public class Person implements Serializable {
    private final long serialVersionUID = 1L;

    private String id;
    private String name;
    private int age;
    private String gender;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
View Code

 

3、編寫測試類

Redis工具類Spring已經做了管理(增加@Compent註解),使用很簡單,只需要注入RedisUtils即可

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {

    @Autowired
    private RedisUtils redisUtils;

    @Test
    public void test(){
        Person person = new Person();
        person.setAge(23);
        person.setId("001");
        person.setName("Zhangsan");
        redisUtils.set("person-001", person);
        System.out.println(redisUtils.get("person-001"));
    }

}

4、測試結果

在IDE控制檯中:

在登入客戶端後檢視value值

 

相關文章