一、redis快速入门
1、redis简介
在java领域,常见的四大缓存分别是ehcache,memcached,redis,guava-cache,其中redis与其他类型缓存相比,有着得天独厚的优势:
- 它是基于内存的数据库,什么意思呢?由于受磁盘IO影响,它所有操作都在内存当中,用以提高性能,同时采用异步的方式将数据保存在硬盘当中。
- 与memcached相比,redis支持多种数据类型,string,list,set,sorted set,hash。让我们使用起来更加灵活
- 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
- 丰富的特性:可用于缓存,消息,可以设置key的过期时间,过期后将会自动删除对应的记录
- 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 */ CollectiongetCacheNames(); }
在这里面最核心的接口当然是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. * * Note: Due to the generic use of caching, it is recommended that * implementations allow storage of null 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. *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. *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) */ @NullableT get(Object key, @Nullable Class 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. * 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. *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 */ @NullableT get(Object key, Callable valueLoader); /** * Associate the specified value with the specified key in this cache. * 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. *This is equivalent to: *
* 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; } } }* Object existingValue = cache.get(key); * if (existingValue == null) { * cache.put(key, value); * return null; * } else { * return existingValue; * } *
该接口定义了操作缓存的最基本行为
同时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)
有些时候我们希望根据条件将对应数据存入缓存,可以这么写:
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback") public Book findBook(String name)
示例中:condition条件为true时,才存入缓存,unless属性是针对本次查询结果筛选。注意当表达式为true时的结果都不会存入缓存, #result 代表了方法的返回结果
@CacheEvict
触发Cache接口中的evict方法,相当于移除key对应的值
@CacheEvict(cacheNames="books", allEntries=true,beforeInvocation=true) public void loadBooks(InputStream batch)
beforeInvocation属性表明是否在执行方法之前清除缓存,allEntries属性会删除缓存下的所有数据,另外如果该值为true时就不能和key同时使用了
@CachePut
执行方法并更新缓存
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
这些缓存注解的幕后英雄当然是我们的AOP啦
3、缓存注解中的SpringEL表达式
springEL可以运用在我们的cache注解当中,比如说其中key属性 condition属性等,其中#result只能在unless属性中使用这点比较特殊,在这里我贴出官网的列表:
Description | Example |
---|---|
The name of the method being invoked |
|
The method being invoked |
|
The target object being invoked |
|
The class of the target being invoked |
|
The arguments (as array) used for invoking the target |
|
Collection of caches against which the current method is executed |
|
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 |
|
The result of the method call (the value to be cached). Only available in |
|
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'
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(); } }
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; } }
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); } }
运行结果:
我们可以看到:第二次查询时并没有输出Hibernate的执行的sql,说明这个是从redis缓存中读取的。