SpringCache学习之操作redis-阿里云开发者社区

开发者社区> 数据库> 正文

SpringCache学习之操作redis

简介: 一、redis快速入门   1、redis简介   在java领域,常见的四大缓存分别是ehcache,memcached,redis,guava-cache,其中redis与其他类型缓存相比,有着得天独厚的优势: 它是基于内存的数据库,什么意思呢?由于受磁盘IO影响,它所有操作都在内存当中,用以提高性能,同时采用异步的方式将数据保存在硬盘当中。

一、redis快速入门

  1、redis简介

  在java领域,常见的四大缓存分别是ehcache,memcached,redis,guava-cache,其中redis与其他类型缓存相比,有着得天独厚的优势:

  1. 它是基于内存的数据库,什么意思呢?由于受磁盘IO影响,它所有操作都在内存当中,用以提高性能,同时采用异步的方式将数据保存在硬盘当中。
  2. 与memcached相比,redis支持多种数据类型,string,list,set,sorted set,hash。让我们使用起来更加灵活
  3. 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行  
  4. 丰富的特性:可用于缓存,消息,可以设置key的过期时间,过期后将会自动删除对应的记录 
  5. redis是单线程单进程的,它利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

 

 2、redis常见的数据类型

   2.1   redis的key:

     我们可以使用任何二进制序列当我们的键,过长过短的键都不建议使用,在这里建议大家使用尝试使用一个模式。例如,“object-type:id”是一个比较推荐的方式,举个例子,我们可以这样来定义键:“user:1000”。点或破折号通常用于多字字段。

    2.2  strings: 该类型是最基本的数据类型,也是最常见的数据类型。我们可以使用set 或get 来设置和获取对应的值:

> set mykey somevalue
OK
> get mykey
"somevalue"

    在这里面set 如果已经存在的key 会替换已有的值。注意value的值不要大于512M

    另外,我们可以使用mset 与 mget来设置和获取多个值,如下:

> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"

    我们可以使用exists判断key对应的值是否存在,del则是删除key对应的值

> set mykey hello
OK
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0

     我们也可以指定key值对应的过期时间:(单位为秒)

> set key some-value
OK
> expire key 5
(integer) 1
> get key (immediately)
"some-value"
> get key (after some time)
(nil)

 

  2.3 lists类型:这个类似于我们的集合类型,可以存放多个值,我们可以使用lpush(在头部添加)与rpush(在尾部添加)来添加对应的元素:

  

 rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"

  其中lrange 是取一定范围的元素

 

  2.4 Hashes 这个类似于key->key ,value的形式,我们可以使用hmset与hget来取值

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"

 

  2.5、SETS:存放了一系列无序的字符串集合,我们可以通过sadd与smembers来取值:

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2

 

  3、redis中的事务

    MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。redis中事务不存在回滚,我们这么操作试试:

localhost:0>multi
"OK"

localhost:0>set test abc
"QUEUED"

localhost:0>incr test
"QUEUED"

localhost:0>exec
 1)  "OK"
 2)  "ERR value is not an integer or out of range"
localhost:0>get test
"abc"

如果事务当中入队不成功,我们可以看一下例子:

localhost:0>multi
"OK"

localhost:0>set test abcd
"QUEUED"

localhost:0>test abc
"ERR unknown command 'test'"

localhost:0>exec
"EXECABORT Transaction discarded because of previous errors."

localhost:0>get test
"abc"

此时就会终止我们提交事务

另外我们调用 DISCARD , 客户端可以清空事务队列并放弃执行事务。例子:

localhost:0>multi
"OK"

localhost:0>set test abcd
"QUEUED"

localhost:0>discard
"OK"

localhost:0>get test
"abc"

 

4、远程连接redis注意事项

   redis现在的版本开启redis-server后,redis-cli只能访问到127.0.0.1,因为在配置文件中固定了ip,因此需要修改redis.conf(有的版本不是这个文件名,只要找到相对应的conf后缀的文件即可)文件以下几个地方。1、bind 127.0.0.1改为 #bind 127.0.0.1   2、protected-mode yes 改为 protected-mode no

     指定配置文件运行:

