Springboot中Spring-cache与redis整合

也是在整合redis的时候偶然间发现spring-cache的。这也是一个不错的框架,与spring的事务使用类似,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作。如果这些注解不满足项目的需求,我们也可以参考spring-cache的实现思想,使用AOP代理+缓存操作来管理缓存的使用。
在这个例子中我使用的是redis,当然,因为spring-cache的存在,我们可以整合多样的缓存技术,例如Ecache、Mamercache等。
下面来看springcache的具体操作吧!
附上官方的文档:
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html

redis中整合spring-cache

我代码的工程还是接着上个项目来使用的, 所以可以结合上一篇博客来看
http://blog.csdn.net/u011521890/article/details/78070773
或者直接找github,欢迎star
https://github.com/hpulzl/book_recommend

缓存的配置如下

  • 在RedisCacheConfig上添加注解
@EnableCaching //加上这个注解是的支持缓存注解
  • 创建RedisCacheManager
 /**
     * 设置RedisCacheManager
     * 使用cache注解管理redis缓存
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
        return redisCacheManager;
    }
  • 自定义缓存的key
 /**
     * 自定义生成redis-key
     *
     * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                sb.append(o.getClass().getName()).append(".");
                sb.append(method.getName()).append(".");
                for (Object obj : objects) {
                    sb.append(obj.toString());
                }
                System.out.println("keyGenerator=" + sb.toString());
                return sb.toString();
            }
        };
    }

在RedisCacheConfig中添加以上的代码,就可以使用springcache的注解了。下面介绍springcache的注解如何使用

spring cache与redis缓存结合

对springCache概念的了解

springCache支持透明的添加缓存到应用程序,类似事务处理一般,不需要复杂的代码支持。

缓存的主要使用方式包括以下两方面
1. 缓存的声明,需要根据项目需求来妥善的应用缓存
2. 缓存的配置方式,选择需要的缓存支持,例如Ecache、redis、memercache等

缓存的注解介绍

@Cacheable 触发缓存入口

@CacheEvict 触发移除缓存

@CacahePut 更新缓存

@Caching 将多种缓存操作分组

@CacheConfig 类级别的缓存注解,允许共享缓存名称

@CacheConfig

该注解是可以将缓存分类,它是类级别的注解方式。我们可以这么使用它。
这样的话,UseCacheRedisService的所有缓存注解例如@Cacheable的value值就都为user。

@CacheConfig(cacheNames = "user")
@Service
public class UseCacheRedisService {}

在redis的缓存中显示如下

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_1"
127.0.0.1:6379> get user~keys
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type user~keys
zset
127.0.0.1:6379> zrange user~keys 0 10
1) "user_1"

我们注意到,生成的user~keys,它是一个zset类型的key,如果使用get会报WRONGTYPE Operation against a key holding the wrong kind of value。这个问题坑了我很久

@Cacheable

一般用于查询操作,根据key查询缓存.


  1. 如果key不存在,查询db,并将结果更新到缓存中。
  2. 如果key存在,直接查询缓存中的数据。


查询的例子,当第一查询的时候,redis中不存在key,会从db中查询数据,并将返回的结果插入到redis中。

@Cacheable
    public List selectAllUser(){
        return userMapper.selectAll();
    }

调用方式。

@Test
    public void selectTest(){
        System.out.println("===========第一次调用=======");
        List list = useCacheRedisService.selectAllUser();
        System.out.println("===========第二次调用=======");
        List list2 = useCacheRedisService.selectAllUser();
        for (User u : list2){
            System.out.println("u = " + u);
        }
    }

打印结果,大家也可以试一试
只输出一次sql查询的语句,说明第二次查询是从redis中查到的。

===========第一次调用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
DEBUG [main] - ==>  Preparing: SELECT id,name,sex,age,password,account FROM user 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
===========第二次调用=======
keyGenerator=com.lzl.redisService.UseCacheRedisService.selectAllUser.
u = User{id=1, name='fsdfds', sex='fdsfg', age=24, password='gfdsg', account='gfds'}

redis中的结果
我们可以看到redis中已经存在
com.lzl.redisService.UseCacheRedisService.selectAllUser.记录了。
这么长的一串字符key是根据自定义key值生成的。

127.0.0.1:6379> keys *
1) "user~keys"
2) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
3) "user_1"
127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.selectAllUser.
"[\"java.util.ArrayList\",[[\"com.lzl.bean.User\",{\"id\":1,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]]]"

@CachePut

一般用于更新和插入操作,每次都会请求db
通过key去redis中进行操作。
1. 如果key存在,更新内容
2. 如果key不存在,插入内容。

 /**
     * 单个user对象的插入操作,使用user+id
     * @param user
     * @return
     */
    @CachePut(key = "\"user_\" + #user.id")
    public User saveUser(User user){
        userMapper.insert(user);
        return user;
    }

