您的位置:首页 > 数据库 > Redis

Redis系列三 - Spring boot如何使用redis做缓存及缓存注解的用法总结

2017-07-19 22:20 1366 查看

1. 概述

本文介绍Spring boot 如何使用redis做缓存,如何对redis缓存进行定制化配置(如key的有效期)以及spring boot 如何初始化redis做缓存。使用具体的代码介绍了@Cacheable,@CacheEvict,@CachePut,@CacheConfig等注解及其属性的用法。

2. spring boot集成redis

2.1. application.properties

配置application.properties,包含如下信息:

指定缓存的类型

配置redis的服务器信息

请不要配置spring.cache.cache-names值,原因后面再说

## 缓存
# spring.cache.cache-names=book1,book2
spring.cache.type=REDIS

# REDIS (RedisProperties)
spring.redis.database=0
spring.redis.host=192.168.188.7
spring.redis.password=
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=-1


2.2. 配置启动类

@EnableCaching: 启动缓存

重新配置RedisCacheManager,使用新的配置的值

@SpringBootApplication
@EnableCaching // 启动缓存
public class CacheApplication {
private static final Logger log = LoggerFactory.getLogger(CacheApplication.class);

public static void main(String[] args) {
log.info("Start CacheApplication.. ");
SpringApplication.run(CacheApplication.class, args);

}

/**
* 重新配置RedisCacheManager
* @param rd
*/
@Autowired
public void configRedisCacheManger(RedisCacheManager rd){
rd.setDefaultExpiration(100L);
}
}


经过以上配置后,redis缓存管理对象已经生成。下面简单介绍spring boot如何初始化redis缓存。

2.3. spring boot 如何初始化redis做缓存

缓存管理接口org.springframework.cache.CacheManager,spring boot就是通过此类实现缓存的管理。redis对应此接口的实现类是org.springframework.data.redis.cache.RedisCacheManager。下面介绍此类如何生成。

首先我们配置application.properties的spring.redis.* 属性后@EnableCaching后,spring会执行RedisAutoConfiguration,初始化RedisTemplate和StringRedisTemplate

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
….
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

}

}


然后RedisCacheConfiguration会将RedisAutoConfiguration生成的RedisTemplate注入方法生成RedisCacheManager 后。

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

private final CacheProperties cacheProperties;

private final CacheManagerCustomizers customizerInvoker;

RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}

@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}

}


根据以上的分析,我们知道在spring已经帮我们生成一个RedisCacheManager并进行了配置。

最后我们再可以对这个RedisCacheManager进行二次配置,这里只列出配置key的有效期

/**
* 重新配置RedisCacheManager
* @param rd
*/
@Autowired
public void configRedisCacheManger(RedisCacheManager rd){
rd.setDefaultExpiration(100L);
}


注意:

请不要在applicaion.properties中配置: spring.cache.cache-names=book1,book2,否则会导致我们新的配置无法作用到这些配置的cache上。这是因为RedisCacheConfiguration 初始化RedisCacheManager后,会立即调用RedisCacheConfiguration 的初始化cache,而此时configRedisCacheManger还没有执行此方法,使得我们的配置无法启作用。反之,如果不配置,则后创建cache,会使用我们的配置。

3. spring缓存注解的用法

上节已经介绍如何配置缓存,这节介绍如何使用缓存。

3.1 辅助类

下方会使用到的辅助类

Book:

public class Book implements Serializable {
private static final long serialVersionUID = 2629983876059197650L;

private String id;
private String name; // 书名
private Integer price; // 价格
private Date update; //

public Book(String id, String name, Integer price, Date update) {
super();
this.id = id;
this.name = name;
this.price = price;
this.update = update;
}
// set/get略
}


BookQry : 封装请求类

public class BookQry {
private String id;
private String name; // 书名

// set/get略
}


AbstractService

抽象类:初始化repositoryBook 值,模拟数据库数据。BookService 和BookService2都是继承此类

