Springboot中使用Redis

一、Springboot配置Redis
 
pom.xml文件需要的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <!--<version>2.1.4.RELEASE</version>-->
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
注意: 1. 是spring-boot-starter-data-reds;
          2. 因为Springboot 2.0 中redis客户端使用了Lettue, 其依赖于commons, 所以加入以上(似乎Jedis依然可以使用.)
 
redis服务: 下载redis的Windows版本, 官网是Linux版本, Windows版由微软在github上维护, 可前往下载, 压缩包解压之后, 点击redis-server.exe开启本地redis服务, 端口号6379. 可使用redisclient客户端查看数据库
 
二、application.properties配置
 
## 是否启动日志SQL语句
spring.jpa.show-sql=true
 
# Redis 数据库索引(默认为 0)
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=
# springboot 2.0 redis默认客户端已换成lettuce
 
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
spring.redis.timeout=5000

 

 
三、自定义序列化(推荐使用StringRedisTemplate)
 
为redis客户端查看操作数据, redisTemplate需要进行序列化设置, 默认配置的jdk序列化会导致在客户端查看不了数据(仍可使用内在函数存取修改, 只是查看不了), 为避免这种情况发生, 使用StringRedisTemplate或自行配置序列化, 自行配置可参考如下代码: 
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
 
@Configuration
public class MyRedisConfig {
 
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
 
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        ////参照StringRedisTemplate内部实现指定序列化器
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }
 
    private RedisSerializer<String> keySerializer(){
        return new StringRedisSerializer();
    }
 
    //使用Jackson序列化器
    private RedisSerializer<Object> valueSerializer(){
        return new GenericJackson2JsonRedisSerializer();
    }
}

 

简单测试代码(test)
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyConfigRedisTemplateTest {
 
    @Autowired
    private RedisTemplate redisTemplate; //在MyRedisConfig文件中配置了redisTemplate的序列化之后, 客户端也能正确显示键值对了
 
    @Test
    public void test(){
        redisTemplate.opsForValue().set("wujinxing", "lige");
        System.out.println(redisTemplate.opsForValue().get("wujinxing"));
        Map<String, Object> map = new HashMap<>();
        for (int i=0; i<10; i++){
            User user = new User();
            user.setId(i);
            user.setName(String.format("测试%d", i));
            user.setAge(i+10);
            map.put(String.valueOf(i),user);
        }
        redisTemplate.opsForHash().putAll("测试", map);
        BoundHashOperations hashOps = redisTemplate.boundHashOps("测试");
        Map map1 = hashOps.entries();
        System.out.println(map1);
    }
    static class User implements Serializable {
    private int id;
    private String name;
    private long age;
    省略getter, setter, toString...
    }
}

 

四、redis操作string, hash, set等
 
