补习系列(14)-springboot redis 整合-数据读写

  目录
  
  一、简介
  
  二、SpringBoot Redis 读写
  
  A. 引入 spring-data-redis
  
  B. 序列化
  
  C. 读写样例
  
  三、方法级缓存
  
  四、连接池
  
  小结
  
  一、简介
  
  在 补习系列(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 =www.quwanyule157.com new RedisPet("Polly", "Bird");
  
  @Test
  
  public void testGet(www.mingcheng178.com/ ) {
  
  operation.getPet(pet1.getName());
  
  Object object =www.feifanyule.cn/ redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
  
  logger.info(String.valueOf(object));
  
  assertNotNull(object);
  
  }
  
  @Test
  
  public void testUpdate(www.trgj888.com) {
  
  operation.updatePet(pet1);
  
  Object object = redisTemplate.opsForValue().get(RedisCacheOperation.PREFIX + pet1.getName());
  
  logger.info(String.www.gcyL157.com 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 =www.michenggw.com 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 www.yigouyule2.cn config init first");
  
  return factory;
  
  }
  
  更多配置可参考这里
  
  示例代码可从 码云gitee 下载。
  
  https://gitee.com/littleatp/springboot-samples/
  
  小结
  
  Redis 在大多数项目中的核心用途是缓存,spring-data-redis 为 SpringBoot 中集成 Redis 读写的封装。
  
  除了 RedisTemplate之外,还实现了方法级的缓存注解,一定程度上简化了业务的使用。
  
  Redis 在分布式系统中的应用场景有很多,后续有机会将进行更多的探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值