./redis-server ../redis.conf

 远程连接的命令:

 

./redis-cli -p 6379 -h xxx.xxx.xxx

 

二、使用SpringCache集成redis

1、关于SpringCache

  从SpringFramework3.1版本开始,Spring给我们提供了一系列注解和接口规范来简化我们操作缓存代码量,几个核心接口如下:

  CacheManager: 该接口主要作用是获取缓存和获取缓存名称

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cache;

import java.util.Collection;

import org.springframework.lang.Nullable;

/**
 * Spring's central cache manager SPI.
 * Allows for retrieving named {@link Cache} regions.
 *
 * @author Costin Leau
 * @since 3.1
 */
public interface CacheManager {

    /**
     * Return the cache associated with the given name.
     * @param name the cache identifier (must not be {@code null})
     * @return the associated cache, or {@code null} if none found
     */
    @Nullable
    Cache getCache(String name);

    /**
     * Return a collection of the cache names known by this manager.
     * @return the names of all caches known by the cache manager
     */
    Collection<String> getCacheNames();

}
View Code

  在这里面最核心的接口当然是Cache:

/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cache;

import java.util.concurrent.Callable;

import org.springframework.lang.Nullable;

/**
 * Interface that defines common cache operations.
 *
 * <b>Note:</b> Due to the generic use of caching, it is recommended that
 * implementations allow storage of <tt>null</tt> values (for example to
 * cache methods that return {@code null}).
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 3.1
 */
public interface Cache {

    /**
     * Return the cache name.
     */
    String getName();

    /**
     * Return the underlying native cache provider.
     */
    Object getNativeCache();

    /**
     * Return the value to which this cache maps the specified key.
     * <p>Returns {@code null} if the cache contains no mapping for this key;
     * otherwise, the cached value (which may be {@code null} itself) will
     * be returned in a {@link ValueWrapper}.
     * @param key the key whose associated value is to be returned
     * @return the value to which this cache maps the specified key,
     * contained within a {@link ValueWrapper} which may also hold
     * a cached {@code null} value. A straight {@code null} being
     * returned means that the cache contains no mapping for this key.
     * @see #get(Object, Class)
     */
    @Nullable
    ValueWrapper get(Object key);

    /**
     * Return the value to which this cache maps the specified key,
     * generically specifying a type that return value will be cast to.
     * <p>Note: This variant of {@code get} does not allow for differentiating
     * between a cached {@code null} value and no cache entry found at all.
     * Use the standard {@link #get(Object)} variant for that purpose instead.
     * @param key the key whose associated value is to be returned
     * @param type the required type of the returned value (may be
     * {@code null} to bypass a type check; in case of a {@code null}
     * value found in the cache, the specified type is irrelevant)
     * @return the value to which this cache maps the specified key
     * (which may be {@code null} itself), or also {@code null} if
     * the cache contains no mapping for this key
     * @throws IllegalStateException if a cache entry has been found
     * but failed to match the specified type
     * @since 4.0
     * @see #get(Object)
     */
    @Nullable
    <T> T get(Object key, @Nullable Class<T> type);

    /**
     * Return the value to which this cache maps the specified key, obtaining
     * that value from {@code valueLoader} if necessary. This method provides
     * a simple substitute for the conventional "if cached, return; otherwise
     * create, cache and return" pattern.
     * <p>If possible, implementations should ensure that the loading operation
     * is synchronized so that the specified {@code valueLoader} is only called
     * once in case of concurrent access on the same key.
     * <p>If the {@code valueLoader} throws an exception, it is wrapped in
     * a {@link ValueRetrievalException}
     * @param key the key whose associated value is to be returned
     * @return the value to which this cache maps the specified key
     * @throws ValueRetrievalException if the {@code valueLoader} throws an exception
     * @since 4.3
     */
    @Nullable
    <T> T get(Object key, Callable<T> valueLoader);

