spring boot 2.x结合redis的使用

本文介绍了在Spring Boot 2.x项目中如何整合并使用Redis作为缓存,包括配置YAML文件,设置缓存Key生成策略,异常处理,以及注解和非注解两种方式的使用。特别强调注解方式使用时,@Cacheable等注解必须直接应用于对外的第一层方法,否则缓存将不起作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

项目中用到了redis缓存,稍微整理下,有错误还望指正。

易错点:

1.注解方式的使用,注意点是,使用注解的方式,注解@cacheable(...)等声明的一定是该类直接对外的第一层方法,不能是该类的某个方法调用的下一层方法!!!!否则, 缓存不会生效

spring boot 版本信息

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

1.基本配置,在application.yml文件添加

spring:
  #redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        min-idle: 0
        max-idle: 8
    timeout: 10000

 

2.配置缓存的key生成,缓存管理,异常处理等

package com.example.demo;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * Created by He on 2018/8/23.
 */
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * @return 自定义策略生成的key
     * @description 自定义的缓存key的生成策略
     * 若想使用这个key 只需要讲注解上keyGenerator的值设置为keyGenerator即可</br>
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuffer sb = new StringBuffer();
            sb.append(target.getClass().getName());
            sb.append(":");
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(":" + obj.toString());
            }
            return sb.toString();
        };
    }

    //缓存管理器,只使用注解方式,非注解方式使用原来的方式设置参数
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        /*以下为Spring boot1.x的方式*/
        //方式1
//        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);//这里的redisTemplate使用redisTemplate(factory)
//        //设置缓存过期时间
//        rcm.setDefaultExpiration(60);//秒
//        return rcm;

        //方式2
//        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
//                .RedisCacheManagerBuilder
//                .fromConnectionFactory(factory);
//        return builder.build();

        //方式3
//        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
//        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//        RedisCacheConfiguration configuration = defaultCacheConfig.entryTtl(Duration.ofSeconds(100));
//        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, configuration);

        /***************以下为Spring boot2.x的方式*************************/

        //方式1,最简单的配置
//        return RedisCacheManager.create(factory);

        /*方式2,需要单独配置每个具体缓存空间的过期时间时使用,注意cacheName的对应*/
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig();
        //需要把配置返回重新赋值才有效
        cacheConfig = cacheConfig.entryTtl(Duration.ofMinutes(2)) // 设置缓存的默认过期时间,也是使用Duration设置
                .disableCachingNullValues() // 不缓存空值
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));// 设置value序列化

        // 设置一个初始化的缓存空间set集合,可针对不同的数据,使用不同的缓存策略;
        // 也可对所有的缓存容器使用同一的过期时间
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("my-redis-cache1");
        cacheNames.add("my-redis-cache2");

        // 对每个缓存空间应用不同的过期时间
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("my-redis-cache1", cacheConfig.entryTtl(Duration.ofSeconds(30)));
        configMap.put("my-redis-cache2", cacheConfig.entryTtl(Duration.ofSeconds(60)));

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)// 使用自定义的缓存配置初始化一个cacheManager
                .initialCacheNames(cacheNames) //注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
                .withInitialCacheConfigurations(configMap)
                .build();
        return cacheManager;

        //-----方式3,简单对所有缓存空间统一过期时间配置-------
//        RedisCacheConfiguration cacheConfiguration =
//                RedisCacheConfiguration.defaultCacheConfig()
//                        .disableCachingNullValues() // 不缓存空值
//                        .entryTtl(Duration.ofMinutes(2))
//                        .serializeValuesWith(RedisSerializationContext.SerializationPair
//                                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
//        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
//                .cacheDefaults(cacheConfiguration).build();
//        return cacheManager;
    }

    /**
     * RedisTemplate配置,适用与非注解方式
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        //设置序列化
        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);
        //配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);

        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);//key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//value序列化

        redisTemplate.setHashKeySerializer(stringSerializer);//Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);//Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }
}

3.封装非注解方式的工具类

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * redis操作CURD的工具类
 * Created by hesh on 2018/8/26.
 */
