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

缓存的配置如下

1、在RedisCacheConfig上添加注解

@EnableCaching  加上这个注解是的支持缓存注解

2、创建RedisCacheManager

    /**
     * 设置RedisCacheManager
     * 使用cache注解管理redis缓存
     *
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
        return redisCacheManager;
    }

3、自定义缓存的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查询缓存.


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

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

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

调用方式。

    @Test
    public void selectTest(){
        System.out.println("===========第一次调用=======");
        List<User> list = useCacheRedisService.selectAllUser();
        System.out.println("===========第二次调用=======");
        List<User> 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\"}]"

spring boot从redis取缓存发生java.lang.ClassCastException异常

      解决方法将devtools热部署注释掉。

        仔细看你会发现这个类转换异常很奇怪,为什么呢?我们注意到这两个User不管是包名还是类名是完全一样的[ java.lang.ClassCastExceptioncom.winds.admin.core.model.system.User cannot be cast tocom.winds.admin.core.model.system.User  ],但也发生了强制转换异常,那么还有什么原因会引起这种情况呢?那就只有一种情况了:使用的类加载器不一样。主要原因是pom文件中引入了DevTools配置。 当你使用DevTools进行缓存时,需要了解这一限制。 当对象序列化到缓存中时,应用程序类加载器是C1。然后,更改一些代码或者配置后,devtools会自动重新启动上下文并创建一个新的类加载器C2。所以当你通过redis操作获取缓存反序列化的时候应用的类加载器是C2,虽然包名及其来类名完全一致,但是序列化与反序列化是通过不同的类加载器加载则在JVM中它们也不是同一个类。如果缓存库没有考虑上下文类加载器,那么这个对象会附加错误的类加载器 ,也就是我们常见的类强制转换异常(ClassCastException)。

java.lang.IllegalArgumentException: Null key returned for cache operation

        @Cacheable key 中值为null 报得错误 尽量

猜你喜欢

转载自blog.csdn.net/Keith003/article/details/84327352