public abstract class AbstractService {

protected static Map<String, Book> repositoryBook = new HashMap<>();

public AbstractService() {
super();
}

@PostConstruct
public void init() {
// 1
Book book1 = new Book("1", "name_1", 11, new Date());
repositoryBook.put(book1.getId(), book1);
// 2
Book book2 = new Book("2", "name_2", 11, new Date());
repositoryBook.put(book2.getId(), book2);
// 3
Book book3 = new Book("3", "name_3", 11, new Date());
repositoryBook.put(book3.getId(), book3);
// 4
Book book4 = new Book("4", "name_4", 11, new Date());
repositoryBook.put(book4.getId(), book4);
}

}


3.2. @Cacheable

@Cacheable的属性的意义

cacheNames:指定缓存的名称

key:定义组成的key值,如果不定义,则使用全部的参数计算一个key值。可以使用spring El表达式

/**
* cacheNames 设置缓存的值
*  key:指定缓存的key,这是指参数id值。 key可以使用spEl表达式
* @param id
* @return
*/
@Cacheable(cacheNames="book1", key="#id")
public Book queryBookCacheable(String id){
logger.info("queryBookCacheable,id={}",id);
return repositoryBook.get(id);
}

/**
* 这里使用另一个缓存存储缓存
*
* @param id
* @return
*/
@Cacheable(cacheNames="book2", key="#id")
public Book queryBookCacheable_2(String id){
logger.info("queryBookCacheable_2,id={}",id);
return repositoryBook.get(id);
}

/**
* 缓存的key也可以指定对象的成员变量
* @param qry
* @return
*/
@Cacheable(cacheNames="book1", key="#qry.id")
public Book queryBookCacheableByBookQry(BookQry qry){
logger.info("queryBookCacheableByBookQry,qry={}",qry);
String id = qry.getId();
Assert.notNull(id, "id can't be null!");
String name = qry.getName();
Book book = null;
if(id != null){
book = repositoryBook.get(id);
if(book != null && !(name != null && book.getName().equals(name))){
book = null;
}
}
return book;
}


keyGenerator:定义key生成的类,和key的不能同时存在

/**
* 以上我们使用默认的keyGenerator,对应spring的SimpleKeyGenerator
*  如果你的使用很复杂,我们也可以自定义myKeyGenerator的生成key
*
*  key和keyGenerator是互斥,如果同时制定会出异常
*  The key and keyGenerator parameters are mutually exclusive and an operation specifying both will result in an exception.
*
* @param id
* @return
*/
@Cacheable(cacheNames="book3",  keyGenerator="myKeyGenerator")
public Book queryBookCacheableUseMyKeyGenerator(String id){
logger.info("queryBookCacheableUseMyKeyGenerator,id={}",id);
return repositoryBook.get(id);
}

// 自定义缓存key的生成类实现如下:
@Component
public class MyKeyGenerator implements KeyGenerator {

@Override
public Object generate(Object target, Method method, Object... params) {
System.out.println("自定义缓存,使用第一参数作为缓存key. params = " + Arrays.toString(params));
// 仅仅用于测试,实际不可能这么写
return params[0] + "0";
}

}


sync:如果设置sync=true:a. 如果缓存中没有数据,多个线程同时访问这个方法,则只有一个方法会执行到方法,其它方法需要等待; b. 如果缓存中已经有数据,则多个线程可以同时从缓存中获取数据

/***
* 如果设置sync=true,
*  如果缓存中没有数据,多个线程同时访问这个方法,则只有一个方法会执行到方法,其它方法需要等待
*  如果缓存中已经有数据,则多个线程可以同时从缓存中获取数据
* @param id
* @return
*/
@Cacheable(cacheNames="book3", sync=true)
public Book queryBookCacheableWithSync(String id) {
logger.info("begin ... queryBookCacheableByBookQry,id={}",id);
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
}
logger.info("end ... queryBookCacheableByBookQry,id={}",id);
return repositoryBook.get(id);
}