@Component
@Slf4j
public class RedisUtil {

    @Autowired
    private RedisTemplate redisTemplate;

/*********************缓存对象,最常用,适合所有类***********************/

    /**
     * 写入缓存,不使用过期时间
     *
     * @param key key
     * @param value 对象
     * @return
     */
    public boolean set(String key, Object value) {
        try {
            //因为redisTemplate已经设置了序列化,不需要再次配置
            //ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set(key, value);
            return true;
        } catch (Exception e) {
            log.error("写入value缓存异常,{}", e);
        }
        return false;
    }

    /**
     * 写入缓存,使用过期时间
     *
     * @param key
     * @param value
     * @param expireTime 过期时间
     * @param timeUnit 单位
     * @return
     */
    public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
        try {
//            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            ValueOperations operations = redisTemplate.opsForValue();
//            operations.set(key, value);
//            redisTemplate.expire(key, expireTime, timeUnit);
            operations.set(key, value, expireTime, timeUnit);
            return true;
        } catch (Exception e) {
            log.error("写入value缓存异常,{}", e);
        }
        return false;
    }

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


    /**
     * 判断缓存中是否有对应的value
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除对应的value
     *
     * @param key
     */
    public boolean remove(final String key) {
        if (exists(key)) {
            return redisTemplate.delete(key);
        }else {
            return false;
        }
    }

    /**
     * 批量删除对应的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);
        }
    }

    /*********************以下为哈希,list等类型的基本操作***********************/


    public boolean hmSet(String key, Object hk, Object hv) {
        try {
            HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
            hash.put(key, hk, hv);
            return true;
        } catch (Exception e) {
            log.error("写入hash缓存异常,{}", e);
        }
        return false;
    }


    /**
     * 哈希获取数据
     *
     * @param key
     * @param hashKey
     * @return
     */
    public Object hmGet(String key, Object hashKey) {
//        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
        return hash.get(key, hashKey);
    }

    /**
     * 列表添加
     *
     * @param k
     * @param v
     */
    public void listSet(String k, Object v) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        list.rightPush(k, v);
    }

    /**
     * 列表获取
     *
     * @param k
     * @param start
     * @param end
     * @return
     */
    public List<Object> listGet(String k, long start, long end) {
        ListOperations<String, Object> list = redisTemplate.opsForList();
        return list.range(k, start, end);
    }

    /**
     * 集合添加
     *
     * @param key
     * @param value
     */
    public void setAdd(String key, Object value) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        set.add(key, value);
    }

    /**
     * 集合获取
     *
     * @param key
     * @return
     */
    public Set<Object> setPop(String key) {
        SetOperations<String, Object> set = redisTemplate.opsForSet();
        return set.members(key);
    }

    /**
     * 有序集合添加
     *
     * @param key
     * @param value
     * @param scoure
     */
    public void zSetAdd(String key, Object value, double scoure) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        zset.add(key, value, scoure);
    }

    /**
     * 有序集合获取
     *
     * @param key
     * @param scoure
     * @param scoure1
     * @return
     */
    public Set<Object> zSetPop(String key, double scoure, double scoure1) {
        ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
        return zset.rangeByScore(key, scoure, scoure1);
    }

}

4.注解方式的使用,注意点是,使用注解的方式,注解@cacheable(...)等声明的一定是该类直接对外的第一层方法,不能是该类的某个方法调用的下一层方法!!!!否则, 缓存不会生效

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * Created by He on 2018/8/23.
 */
@Service
@Slf4j
//@CacheConfig(cacheNames = "User",keyGenerator = "keyGenerator")
public class UserServiceImpl implements UserService {