常见操作(均在controller上使用, 仅做测试, 实际项目应在service层使用redis):
@Controller
@RequestMapping("/redis")
public class RedisController {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisController.class);
 
    @Autowired
    private RedisTemplate redisTemplate = null;
 
    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;
 
    @RequestMapping("/stringAndHash")
    @ResponseBody
    public Map<String, Object> testStringAndHash(){
        redisTemplate.opsForValue().set("key1", "value1");
        //注意这里使用了 JDK 的序列化器 ,所以 Redis 保存时不是整数, 不能运算
        redisTemplate.opsForValue().set("int_key", "1");
        stringRedisTemplate.opsForValue().set("int", "1");
        //使用运算
        stringRedisTemplate.opsForValue().increment("int", 1);
 
        Map<String, Object> hash = new HashMap<>();
        hash.put("field1", "value1");
        hash.put("field2", "value2");
        stringRedisTemplate.opsForHash().putAll("hash2", hash);  //将Hashmap存储到redis中
        stringRedisTemplate.opsForHash().put("hash2", "field3", "value3");
 
        //绑定散列操作的 key,这样可以连续对同一个散列数据类型进行操作
        BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash2");
        hashOps.delete("field2", "field1"); //删除元素
        hashOps.put("field4", "value4"); //添加元素
        //LOGGER.info(hashOps.entries().toString());
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        return map;
    }
 
    @RequestMapping("/list")
    @ResponseBody
    public Map<String, Object> testList(){
        //链表从左到右的顺序为v10, v8, v6, v4, v2
        stringRedisTemplate.opsForList().leftPushAll("list1", "v2","v4","v6","v8","v10");
        //链表从左到右的顺序为v1, v3, v5, v7, v9
        stringRedisTemplate.opsForList().rightPushAll("list2", "v1","v3","v5","v7","v9");
 
        //绑定list2操作链表
        BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
        Object result1 = listOps.rightPop();//从右边弹出一个成员
        LOGGER.info("list2的最右边元素为: "+result1.toString());
 
        Object result2 = listOps.index(1); //获取定位元素, 下标从0开始
        LOGGER.info("list2下标为1的元素为"+result2.toString());
 
        listOps.leftPush("v0"); //从左边插入链表
 
        Long size = listOps.size();//求链表长
        LOGGER.info("list2的长度为: "+size);
 
        List element = listOps.range(0, size-2); //求链表区间成员
        LOGGER.info("list2从0到size-2的元素依次为: "+element.toString());
 
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        return map;
    }
 
    @RequestMapping("/set")
    @ResponseBody
    public Map<String, Object> testSet(){
        //重复的元素不会被插入
        stringRedisTemplate.opsForSet().add("set1", "v1","v1","v3","v5","v7","v9");
        stringRedisTemplate.opsForSet().add("set2", "v2","v4","v6","v5","v10","v10");
 
        //绑定sert1集合操作
        BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
        setOps.add("v11", "v13");
        setOps.remove("v1", "v3");
        Set set = setOps.members();//返回所有元素
        LOGGER.info("集合中所有元素: "+set.toString());
 
        Long size = setOps.size();//求成员数
        LOGGER.info("集合长度: "+String.valueOf(size));
 
        Set inner = setOps.intersect("set2"); //求交集
        setOps.intersectAndStore("set2", "set1_set2");//求交集并用新的集合保存
        LOGGER.info("集合的交集: "+inner.toString());
 
        Set diff = setOps.diff("set2"); //求差集
        setOps.diffAndStore("set2","set1-set2"); //求差集并用新的集合保存
        LOGGER.info("集合的差集: "+diff.toString());
 
        Set union = setOps.union("set2"); //求并集
        setOps.unionAndStore("set2", "set1=set2"); //求并集并用新的集合保存
        LOGGER.info("集合的并集: "+union.toString());
 
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        return map;
    }
 
    /**
     * redis操作有序集合
     * @return
     */
    @RequestMapping("/zset")
    @ResponseBody
    public Map<String, Object> testZSet(){
        Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
        for(int i=1; i<=9; i++){
            //分数
            double score = i*0.1;
            //创建一个TypedTuple对象, 存入值和分数
            ZSetOperations.TypedTuple typedTuple = new DefaultTypedTuple<String>("value" + i, score);
            typedTupleSet.add(typedTuple);
        }
        LOGGER.info("新建的set: "+typedTupleSet.toString());
        //往有序集合插入元素
        stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
        //绑定zset1有序集合操作
        BoundZSetOperations<String, String> zSetOps = stringRedisTemplate.boundZSetOps("zset1");
        zSetOps.add("value10", 0.26);
        Set<String> setRange = zSetOps.range(1,6);
        LOGGER.info("下标下1-6的set: " + setRange.toString());
 
        //按分数排序获取有序集合
        Set<String> setScore = zSetOps.rangeByScore(0.2, 0.6);
        LOGGER.info("按分数排序获取有序集合: "+ setScore.toString());
 
        //定义值范围
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        range.gt("value3"); //大于value3
        //range.gte("value3"); //大于等于value3
        //range.lt("value8"); //小于value8
        range.lte("value8"); //小于等于value8
 
        //按值排序, 注意这个排序是按字符串排序
        Set<String> setLex = zSetOps.rangeByLex(range);
        LOGGER.info("按值排序: "+setLex.toString());
 
        zSetOps.remove("value9", "value2");  //删除元素
        Double score = zSetOps.score("value8"); //求分数
        LOGGER.info("求value8的分数: "+score);
 
        //在下标区间 按分数排序, 同时返回value和score
        Set<ZSetOperations.TypedTuple<String>> rangeSet = zSetOps.rangeWithScores(1,6);
        LOGGER.info("在下标区间 按分数排序, 同时返回value和score:  "+rangeSet.toString());
 
        //在下标区间 按分数排序, 同时返回value和score
        Set<ZSetOperations.TypedTuple<String>> scoreSet = zSetOps.rangeByScoreWithScores(1,6);
        LOGGER.info("在下标区间 按分数排序, 同时返回value和score:  "+scoreSet.toString());
 
        //按从大到小排序
        Set<String> reverseSet = zSetOps.reverseRange(2, 8);
        LOGGER.info("按从大到小排序: "+reverseSet.toString());
 
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        return map;
    }
 
    @RequestMapping("/multi")
    @ResponseBody
    public Map<String, Object> testMulti(){
        stringRedisTemplate.opsForValue().set("key1", "value1");
 
        /*List list = (List) stringRedisTemplate.execute((RedisOperations operations)->{
            operations.watch("key1");
            operations.multi();
            operations.opsForValue().set("key2", "value2");
            //operations.opsForValue().increment("key1", 1);
            //获取的值将为null, 因为redis知识把命令放入队列
            Object value2 = operations.opsForValue().get("key2");
            System.out.println("命令在队列, 所以value2为null [ " + value2 + " ] ");
            operations.opsForValue().set("key3", "value3");
            Object value3 = operations.opsForValue().get("key3");
            System.out.println("命令在队列, 所以value3为null [ " + value3 + " ] ");
 
            //执行exce()命令,将先判断key1是否在监控后被修改过, 如果是则不执行事务, 否则就执行事务
            return operations.exec();
        });
        System.out.println(list);*/
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        return map;
    }
}

 

 
五、Service层使用redis
service层上简单使用redis的例子: 
@Service
public class CityServiceImpl implements CityService {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(CityServiceImpl.class);
 