    /**
     * Associate the specified value with the specified key in this cache.
     * <p>If the cache previously contained a mapping for this key, the old
     * value is replaced by the specified value.
     * @param key the key with which the specified value is to be associated
     * @param value the value to be associated with the specified key
     */
    void put(Object key, @Nullable Object value);

    /**
     * Atomically associate the specified value with the specified key in this cache
     * if it is not set already.
     * <p>This is equivalent to:
     * <pre><code>
     * Object existingValue = cache.get(key);
     * if (existingValue == null) {
     *     cache.put(key, value);
     *     return null;
     * } else {
     *     return existingValue;
     * }
     * </code></pre>
     * except that the action is performed atomically. While all out-of-the-box
     * {@link CacheManager} implementations are able to perform the put atomically,
     * the operation may also be implemented in two steps, e.g. with a check for
     * presence and a subsequent put, in a non-atomic way. Check the documentation
     * of the native cache implementation that you are using for more details.
     * @param key the key with which the specified value is to be associated
     * @param value the value to be associated with the specified key
     * @return the value to which this cache maps the specified key (which may be
     * {@code null} itself), or also {@code null} if the cache did not contain any
     * mapping for that key prior to this call. Returning {@code null} is therefore
     * an indicator that the given {@code value} has been associated with the key.
     * @since 4.1
     */
    @Nullable
    ValueWrapper putIfAbsent(Object key, @Nullable Object value);

    /**
     * Evict the mapping for this key from this cache if it is present.
     * @param key the key whose mapping is to be removed from the cache
     */
    void evict(Object key);

    /**
     * Remove all mappings from the cache.
     */
    void clear();


    /**
     * A (wrapper) object representing a cache value.
     */
    @FunctionalInterface
    interface ValueWrapper {

        /**
         * Return the actual value in the cache.
         */
        @Nullable
        Object get();
    }


    /**
     * Wrapper exception to be thrown from {@link #get(Object, Callable)}
     * in case of the value loader callback failing with an exception.
     * @since 4.3
     */
    @SuppressWarnings("serial")
    class ValueRetrievalException extends RuntimeException {

        @Nullable
        private final Object key;

        public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
            super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
            this.key = key;
        }

        @Nullable
        public Object getKey() {
            return this.key;
        }
    }

}
View Code

  该接口定义了操作缓存的最基本行为

  同时Spring给我们提供了一些默认的缓存实现 比如说:JDK java.util.concurrent.ConcurrentMap,ehcache2.x , Gemfire cache, guava等

2、几个重要的注解

   @EnableCaching:用于开启缓存注解,通常需要和@Configuration使用

   @Cacheable 该注解的意思为:读缓存中的值,如果没有该值,那么将执行注解对应的方法并将方法返回结果存入缓存

   使用示例:

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
View Code

   有些时候我们希望根据条件将对应数据存入缓存,可以这么写:

@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)
View Code

   示例中:condition条件为true时,才存入缓存,unless属性是针对本次查询结果筛选。注意当表达式为true时的结果都不会存入缓存,  #result 代表了方法的返回结果

 

 

     @CacheEvict 触发Cache接口中的evict方法,相当于移除key对应的值

@CacheEvict(cacheNames="books", allEntries=true,beforeInvocation=true)
public void loadBooks(InputStream batch)
View Code

  beforeInvocation属性表明是否在执行方法之前清除缓存,allEntries属性会删除缓存下的所有数据,另外如果该值为true时就不能和key同时使用了

 

    @CachePut 执行方法并更新缓存

@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)
View Code

   这些缓存注解的幕后英雄当然是我们的AOP啦

3、缓存注解中的SpringEL表达式

  springEL可以运用在我们的cache注解当中,比如说其中key属性 condition属性等,其中#result只能在unless属性中使用这点比较特殊,在这里我贴出官网的列表:

Description Example

The name of the method being invoked

#root.methodName

The method being invoked

#root.method.name

The target object being invoked

#root.target

The class of the target being invoked

#root.targetClass

The arguments (as array) used for invoking the target

#root.args[0]

Collection of caches against which the current method is executed

#root.caches[0].name

Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0).

#iban or #a0 (one can also use #p0 or #p<#arg>notation as an alias).

The result of the method call (the value to be cached). Only available in unlessexpressions, cache putexpressions (to compute the key), or cache evictexpressions (when beforeInvocation is false). For supported wrappers such as Optional#resultrefers to the actual object, not the wrapper.

#result

4、代码集成示例

该代码是基于spring与hibernate集成,请参考:这里

gradle配置:

compile 'org.springframework.data:spring-data-redis:2.0.6.RELEASE'

    // https://mvnrepository.com/artifact/redis.clients/jedis
    compile group: 'redis.clients', name: 'jedis', version: '2.9.0'

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'

// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
    compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
View Code

 

1.创建RedisConfig:

package com.bdqn.lyrk.ssh.study.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

@Configuration
@EnableCaching
public class RedisConfig {

    /**
     * 创建redisConnectionFactory
     * @return
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        /*
            设置相关配置
         */
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setPort(6379);
        configuration.setHostName("localhost");
        configuration.setDatabase(0);
        JedisConnectionFactory jedisConnectionFactory =
                new JedisConnectionFactory(configuration);
        return jedisConnectionFactory;
    }

    @Bean
    public StringRedisTemplate stringRedisTemplate() {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
        return stringRedisTemplate;
    }


    @Bean
    public RedisCacheManager cacheManager() {
        /*
            配置json序列化方式
         */
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().
                serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(redisConnectionFactory()).
                transactionAware().cacheDefaults(cacheConfiguration).build();
    }
}
View Code

2.创建StudentService

package com.bdqn.lyrk.ssh.study.service;

import com.bdqn.lyrk.ssh.study.entity.StudentEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class StudentService {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    @Transactional
    public int save(StudentEntity studentEntity) {
        hibernateTemplate.save(studentEntity);
        return studentEntity.getId();
    }

    @Cacheable(key = "#id", cacheNames = "student", cacheManager = "cacheManager", condition = "#id==1")
    public StudentEntity listStudents(Integer id) {
        return hibernateTemplate.get(StudentEntity.class, id);
    }

    @Transactional
    @CachePut(key = "#id", cacheNames = "student", cacheManager = "cacheManager")
    public StudentEntity updateStudent(Integer id) {
        StudentEntity studentEntity = new StudentEntity();
        studentEntity.setId(id);
        studentEntity.setStuName("tests");
        studentEntity.setPassword("password");
        hibernateTemplate.update(studentEntity);
        return studentEntity;
    }

    @CacheEvict(key = "#id", cacheNames = "student")
    @Transactional
    public StudentEntity deleteStudent(Integer id) {
        StudentEntity studentEntity = hibernateTemplate.load(StudentEntity.class, id);
        hibernateTemplate.delete(studentEntity);
        return studentEntity;
    }
}
View Code

3.Main方法:

package com.bdqn.lyrk.ssh.study;

import com.bdqn.lyrk.ssh.study.config.AppConfig;
import com.bdqn.lyrk.ssh.study.service.StudentService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(AppConfig.class);
        StudentService studentService =
                applicationContext.getBean(StudentService.class);
        studentService.updateStudent(1);
        System.out.println(studentService.listStudents(1).getStuName());
        studentService.deleteStudent(1);
    }
}
View Code

运行结果:

 

我们可以看到:第二次查询时并没有输出Hibernate的执行的sql,说明这个是从redis缓存中读取的。 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
数据库
使用钉钉扫一扫加入圈子
+ 订阅

分享数据库前沿,解构实战干货,推动数据库技术变革

其他文章