redis中的结果
多了一条记录user_2

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "com.lzl.redisService.UseCacheRedisService.selectAllUser."
4) "user_1"
127.0.0.1:6379> get user_2
"[\"com.lzl.bean.User\",{\"id\":2,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]"

@CacheEvict

根据key删除缓存中的数据。allEntries=true表示删除缓存中的所有数据。

 @CacheEvict(key = "\"user_\" + #id")
    public void deleteById(Integer id){
        userMapper.deleteByPrimaryKey(id);
    }

测试方法

 @Test
    public void deleteTest(){
        useCacheRedisService.deleteById(1);
    }

redis中的结果
user_1已经移除掉。

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "com.lzl.redisService.UseCacheRedisService.selectAllUser."

测试allEntries=true时的情形。

 @Test
    public void deleteAllTest(){
        useCacheRedisService.deleteAll();
    }
 @CacheEvict(allEntries = true)
    public void deleteAll(){
        userMapper.deleteAll();
    }

redis中的结果
redis中的数据已经全部清空

127.0.0.1:6379> keys *
(empty list or set)

@Caching

通过注解的属性值可以看出来,这个注解将其他注解方式融合在一起了,我们可以根据需求来自定义注解,并将前面三个注解应用在一起

public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}

使用例子如下

 @Caching(
            put = {
                    @CachePut(value = "user", key = "\"user_\" + #user.id"),
                    @CachePut(value = "user", key = "#user.name"),
                    @CachePut(value = "user", key = "#user.account")
            }
    )
    public User saveUserByCaching(User user){
        userMapper.insert(user);
        return user;
    }
 @Test
    public void saveUserByCachingTest(){
        User user = new User();
        user.setName("dkjd");
        user.setAccount("dsjkf");
        useCacheRedisService.saveUserByCaching(user);
    }

redis中的执行结果
一次添加三个key

127.0.0.1:6379> keys *
1) "user~keys"
2) "dsjkf"
3) "dkjd"
4) "user_3"

结合@Caching还可以设置自定义的注解

自定义注解

@Caching(
        put = {
                @CachePut(value = "user", key = "\"user_\" + #user.id"),
                @CachePut(value = "user", key = "#user.name"),
                @CachePut(value = "user", key = "#user.account")
        }
)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SaveUserInfo {

}

使用如下

@SaveUserInfo
    public User saveUserByInfo(User user){
        userMapper.insert(user);
        return user;
    }

测试

@Test
    public void saveUserByInfoTest(){
        User user = new User();
        user.setName("haha");
        user.setAccount("hhhcc");
        useCacheRedisService.saveUserByInfo(user);
    }

redis结果

127.0.0.1:6379> keys *
1) "user_4"
2) "dsjkf"
3) "dkjd"
4) "user~keys"
5) "haha"
6) "hhhcc"
7) "user_3"

通过以上的例子基本可以了解springcache的使用了,当然还有更加复杂的操作,这里只是简单的介绍一下,运用到实际的项目中还是有所欠缺的。不过有这个基础应该不会太难。同时有时间可以再研究一下spring-cache的实现原理。是基于AOP的实现的,这也是我们在项目中学习的地方。

遇到的两个问题

WRONGTYPE Operation against a key holding the wrong kind of value

这个就是上面所说的类型不一致,使用redis命令不当造成的。所以在查找redis的value时候,需要知道key的类型。

type key

Invalid argument(s)

还是redis现实的错误,这个有些困惑,在get的时候,一定要加上”“(引号)才行。

127.0.0.1:6379> keys *
1) "user_4"
2) "com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
3) "dsjkf"
4) "dkjd"
5) "user~keys"
6) "haha"
7) "hhhcc"
8) "user_3"
127.0.0.1:6379> get com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> type com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> get "com.lzl.redisService.UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
"[\"com.lzl.bean.User\",{\"id\":5,\"name\":\"fsdsg\",\"sex\":\"vcxvx\",\"age\":24,\"password\":\"vcxvcxc\",\"account\":\"vxcvxc\"}]"

你可能感兴趣的:(springboot)