    @Autowired
    private CityMapper cityMapper;
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    /**
     * 获取城市逻辑:
     * 如果缓存存在,从缓存中获取城市信息
     * 如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
     */
    @Override
    public City findCityById(Long id){
        //从缓存中获取城市信息
        String key = "city_"+id;
        ValueOperations<String,City> operations = redisTemplate.opsForValue();
 
        //缓存存在
        boolean hasKey = redisTemplate.hasKey(key);
        if(hasKey){
            City city = operations.get(key);
            LOGGER.info("CityServiceImpl.findCityById() : 从缓存中获取了城市 >> " + city.toString());
            return city;
        }
        //从DB中获取城市
        City city = cityMapper.findById(id);
 
        //插入缓存
        operations.set(key,city,10,TimeUnit.SECONDS); //缓存的时间仅有十秒钟
        LOGGER.info("CityServiceImpl.findCityById() : 城市插入缓存 >> " + city.toString());
        LOGGER.info("刚才加入redis的数据是: "+operations.get(key));
        return city;
    }
 
    @Override
    public Long saveCity(City city) {
        return cityMapper.saveCity(city);
    }
 
    /**
     * 更新城市逻辑:
     * 如果缓存存在,删除
     * 如果缓存不存在,不操作
     */
    @Override
    public Long updateCity(City city) {
        Long ret = cityMapper.updateCity(city);
 
        //缓存存在,删除缓存
        String key = "city_" + city.getId();
        boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey){
            redisTemplate.delete(key);
            LOGGER.info("CityServiceImpl.updateCity() : 从缓存中删除城市 >> " + city.toString());
        }
        return ret;
    }
 
    @Override
    public Long deleteCity(Long id) {
        Long ret = cityMapper.deleteCity(id);
 
        String key = "city_" + id;
        boolean hasKey = redisTemplate.hasKey(key);
        if(hasKey){
            redisTemplate.delete(key);
            LOGGER.info("CityServiceImpl.deleteCity() : 从缓存中删除城市 ID >> " + id);
        }
        return ret;
    }
}
 以上代码参考于<深入实践Springboot2.x>, 网上的相关教程等.
posted @ 2019-05-29 09:55  陆上江南  阅读(20416)  评论(0编辑  收藏  举报