    @Override
    @Cacheable(cacheNames = "my-redis-cache1",key = "'cache1-'.concat(#username)",
            unless = "#result ==null || #result.get(0).photoInfo.thumbs.size()<=0")//使用过期时间1,测试OK
    public List<User> getUser(String username) {
        log.info("进入实现类获取数据: {}" + username);
        Random random = new Random();
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        User user1 =new User(username, random.nextInt(30),photoInfo);
        User user2 =new User(username, random.nextInt(30),photoInfo);
        List<User> users = new ArrayList<>();
        users.add(user1);
        users.add(user2);
        return users;
    }


    @Cacheable(cacheNames = "my-redis-cache2",key = "'cache2-'.concat(#username)")//使用过期时间2,测试OK
    @Override
    public User getUser2(String username) {
        log.info("进入实现类获取数据: {}" + username);
        Random random = new Random();
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        int age = random.nextInt(30);
        if (age < 20) {
            return null; //测试返回空,不缓存的策略
        }else {
            return new User(username, age,photoInfo);
        }
    }
}

5.非注解方式的使用

package com.example.demo;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;

/**
 * Created by He on 2018/8/24.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserServiceImplTest {

/* 测试直接使用redisTemplate使用redis操作数据 */

    @Autowired
    private RedisUtil redisUtil;

    /*测试缓存object*/
    @Test
    public void setAndGet() {
        redisUtil.set("test:value:setString", "value1");
        Assert.assertEquals("value1", redisUtil.get("test:value:setString"));
    }

    @Test
    //直接使用redisTemplate存取对象
    public void setAndGetUser() {
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        User user = new User("Tom", 10, photoInfo);
        redisUtil.set("test:value:setObject", user);

        User userTest = (User) redisUtil.get("test:value:setObject");

        log.info("获取对象:{}", userTest);
    }

    /*使用过期时间*/
    @Test
    public void testTimeOut() {
        redisUtil.set("test:value:setTimeout", "timeout", Long.valueOf(20), TimeUnit.SECONDS);
        log.info("第1次获取对象:{}", redisUtil.get("test:value:setTimeout"));
        try {
            Thread.sleep(20 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("第2次获取对象:{}", redisUtil.get("test:value:setTimeout"));
    }

    /*是否存在,和删除*/
    @Test
    public void testDeleteExist() {
        boolean isExist = redisUtil.exists("test:value:setString");
        log.info("存在该值? {}", isExist);
        if (isExist) {
            boolean isDeleted = redisUtil.remove("test:value:setString");
            log.info("删除成功? {}", isDeleted);
        }
    }


    /**
     * hash
     */
    @Test
    public void testHash() {
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        User user = new User("hm1", 10, photoInfo);
        redisUtil.hmSet("test:hash:setObject:", "user1", user);
        Assert.assertEquals("hm1", ((User) redisUtil.hmGet("test:hash:setObject:", "user1")).getUsername());
    }


    /**
     * list
     */
    @Test
    public void testList() {
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        User user = new User("list1", 10, photoInfo);
        List<User> users = new ArrayList<>();
        users.add(user);
        redisUtil.listSet("test:list:setObject:", users);
        Assert.assertNotNull(redisUtil.listGet("test:list:setObject:", 0, users.size() - 1));
    }

    /**
     * set
     */
    @Test
    public void testSet() {
        PhotoInfo photoInfo = new PhotoInfo();
        List<String> urls = new ArrayList<>();
        urls.add("http://xxx.com");
        photoInfo.setThumbs(urls);
        User user = new User("list1", 10, photoInfo);
        Set<User> users = new HashSet<>();
        users.add(user);
        redisUtil.setAdd("test:set:setObject:", users);
        Assert.assertNotNull(redisUtil.setPop("test:set:setObject:"));
    }

    /**
     * zSet
     */
//    @Test
//    public void testZSet() {
//        User user1 = new User("list1", 12);
//        User user2 = new User("list1", 11);
//        LinkedHashSet<User> users = new LinkedHashSet<>();
//        users.add(user1);
//        users.add(user2);
//        redisUtil.zSetAdd("test:ZSet:setObject:", users, 2);
//        Assert.assertNotNull(redisUtil.zSetPop("test:ZSet:setObject:", 2, 1));
//    }
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值