Spring Boot——使用Redis

什么是Redis?简单来说,就是缓存系统。Redis是一种可以持久化存储的缓存系统,是一个高性能的Key-Value数据库,它使用键_值对的方式来存储数据。参考:https://www.jianshu.com/p/30055a941452

书籍:《深入实践SpringBoot》
1、引入Starter:

        
            org.springframework.boot
            spring-boot-starter-redis
        

        
        
            com.fasterxml.jackson.core
            jackson-annotations
            2.9.3
        
        
            com.fasterxml.jackson.core
            jackson-core
            2.9.3
        
        
            com.fasterxml.jackson.core
            jackson-databind
            2.9.3
        



2、创建Redis服务类
Redis提供:string、hash、list、set及zset。
Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。RedisTemplate位于spring-data-redis包下。本篇我们将使用string进行存取操作,实体User:

package com.guxf.domain;

import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 3456232569272497427L;

    private int id;

    private String name;

    private int age;

    public User() {
    }

    public User(int id, String name, int age) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
    }
// getter和setter以及toString略
}

Redis没有表结构的概念,所以要实现MySQL数据库中表的数据在Redis中存取,必须做一些转换,使用json格式的文本作为Redis与java普通对象交换数据的存储格式。

package com.guxf.util;

import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonUtil {

    private static ObjectMapper objectMapper = new ObjectMapper();

    public static String convertObj2String(Object object) {
        String s = null;
        try {
            s = objectMapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return s;
    }

    public static  T convertString2Obj(String s, Class clazz) {
        T t = null;
        try {
            t = objectMapper.readValue(s, clazz);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return t;
    }
}

因为Redis使用了key-value的方式存储数据,所以存入时要生成一个唯一的key,而要查询或者删除数据时,就可以使用这个唯一的key进行相应的操作。保存在Redis数据库中的数据默认是永久存储的,可以指定一个时限来确定数据的生命周期,超过指定时限的数据将被Redis自动清除。
根据StringRedisTemplate对象命名我们可以知道该对象支持String类型,但是在实际的应用中,我们可能需要存入Object对象,所以我们需要把对象转成json格式字符串。

Redis连接配置文件,application.properties配置如下:

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=6000

RedisTemplate 是 redis 模块的核心类,是对 redis 操作的较高抽象具有丰富的特性。他关注的是序列化和连接管理,线程安全,提供了如下操作接口:

HashOperations
HyperLogLogOperations
ListOperations
SetOperations
ValueOperations
ZSetOperations

所以我们需要实现一个通用的RedisService类完成Redis的读写操作:

package com.guxf.service;

import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.guxf.util.JsonUtil;

@Service
public class UserRedisService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 一周有多少秒
     */
    private static final long WEEK_SECONDS = 7 * 24 * 60 * 60;

    /**
     * 将 key,value 存放到redis数据库中,默认设置过期时间为一周
     *
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), WEEK_SECONDS, TimeUnit.SECONDS);
    }

    /**
     * 将 key,value 存放到redis数据库中,设置过期时间单位是秒
     * 
     */
    public void set(String key, Object value, long expireTime) {
        System.err.println("111111111111111");
        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime,TimeUnit.SECONDS);
        System.err.println("肯定执行不到这一句话");
    }

    /**
     * 判断 key 是否在 redis 数据库中
     *
     * @param key
     * @return
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获取与 key 对应的对象
     * 
     * @param key
     * @param clazz
     *            目标对象类型
     * @param 
     * @return
     */
    public  T get(String key, Class clazz) {
        String s = get(key);
        if (s == null) {
            return null;
        }
        return JsonUtil.convertString2Obj(s, clazz);
    }

    /**
     * 获取 key 对应的字符串
     * 
     * @param key
     * @return
     */
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除 key 对应的 value
     * 
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

采取单元测试,代码如下:

package com.guxf;

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.test.context.junit4.SpringJUnit4ClassRunner;

import com.guxf.domain.User;
import com.guxf.service.UserRedisService;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class UserRedisTest {

    @Autowired
    private UserRedisService redisService;

    @Test
    public void testRedisService() {
        User user3 = new User(2, "李逍遥", 100);
        redisService.set("user3", user3, 1000 * 60l);
        System.out.println("22222222222222222");
        User userV3 = redisService.get("user3", User.class);
        System.out.println("userV3=====" + userV3.toString());
    }
}

接下来,我们就需要启动Redis,但是我Redis启动Redis报错:


Spring Boot——使用Redis_第1张图片
Redis启动报错.png

启动报了 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 错,由于之前是可以启动的,所以百度了一下,重启即可,命令:
redis-cli.exe

127.0.0.1:6379>shutdown

not connected>exit

然后重新运行redis-server.exe redis.windows.conf,启动成功!
运行UserRedisTest,查看执行结果:

Spring Boot——使用Redis_第2张图片
结果.png

Spring Boot——使用Redis_第3张图片
Redis取值——输入get user3.png

通过使用StringRedisTemplate对象完全实现了对Object对象的存储.通过redis-cli.exe可以查看到我们存储的Object对象是json格式字符串,但是当某个对象很大时,这个json字符串会很冗长,那我们有没有其他方式实现呢。如果有使用过spring-data-redis的开发者一定熟悉RedisTemplate接口,StringRedisTemplate就相当于RedisTemplate的实现。参照: https://www.cnblogs.com/guanzhyan/p/8367585.html

在测试过程中,曾遇到如下问题,连接Redis失败,Junit报错如下:

org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379
    at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:966)
    at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getConnection(LettuceConnectionFactory.java:934)
    at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getSharedConnection(LettuceConnectionFactory.java:786)
    at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory.getConnection(LettuceConnectionFactory.java:300)
    at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:132)
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:95)
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:82)
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:211)
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
    at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
    at org.springframework.data.redis.core.DefaultValueOperations.set(DefaultValueOperations.java:218)
    at com.guxf.service.UserRedisService.set(UserRedisService.java:38)
    at com.guxf.UserRedisTest.testRedisService(UserRedisTest.java:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379
    at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:56)
    at io.lettuce.core.AbstractRedisClient.getConnection(AbstractRedisClient.java:233)
    at io.lettuce.core.RedisClient.connectStandalone(RedisClient.java:253)
    at io.lettuce.core.RedisClient.connect(RedisClient.java:202)
    at org.springframework.data.redis.connection.lettuce.StandaloneConnectionProvider.getConnection(StandaloneConnectionProvider.java:56)
    at org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory$SharedConnection.getNativeConnection(LettuceConnectionFactory.java:959)
    ... 43 more
Caused by: io.lettuce.core.RedisCommandTimeoutException: Command timed out
    at io.lettuce.core.LettuceFutures.awaitOrCancel(LettuceFutures.java:114)
    at io.lettuce.core.AbstractRedisAsyncCommands.auth(AbstractRedisAsyncCommands.java:81)
    at io.lettuce.core.RedisClient.lambda$connectStatefulAsync$2(RedisClient.java:324)
    at io.lettuce.core.RedisClient$$Lambda$489/21065289.apply(Unknown Source)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602)
    at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
    at java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:442)
    at io.netty.util.concurrent.DefaultEventExecutor.run(DefaultEventExecutor.java:66)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:745)

检查无法连接Redis,检查配置文件,没发现什么问题。所以打桩追踪:

public void set(String key, Object value, long expireTime) {
        System.err.println("111111111111111");
        redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime,TimeUnit.SECONDS);
        System.err.println("肯定执行不到这一句话");
    }

找度娘,发现这一句话并无任何问题,再次检查配置文件,发现spring.redis.password= 这一项我多打了空格,擦!

》》》》》补充========== StringRedisTemplate常用操作 ============

//向redis里存入数据和设置缓存时间
stringRedisTemplate.opsForValue().set("test", "100",60*10,TimeUnit.SECONDS);

//val做-1操作
stringRedisTemplate.boundValueOps("test").increment(-1);

//根据key获取缓存中的val
stringRedisTemplate.opsForValue().get("test")

//val +1
stringRedisTemplate.boundValueOps("test").increment(1);

//根据key获取过期时间
stringRedisTemplate.getExpire("test")

//根据key获取过期时间并换算成指定单位
stringRedisTemplate.getExpire("test",TimeUnit.SECONDS)

//根据key删除缓存
stringRedisTemplate.delete("test");

//检查key是否存在,返回boolean值
stringRedisTemplate.hasKey("546545");

//向指定key中存放set集合
stringRedisTemplate.opsForSet().add("red_123", "1","2","3");

//设置过期时间
stringRedisTemplate.expire("red_123",1000 , TimeUnit.MILLISECONDS);

//根据key查看集合中是否存在指定数据
stringRedisTemplate.opsForSet().isMember("red_123", "1")

//根据key获取set集合
stringRedisTemplate.opsForSet().members("red_123");

你可能感兴趣的:(Spring Boot——使用Redis)