• -------------------------------------------------------------
  • ====================================

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

技能 dewbay 5年前 (2019-04-12) 1566次浏览 已收录 0个评论 扫描二维码
  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
1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
经过以上配置后,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 redisTemplate(
    RedisConnectionFactory redisConnectionFactory)
    throws UnknownHostException {
    RedisTemplate template = new RedisTemplate();
    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;
    }

}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
然后 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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
根据以上的分析,我们知道在 spring 已经帮我们生成一个 RedisCacheManager 并进行了配置。
最后我们再可以对这个 RedisCacheManager 进行二次配置,这里只列出配置 key 的有效期

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

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

  1. 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 略

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BookQry : 封装请求类

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

// set/get 略

}
1
2
3
4
5
6
7
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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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";
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3.4. @CachePut
每次执行都会执行方法,无论缓存里是否有值,同时使用新的返回值的替换缓存中的值。这里不同于@Cacheable:@Cacheable 如果缓存没有值,从则执行方法并缓存数据,如果缓存有值,则从缓存中获取值

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

1
2
3
4
5
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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

  1. 测试
    4.1. 准备 redis
    可以通过 docker 安装 redis,非常方便。docker 的用法见 docker 安装和常用命令

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

  1. 代码

本文的详细代码 Github

作者:hry2015
来源:CSDN
原文:https://blog.csdn.net/hry2015/article/details/75451705
版权声明:本文为博主原创文章,转载请附上博文链接!


露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:Redis系列三 – Spring boot如何使用redis做缓存及缓存注解的用法总结
喜欢 (0)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址