condition和unless 只满足特定条件才进行缓存:

condition: 在执行方法前,condition的值为true,则缓存数据

unless :在执行方法后,判断unless ,如果值为true,则不缓存数据

conditon和unless可以同时使用,则此时只缓存同时满足两者的记录

/**
* 条件缓存:
* 只有满足condition的请求才可以进行缓存,如果不满足条件,则跟方法没有@Cacheable注解的方法一样
*  如下面只有id < 3才进行缓存
*
*/
@Cacheable(cacheNames="book11", condition="T(java.lang.Integer).parseInt(#id) < 3 ")
public Book queryBookCacheableWithCondition(String id) {
logger.info("queryBookCacheableByBookQry,id={}",id);
return repositoryBook.get(id);
}

/**
* 条件缓存:
* 对不满足unless的记录,才进行缓存
*  "unless expressions" are evaluated after the method has been called
*  如下面:只对不满足返回 'T(java.lang.Integer).parseInt(#result.id) <3 ' 的记录进行缓存
* @param id
* @return
*/
@Cacheable(cacheNames="book22", unless = "T(java.lang.Integer).parseInt(#result.id) <3 ")
public Book queryBookCacheableWithUnless(String id) {
logger.info("queryBookCacheableByBookQry,id={}",id);
return repositoryBook.get(id);
}


3.3. @CacheEvict

删除缓存

allEntries = true: 清空缓存book1里的所有值

allEntries = false: 默认值,此时只删除key对应的值

/**
* allEntries = true: 清空book1里的所有缓存
*/
@CacheEvict(cacheNames="book1", allEntries=true)
public void clearBook1All(){
logger.info("clearAll");
}
/**
* 对符合key条件的记录从缓存中book1移除
*/
@CacheEvict(cacheNames="book1", key="#id")
public void updateBook(String id, String name){
logger.info("updateBook");
Book book = repositoryBook.get(id);
if(book != null){
book.setName(name);
book.setUpdate(new Date());
}
}


3.4. @CachePut

每次执行都会执行方法,无论缓存里是否有值,同时使用新的返回值的替换缓存中的值。这里不同于@Cacheable:@Cacheable如果缓存没有值,从则执行方法并缓存数据,如果缓存有值,则从缓存中获取值

@CachePut(cacheNames="book1", key="#id")
public Book queryBookCachePut(String id){
logger.info("queryBookCachePut,id={}",id);
return repositoryBook.get(id);
}


3.5. @CacheConfig

@CacheConfig: 类级别的注解:如果我们在此注解中定义cacheNames,则此类中的所有方法上 @Cacheable的cacheNames默认都是此值。当然@Cacheable也可以重定义cacheNames的值

@Component
@CacheConfig(cacheNames="booksAll")
public class BookService2 extends AbstractService {
private static final Logger logger = LoggerFactory.getLogger(BookService2.class);

/**
* 此方法的@Cacheable没有定义cacheNames,则使用类上的注解@CacheConfig里的值 cacheNames
* @param id
* @return
*/
@Cacheable(key="#id")
public Book queryBookCacheable(String id){
logger.info("queryBookCacheable,id={}",id);
return repositoryBook.get(id);
}

/**
* 此方法的@Cacheable有定义cacheNames,则使用此值覆盖类注解@CacheConfig里的值cacheNames
*
* @param id
* @return
*/
@Cacheable(cacheNames="books_custom", key="#id")
public Book queryBookCacheable2(String id){
logger.info("queryBookCacheable2,id={}",id);
return repositoryBook.get(id);
}
}


4. 测试

4.1. 准备 redis

可以通过docker安装redis,非常方便。docker的用法见docker 安装和常用命令

4.2. 测试类

如果要验证以上各个方法,可以下载工程,并执行测试类CacheTest。由于比较简单,这里不在演示。

5. 代码

本文的详细代码Github
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: