七、Java中使用Redis

1. 选择客户端

选择java连接redisd的客户端工具包为jedis

在这里插入图片描述

2. 引入相关jar包

  • jedis-2.9.0.jar
  • commons-pool2-2.4.2.jar

maven的依赖配置

<!-- jedis的依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

3. 测试Jedis

public class JedisDemo {
    public static void main(String[] args) {
        //创建连接redis数据库的对象
//        Jedis jedis = new Jedis("localhost", 6379);
        //创建连接池对象
        JedisPool jedisPool = new JedisPool("localhost", 6379);
        //通过连接池对象获取连接redis的连接对象
        Jedis jedis = jedisPool.getResource();

        //测试是否连接
        String pong = jedis.ping();
        System.out.println("是否连接:" + pong);

        //切换数据库
        String result = jedis.select(1);
        System.out.println("切换数据库:" + result);
        //查看当前数据库中所有键key
        Set<String> keys = jedis.keys("*");
        System.out.println("所有键:" + keys);
        //获取键的数量
        long keySize = jedis.dbSize();
        System.out.println("键的数量:" + keySize);

        System.out.println("---string类型---");
        //1.string类型
        //存储
        jedis.set("hello", "welcome to redis");
        //获取
        System.out.println(jedis.get("hello"));

        System.out.println("---list类型---");
        //2.list类型
        //存储
        jedis.lpush("names","张三","李四","王五");
        //获取
        List<String> names =  jedis.lrange("names",0,100);
        for (String name : names) {
            System.out.println(name);
        }

        System.out.println("---set类型---");
        //3.set类型
        //存储
        long setSize = jedis.sadd("city","北京","上海","深圳","上海");
        System.out.println(setSize);
        //获取
        Set<String> city = jedis.smembers("city");
        for (String s : city) {
            System.out.println(s);
        }

        System.out.println("---sorted set类型---");
        //4.sorted set类型
        //存储
        jedis.zadd("week",1,"周一");
        jedis.zadd("week",2,"周二");
        jedis.zadd("week",3,"周三");
        //获取
        Set<String> week = jedis.zrange("week", 0, 100);
        for (String s : week) {
            System.out.println(s);
        }
        Set<String> week2 = jedis.zrevrange("week",0,100);
        for (String s : week2) {
            System.out.println(s);
        }

        Map<String,Double> majorMap = new HashMap<>();
        majorMap.put("软件工程",100.0);
        majorMap.put("通信工程",80.0);
        majorMap.put("网络工程",90.0);
        jedis.zadd("major",majorMap);
        Set<String> major = jedis.zrange("major",0,100);
        for (String s : major) {
            System.out.println(s);
        }

        System.out.println("---hash类型---");
        //5. hash类型
        jedis.hset("user1","id","101");
        jedis.hset("user1","name","小明");
        jedis.hset("user1","age","20");

        Map<String,String> userMap = new HashMap<>();
        userMap.put("id","102");
        userMap.put("name","小红");
        userMap.put("age","22");
        jedis.hmset("user2", userMap);

        List<String> user1 = jedis.hmget("user1","id","name","age");
        for (String s : user1) {
            System.out.println(s);
        }
        Map<String,String> user2 = jedis.hgetAll("user2");
        System.out.println(user2);
    }
}

八、Spring整合Redis

1. 引入相关jar包

相关jar包包含三部分:

  1. Spring的jar包
  2. jedis的jar包
  3. Spring对redis的支持jar包

maven的依赖配置

<!-- spring核心包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!-- springbean包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!-- springcontext包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!-- spring表达式包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!-- springAOP包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.18.RELEASE</version>
</dependency>
<!--spring对redis的支持包-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.13.RELEASE</version>
</dependency>

<!-- jedis的依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.2</version>
</dependency>

2. 配置redis

applicationContext-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd">

    <context:property-placeholder location="classpath:redis.properties"/>
    <context:component-scan base-package="com.redis"/>

    <!-- 配置redis数据源 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大空闲数 -->
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <!-- 最大空连接数 -->
        <property name="maxTotal" value="${redis.maxTotal}"/>
        <!-- 最大等待时间 -->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
        <!-- 返回连接时,检测连接是否成功 -->
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>

    <!-- 配置redis连接池管理工厂JedisConnectionFactory -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- IP地址 -->
        <property name="hostName" value="${redis.host}"/>
        <!-- 端口号 -->
        <property name="port" value="${redis.port}"/>
        <!-- 密码 -->
        <!--<property name="password" value="${redis.password}"/>-->
        <!-- 超时时间 默认2000-->
        <property name="timeout" value="${redis.timeout}" />
        <!-- 选用的数据库 -->
        <property name="database" value="${redis.database}"/>
        <!-- 连接池配置引用 -->
        <property name="poolConfig" ref="poolConfig"/>
    </bean>

    <!-- 配置RedisTemplate:spring中提供的redis操作工具类 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <!--注入连接工厂-->
        <property name="connectionFactory" ref="jedisConnectionFactory"/>
    </bean>
