zoukankan      html  css  js  c++  java
  • 补习系列(14)-springboot redis 整合-数据读写

    目录

    一、简介

    在 补习系列(A3)-springboot redis 与发布订阅 一文中,我们介绍了使用 Redis 实现消息订阅发布的机制,并且给出了一个真实用例。
    然而,绝大多数场景下 Redis 是作为缓存被使用的(这是其主要优势)。除此之外,由于Redis 提供了 AOF以及RDB两种持久化机制,某些情况下也可以作为临时数据库使用。
    本次将介绍 SpringBoot 中如何使用 Redis 进行缓存读写。

    Redis 的基本命令
    在学习之前,需要先了解一些Redis 的基本命令,可以参考这里
    http://www.redis.cn/

    二、SpringBoot Redis 读写

    A. 引入 spring-data-redis

    添加依赖

     <!-- redis -->
      <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
       <version>${spring-boot.version}</version>
      </dependency>

    spring-boot-starter-redis在1.4版本已经废弃

    配置redis连接
    application.properties

    # redis 连接配置
    spring.redis.database=0 
    spring.redis.host=127.0.0.1
    spring.redis.password=
    spring.redis.port=6379
    spring.redis.ssl=false
    
    # 连接池最大数
    spring.redis.pool.max-active=10 
    # 空闲连接最大数
    spring.redis.pool.max-idle=10
    # 获取连接最大等待时间(s)
    spring.redis.pool.max-wait=600000

    B. 序列化

    同样,我们需要指定 JSON作为 Key/HashKey/Value的主要方式:

        /**
         * 序列化定制
         * 
         * @return
         */
        @Bean
        public Jackson2JsonRedisSerializer<Object> jackson2JsonSerializer() {
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                    Object.class);
    
            // 初始化objectmapper
            ObjectMapper mapper = new ObjectMapper();
            mapper.setSerializationInclusion(Include.NON_NULL);
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(mapper);
            return jackson2JsonRedisSerializer;
        }
    
       /**
         * 操作模板
         * 
         * @param connectionFactory
         * @param jackson2JsonRedisSerializer
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory connectionFactory,
                Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer) {
    
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(connectionFactory);
    
            // 设置key/hashkey序列化
            RedisSerializer<String> stringSerializer = new StringRedisSerializer();
            template.setKeySerializer(stringSerializer);
            template.setHashKeySerializer(stringSerializer);
    
            // 设置值序列化
            template.setValueSerializer(jackson2JsonRedisSerializer);
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
    
            // template.setValueSerializer(new
            // GenericToStringSerializer<Object>(Object.class));
            return template;
        }

    Jackson2JsonRedisSerializer是Jackson转换的桥接器;
    RedisTemplate是用于读写的主要操作类;

    C. 读写样例

    首先定义一个Pet实体类

    public class RedisPet {
    
        private String name;
        private String type;
    ... ignore get set

    利用RedisTemplate封装一层Repository,如下:

        @Repository
        public static class PetRepository {
    
            private static final String KEY = "Pets";
    
            @Autowired
            private RedisTemplate<String, Object> redisTemplate;
            private HashOperations<String, String, Object> hashOperations;
    
            @PostConstruct
            private void init() {
                hashOperations = redisTemplate.opsForHash();
            }
    
            public void add(RedisPet pet) {
                hashOperations.put(KEY, pet.getName(), pet);
            }
    
            public RedisPet find(String name) {
                return (RedisPet) hashOperations.get(KEY, name);
            }
    
            public Map<String, Object> findAll() {
                return hashOperations.entries(KEY);
            }
            
            public void clear() {
                hashOperations.getOperations().delete(KEY);
            }
        }

    PetRepository 的实现中,我们利用Hash结构来存储 Pet信息(Pet.name是key)
    分别实现了添加(add)/查找(get)/清除(clear)等方法。

    最后,实现读写调用:

    
    @Service
    public class RedisDataOperation {
    
        private static final Logger logger = LoggerFactory.getLogger(RedisDataOperation.class);
    
        @Autowired
        private PetRepository petRepo;
    
    
        @PostConstruct
        public void start() {
    
            RedisPet pet1 = new RedisPet("Polly", "Bird");
            RedisPet pet2 = new RedisPet("Tom", "Cat");
    
            //写入宠物信息
            petRepo.add(pet1);
            petRepo.add(pet2);
    
            //打印宠物信息
            logger.info("polly {}", JsonUtil.toJson(petRepo.find("Polly")));
            logger.info("pets  {}", JsonUtil.toJson(petRepo.findAll()));
    
            //清空
            petRepo.clear();
        }

    上面的代码在应用启动时,会写入两个Pet信息,之后完成清理,控制台输出如下:

    RedisDataOperation : polly {"name":"Polly","type":"Bird"}
    RedisDataOperation : pets {"Tom":{"name":"Tom","type":"Cat"},"Polly":{"name":"Polly","type":"Bird"}}

    三、方法级缓存

    除了上面的RedisTemplate,spring-data-redis还提供了方法级缓存,
    就是将业务方法的执行结果缓存起来,后面再次调用直接从缓存中取得结果返回。

    这种方式可以简化缓存逻辑的代码,比如配置类数据的读取,通过方法注解就可以实现,
    下面是一个样例:

    /**
     * 方法级缓存样例
     * 
     * @author atp
     *
     */
    @Service
    public class RedisCacheOperation {
    
        private static final Logger logger = LoggerFactory.getLogger(RedisCacheOperation.class);
    
        public static final String PREFIX = "pets:";
        public static final String WRAP_PREFIX = "'pets:'";
    
        /**
         * 当结果不为空时缓存
         * 
         * @param name
         * @return
         */
        @Cacheable(value = "petCache", key = WRAP_PREFIX + "+#name", unless = "#result==null")
        public RedisPet getPet(String name) {
            logger.info("get pet {}", name);
            return new RedisPet(name, "Bird");
        }
    
        /**
         * 当结果不为空时淘汰缓存
         * 
         * @param pet
         * @return
         */
        @CacheEvict(value = "petCache", key = WRAP_PREFIX + "+#pet.name", condition = "#result!=null")
        public RedisPet updatePet(RedisPet pet) {
            logger.info("update pet {}", pet.getName());
            return new RedisPet(pet.getName(), "Bird1");
        }
    
        /**
         * 当结果为true时淘汰缓存
         * 
         * @param name
         * @return
         */
        @CacheEvict(value = "petCache", key = WRAP_PREFIX + "+#name", condition = "#result==true")
        public boolean deletePet(String name) {
            logger.info("delete pet {}", name);
            return true;
        }
    }

    涉及到几个注解:

    注解 说明
    @Cachable 方法执行结果缓存
    @CachePut 方法执行结果缓存(强制)
    @CacheEvict 方法执行时触发删除

    其中 @CachePut 与 @Cachable 的区别在于,前者一定会执行方法,并尝试刷新缓存(条件满足),
    而后者则是当缓存中不存在时才会执行方法并更新。
    注解中的属性 key/condition 都支持通过 Spring EL 表达式来引用参数对象。

    启用注解

    除了上面的代码,我们还需要使用 @EnableCaching 启用注解:

    @EnableCaching
    @Configuration
    public class RedisConfig {
    
        private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
    
        /**
         * 缓存管理,支持方法级注解
         * 
         * @param template
         * @return
         */
        @Bean
        public RedisCacheManager cacheManager(RedisTemplate<String, Object> template) {
            RedisCacheManager redisCacheManager = new RedisCacheManager(template);
            // 默认过期时间
            redisCacheManager.setDefaultExpiration(30 * 60 * 1000);
            return redisCacheManager;
        }

    当@Cacheable 的key属性为空时,框架会自动生成,格式类似:

    param1,param2,param3...

    如果希望修改默认的行为,可以使用自定义的 KeyGenerator

        /**
         * 定制方法缓存的key生成策略
         *
         * @return
         */
        @Bean
        public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... args) {
                    StringBuilder sb = new StringBuilder();
                    sb.append(target.getClass().getName());
                    sb.append(method.getName());
    
                    for (Object arg : args) {
                        sb.append(arg.toString());
                    }
                    return sb.toString();
                }
            };
        }

    单元测试

    使用一小段单元测试代码来测试方法级缓存功能

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes =BootSampleRedis.class)
    public class RedisCacheOperationTest {
    
        private static final Logger logger = LoggerFactory.getLogger(RedisCacheOperationTest.class);
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        @Autowired
        private RedisCacheOperation operation;
    
        private RedisPet pet1 = new RedisPet("Polly", "Bird");
    
        @Test
        public void testGet() {
            operation.getPet(pet1.getName());
    
            Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
            logger.info(String.valueOf(object));
    
            assertNotNull(object);
        }
    
        @Test
        public void testUpdate() {
            operation.updatePet(pet1);
    
            Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
            logger.info(String.valueOf(object));
    
            assertNull(object);
        }
    
        @Test
        public void testDelete() {
            operation.getPet(pet1.getName());
    
            // delete cache
            operation.deletePet(pet1.getName());
    
            Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
            logger.info(String.valueOf(object));
    
            assertNull(object);
        }
    
    }

    四、连接池

    如果希望通过代码来配置 Jedis 的连接池(熟悉的方式),可以声明 JedisConnectionFactory 实现:

        /**
         * 连接池配置
         *
         * @return
         */
        @Bean
        public JedisConnectionFactory jedisConnectionFactory() {
            JedisPoolConfig config = new JedisPoolConfig();
    
            // 最大连接
            config.setMaxTotal(10);
            // 最大空闲,与最大连接保持一致,可减少频繁键链的开销
            config.setMaxIdle(10);
            // 连接最大空闲时间
            config.setMinEvictableIdleTimeMillis(10 * 60 * 1000);
            // 获取连接等待的最大时长
            config.setMaxWaitMillis(30000);
    
            // 进行空闲连接检测的时间间隔
            config.setTimeBetweenEvictionRunsMillis(30 * 1000);
            // 取消不必要的test,有利于性能提升
            config.setTestOnBorrow(false);![](https://img2018.cnblogs.com/blog/242916/201812/242916-20181206231048870-1133770725.png)
    
            config.setTestOnReturn(false);
    
            JedisConnectionFactory factory = new JedisConnectionFactory(config);
            factory.setHostName("127.0.0.1");
            factory.setPort(6379);
    
            logger.info("redis config init first");
            return factory;
        }

    更多配置可参考这里

    示例代码可从 码云gitee 下载。
    https://gitee.com/littleatp/springboot-samples/

    小结

    Redis 在大多数项目中的核心用途是缓存,spring-data-redis 为 SpringBoot 中集成 Redis 读写的封装。
    除了 RedisTemplate之外,还实现了方法级的缓存注解,一定程度上简化了业务的使用。
    Redis 在分布式系统中的应用场景有很多,后续有机会将进行更多的探讨。

    欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^

    作者:美码师

  • 相关阅读:
    HTML DOM 12 表格排序
    HTML DOM 10 常用场景
    HTML DOM 10 插入节点
    HTML DOM 09 替换节点
    HTML DOM 08 删除节点
    HTML DOM 07 创建节点
    022 注释
    024 数字类型
    005 基于面向对象设计一个简单的游戏
    021 花式赋值
  • 原文地址:https://www.cnblogs.com/huaweicloud/p/11861493.html
Copyright © 2011-2022 走看看