基于Redis实现SpringCache

在Java的世界里,当我们需要缓存一个对象时,我们会想到用Guava、Ehcache、Memcache、Redis ,这些缓存框架各有优劣,我今天想给大家介绍一个Spring世界里使用的缓存框架。理解SpringCache,我们可以把上述guava\ehcache等当成mysql-jdbc-driver/oracle-jdbc-driver ,而SpringCache就相当于是jdbc标准。

注解

SpringCache里面通过使用如下几个注解来实现各种缓存功能:

  • @EnableCaching 开启缓存功能
  • @CacheConfig 通用缓存设置,Class维度
  • @Cacheable 定义方法有缓存功能
  • @CachePut 触发更新缓存
  • @CacheEvict 触发删除缓存
  • @Caching 组合定义多种缓存功能一次性定义:Cacheable、CachePut、CacheEvict

使用缓存前的准备工作

环境依赖


        org.springframework.boot
        spring-boot-starter-parent
        2.1.9.RELEASE
         
        
    
        
            org.springframework.boot
            spring-boot-starter-cache
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
            ai.grakn
            redis-mock
            0.1.6
        
        
            com.alibaba
            fastjson
            1.2.61
        

开启缓存

SpringBoot开启缓存

@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
}

基于Redis实现分布式缓存

@EnableCaching
@Configuration
public class CacheConfig {
    @Configuration
public class CacheConfig {

    @Primary
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //缓存配置对象
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();

        redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
                .disableCachingNullValues()             //如果是空值,不缓存
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))         //设置key序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer())));  //设置value序列化器

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    private RedisSerializer keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 自定义值序列化-json序列化
     * @return
     */
    private RedisSerializer valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}
}

一种通用自定义key生成方式

@Slf4j
@Configuration("keyGenerator")
public class CacheKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        String simpleName = target.getClass().getSimpleName();
        //如果是代理类去掉后缀
        simpleName=simpleName.split("\\$\\$")[0];
        key.append(simpleName).append(":").append(method.getName()).append(":");
        String finalKey = key.toString();
        if (params.length == 0) {
            return finalKey;
        }
        for (int i = 0; i < params.length; i++) {
            Object param = params[i];
            if (param == null) {
                del(key);
            } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                int length = Array.getLength(param);
                for (int j = 0; j < length; j++) {
                    key.append(Array.get(param, j));
                    key.append(',');
                }
            } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                key.append(param);
            } else {
                key.append(param.toString());
            }
            key.append('-');
        }
        del(key);
        log.debug("final key:{}",key);
        return finalKey;
    }

    private StringBuilder del(StringBuilder stringBuilder) {
        if (stringBuilder.toString().endsWith("-")) {
            stringBuilder.deleteCharAt(stringBuilder.length() - 1);
        }
        return stringBuilder;
    }
}

查询缓存

/**
     * result 不为空时缓存
     * @param id
     * @return
     */
    @Cacheable(value = "user",key = "#id" ,unless="#result == null")
    public User findById(Integer id){
        System.out.println(String.format("id[%s]未命中缓存", id));
        return kvStore.get(id);
    }

更新缓存

/**
     * 更新成功时更新缓存
     * @param id
     * @param userDto
     * @return
     */
    @CachePut(value = "user",key = "#id",unless="#result == null")
    public User upateUser(Integer id,UserDto userDto){
        if(kvStore.containsKey(id)){
            User user = User.builder().build();
            BeanUtils.copyProperties(userDto, user);
            kvStore.put(id, user);
            return user;
        }else{
            return null;
        }
    }

删除缓存


    /**
     * 移除
     * @param id
     * @return
     */
    @CacheEvict(value = "user",key = "#id" )
    public User removeById(Integer id){
        return kvStore.remove(id);
    }

测试运行

package com.example.rediscache;

import ai.grakn.redismock.RedisServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;

import java.io.IOException;

@SpringBootApplication
@EnableCaching
public class RedisCacheApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(RedisCacheApplication.class, args);
    }
    @Autowired
    UserService userService;
    @Override
    public void run(String... args) throws Exception {
      
        mockRedisServerStart();
        System.out.println(userService.findById(1));
        System.out.println("插入id:1 name=张三");
        userService.insertUser(User.builder().id(1).name("张三").build());

        System.out.println("查询id:1 "+userService.findById(1));
        System.out.println("更新id:1 name=张三(新)");
        userService.upateUser(1, UserDto.builder().name("张三(新)").build());

        System.out.println("查询id:1 "+userService.findById(1));

        userService.removeById(1);
        System.out.println("移除id:1");
        System.out.println("查询id:1 "+userService.findById(1));
    }
    private void mockRedisServerStart() {
        RedisServer redisServer = null;
        try {
            redisServer = RedisServer.newRedisServer(6379);
            redisServer.start();
            int bindPort = redisServer.getBindPort();
            String host = redisServer.getHost();
            System.out.println(String.format("redis server start on host:%s port %s", host,bindPort));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行结果

image.png

使用SpringCache缓存Java方法示例

注意事项

  • 1、如果出现反序列化异常。可能的原因缓存对象Class发生了变化,我们通常会将缓存删掉,所以我们可以在反序列化失败时捕捉异常,并将产生异常的缓存key删除掉.
  • 2、如果KeyGenerator与类的包路径有关,调整包路径时可能会造成key无法命中缓存

你可能感兴趣的:(基于Redis实现SpringCache)