</beans>

redis.properties

# redis服务器地址
redis.host=localhost

# redis服务器端口号
redis.port=6379

# redis密码
#redis.password=123456

# redis超时时间
redis.timeout=2000

# redis选用的数据库
redis.database=1

# redis最大空闲数
redis.maxIdle=300

# redis最大空连接数
redis.maxTotal=600

# redis最大等待时间
redis.maxWaitMillis=1000

# reids返回连接时,检测连接是否成功
redis.testOnBorrow=true

# redis缓存数据过期时间单位秒
redis.expiration=0

3. RedisTemplate的配置与使用

3.1 Jedis与RedisTemplate的区别

Jedis是Redis官方推荐的面向Java的操作Redis的客户端,可以用JedisPool来获得连接进行get、set、del等操作相对简单,而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。

3.2 测试
public class RedisTemplateDemo {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-redis.xml");
        RedisTemplate redisTemplate = ac.getBean("redisTemplate", RedisTemplate.class);
        System.out.println(redisTemplate);

        redisTemplate.opsForValue().set("spring", "hello spring-reids");
        System.out.println("存储成功...");

        String key = (String) redisTemplate.opsForValue().get("hello");
        System.out.println(key);
    }
}

执行后输出如下:

在这里插入图片描述

可以看到存储和获取数据都没有问题,但是使用redis-cli,却查询不到 spring 这个key的内容。

在这里插入图片描述

使用代码去查没问题,直接控制台连接,发现这个key和我们预期的不一样,多了一些前缀。为什么呢?

3.3 序列化问题

为了解决上面的问题,查看RedisTemplate的代码如下:

public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
    private boolean enableTransactionSupport = false;
    private boolean exposeConnection = false;
    private boolean initialized = false;
    private boolean enableDefaultSerializer = true;
    private RedisSerializer<?> defaultSerializer;
    private ClassLoader classLoader;

    private RedisSerializer keySerializer = null;
    private RedisSerializer valueSerializer = null;
    private RedisSerializer hashKeySerializer = null;
    private RedisSerializer hashValueSerializer = null;
    private RedisSerializer<String> stringSerializer = new StringRedisSerializer();

    private ScriptExecutor<K> scriptExecutor;

    // cache singleton objects (where possible)
    private ValueOperations<K, V> valueOps;
    private ListOperations<K, V> listOps;
    private SetOperations<K, V> setOps;
    private ZSetOperations<K, V> zSetOps;
    private GeoOperations<K, V> geoOps;
    private HyperLogLogOperations<K, V> hllOps;

    /**
        * Constructs a new <code>RedisTemplate</code> instance.
        */
    public RedisTemplate() {}

    public void afterPropertiesSet() {

        super.afterPropertiesSet();

        boolean defaultUsed = false;

        if (defaultSerializer == null) {

            defaultSerializer = new JdkSerializationRedisSerializer(
                    classLoader != null ? classLoader : this.getClass().getClassLoader());
        }

        if (enableDefaultSerializer) {

            if (keySerializer == null) {
                keySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (valueSerializer == null) {
                valueSerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashKeySerializer == null) {
                hashKeySerializer = defaultSerializer;
                defaultUsed = true;
            }
            if (hashValueSerializer == null) {
                hashValueSerializer = defaultSerializer;
                defaultUsed = true;
            }
        }

        if (enableDefaultSerializer && defaultUsed) {
            Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
        }

        if (scriptExecutor == null) {
            this.scriptExecutor = new DefaultScriptExecutor<K>(this);
        }

        initialized = true;
    }

    //部分代码省略...
}

得知RedisTemplate中key/value的数据默认采用JdkSerializationRedisSerializer来进行序列化。

  • keySerializer:字符串、列表、集合、有序集合、哈希的键的序列化策略
  • valueSerializer:字符串、列表、集合、有序集合、哈希的值的序列化策略
  • hashKeySerializer:哈希的值中数据键的序列化策略
  • hashValueSerializer:哈希的值中数据值的序列化策略

而我们继续进一步查看源码,最后发现实现序列化的代码如下:

public class DefaultSerializer implements Serializer<Object> {
    /**
        * Writes the source object to an output stream using Java serialization.
        * The source object must implement {@link Serializable}.
        * @see ObjectOutputStream#writeObject(Object)
        */
    @Override
    public void serialize(Object object, OutputStream outputStream) throws IOException {
        if (!(object instanceof Serializable)) {
            throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
                    "but received an object of type [" + object.getClass().getName() + "]");
        }
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
    }
}

所以具体的实现很清晰了,就是ObjectOutputStream这个流,是Java中最原始的序列化反序列流工具,会包含类型信息,所以会带上那串前缀了。

3.4 序列化策略以及配置

针对数据的“序列化/反序列化”,提供了多种可选择策略:

  1. JdkSerializationRedisSerializer:序列化java对象,使用JDK本身序列化机制,将对象通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。目前默认的序列化策略。
  2. StringRedisSerializer:简单的字符串序列化,Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
  3. Jackson2JsonRedisSerializer:序列化object对象为json字符串,jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson工具支持】
  4. OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】

applicationContext.xml中修改redisTemplate的配置,为其注入序列化策略:

<!-- 配置RedisTemplate:spring中提供的redis操作工具类 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <!--注入连接工厂-->
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <property name="keySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    </property>
    <property name="valueSerializer">
        <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    </property>
    <property name="hashKeySerializer">
        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    </property>
    <property name="hashValueSerializer">
        <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    </property>
</bean>

配置之后再次测试,正常。

在这里插入图片描述

3.5 RedisTemplate API

针对不同的数据结构(String, List, Set, ZSet, Hash)封装了不同的调用方式 opsForXXX

  • string类型:redisTemplate.opsForValue()
  • list类型:redisTemplate.opsForList()
  • set类型:redisTemplate.opsForSet()
  • zset类型:redisTemplate.opsForZSet()
  • hash类型:edisTemplate.opsForHash()

封装的工具类RedisUtil代码如下:

/**
 * RedisTemplate 工具类
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key 键
     * @param by  要增加几(大于0)
     * @return
     */
    public long incr(String key, long by) {
        if (by < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, by);
    }

    /**
     * 递减
     *
     * @param key 键
     * @param by  要减少几(小于0)
     * @return
     */
    public long decr(String key, long by) {
        if (by < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -by);
    }

    // ================================Hash=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }
    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return 0;
        }
    }

    // ===============================sorted set=================================

    /**
     * 向有序集合添加一个成员的
     *
     * ZADD key score1 member1 [score2 member2]
     *
     */
    public boolean zadd(String key, Object member, double score, long time) {
        try {
            redisTemplate.opsForZSet().add(key, member, score);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return false;
        }
    }

    /**
     * ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
     通过分数返回有序集合指定区间内的成员
     *
     */
    public Set<Object> zRangeByScore(String key, double minScore, double maxScore) {
        try {
            return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * ZSCORE key member
     返回有序集中,成员的分数值
     *
     */
    public Double zscore(String key, Object member) {
        try {
            return redisTemplate.opsForZSet().score(key, member);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * ZRANK key member 返回有序集合中指定成员的索引
     *
     */
    public Long zrank(String key, Object member) {
        try {
            return redisTemplate.opsForZSet().rank(key, member);
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }

    /**
     * Zscan 迭代有序集合中的元素(包括元素成员和元素分值)
     *
     */
    public Cursor<ZSetOperations.TypedTuple<Object>> zscan(String key) {
        try {
            Cursor<ZSetOperations.TypedTuple<Object>> cursor = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE);
            return cursor;
        } catch (Exception e) {
            System.out.println("redis error: "+e);
            return null;
        }
    }
}
3.6 StringRedisTemplate与RedisTemplate
  • 两者的关系是StringRedisTemplate继承RedisTemplate。
  • 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
  • StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
  • RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

4. 缓存注解

在实际应用中除了可以使用自己封装的util工具类,也可以通过注解的方式使用redis,实现数据缓存。

4.1 配置

添加命名空间和标签约束

xmlns:cache="http://www.springframework.org/schema/cache"

http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd

applicationContext.xml

<!-- 配置RedisCacheManager -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    <!-- 注入redisTemplate实例 -->
    <constructor-arg name="redisOperations" ref="redisTemplate"/>
    <!-- 配置默认的key过期时间 -->
    <property name="defaultExpiration" value="${redis.expiration}"/>
</bean>
<!-- 开启缓存注解 -->
<cache:annotation-driven/>
4.2 注解介绍

1、@Cacheable:配置在方法或类上,作用:在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存值。否则,方法就会被调用,返回值会放到缓存之中。

2、@CachePut:配置在方法或类上,作用:将方法的返回值放到缓存中。在方法的调用前不会检查缓存,方法始终都会被调用。

@Cacheable和@CachePut共有参数如下:

参数名称类型解析示例
valueString[]要使用的缓存名称@Cacheable(value=“user:loginUser”) @Cacheable(value={“user:loginUser”,“myCache”}
keyStringSpEL表达式,缓存的key,可以为空@Cacheable(value=“user:loginUser”,key="#username")
conditionStringSpEL表达式,缓存的条件,可以为空,只有为true才进行缓存@Cacheable(value=“user:loginUser”, key="#username", condition="#username.length()>2")
unlessStringSpEL表达式,缓存的条件,与condition相反,只有为false才进行缓存@Cacheable(value=“user:loginUser”, key="#username", unless="#result eq null")

Spring 提供了多个用来定义缓存规则的SpEL表达式

表达式描述
#root.args传递给缓存方法的参数,形式为数组
#root.caches该方法执行时对应的缓存,形式为数组
#root.target目标对象
#root.targetClass目标对象的类,是 #root.target.class的简写形式
#root.method缓存方法
#root.methodName缓存方法的名称,是#root.method.name的简写形式
#result方法调用的返回值
Argument任意的方法参数名(例如 #username)或参数索引(例如 #a0 或 #p0)

3、@CacheEvict:配置在方法或类上,作用:清除指定的一个或多个缓存。

参数如下:

参数名称类型解析示例
valueString[]要清除的缓存名称@CachEvict(value=“user:loginUser”) @CachEvict(value={“user:loginUser”,“myCache”}
keyStringSpEL表达式,缓存的key,可以为空@CachEvict(value=“user:loginUser”, key="#username")
conditionStringSpEL表达式,缓存的条件,可以为空,只有为true才清除缓存@CachEvict(value=“myCache”, condition="#username.length()>2")
allEntriesBoolean默认为false,如果为true,会清除指定的所有缓存@Cacheable(value=“user:loginUser”, allEntries=true)
beforeInvocationBoolean默认为false,如果为true,在方法调用之前清除缓存,如果为false,在方法调用之后清除缓存@Cacheable(value=“myCache”, beforeInvocation=true)

九、在SpringBoot中使用Redis

1. 引入redis的启动器依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. application.yml

spring:
  # redis的配置
  redis:
    host: localhost
    database: 1
    port: 6379

3. 配置类

@Configuration
/**
 * @EnableCaching
 * 作用:开启注解式缓存
 */
@EnableCaching
public class RedisConfig {

    /**
     * 1.配置redisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(jackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 2.json序列化
     */
    @Bean
    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        return serializer;
    }

    /**
     * 配置缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间,也是使用Duration设置
        config = config.entryTtl(Duration.ofMinutes(30))
                // 设置 key为string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置value为json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                // 不缓存空值
                .disableCachingNullValues();

        // 设置一个初始化的缓存空间set集合
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("default:cache");
        cacheNames.add("user:login:cache");

        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        configMap.put("default:cache", config);
        configMap.put("user:login:cache", config.entryTtl(Duration.ofMinutes(120)));

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

    @Bean
    public RedisUtil redisUtil(){
        return new RedisUtil();
    }
}

4. 使用与测试

@Service
public class CacheService {

    @Cacheable(value = "user:login:cache", key = "#username", unless = "#result eq null")
    public User cacheUser(String username) {
        System.out.println("cacheUser..." + username);
        if(StringUtils.isEmpty(username)) {
            return null;
        }
        User user = new User(1, username, "123", "13012312312");
        return user;
    }
}

.defaultCacheConfig();
// 设置缓存的默认过期时间,也是使用Duration设置
config = config.entryTtl(Duration.ofMinutes(30))
// 设置 key为string序列化
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 设置value为json序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
// 不缓存空值
.disableCachingNullValues();

    // 设置一个初始化的缓存空间set集合
    Set<String> cacheNames = new HashSet<>();
    cacheNames.add("default:cache");
    cacheNames.add("user:login:cache");

    // 对每个缓存空间应用不同的配置
    Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
    configMap.put("default:cache", config);
    configMap.put("user:login:cache", config.entryTtl(Duration.ofMinutes(120)));

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

@Bean
public RedisUtil redisUtil(){
    return new RedisUtil();
}

}


### 4. 使用与测试

```java
@Service
public class CacheService {

    @Cacheable(value = "user:login:cache", key = "#username", unless = "#result eq null")
    public User cacheUser(String username) {
        System.out.println("cacheUser..." + username);
        if(StringUtils.isEmpty(username)) {
            return null;
        }
        User user = new User(1, username, "123", "13012312312");
        return user;
    }
}

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