易筋SpringBoot 2.1 | 第廿七篇:SpringBoot之Redis Template和Repository

写作时间:2019-10-05
Spring Boot: 2.1 ,JDK: 1.8, IDE: IntelliJ IDEA

说明

SpringBoot中默认用Redis替换Jedis来访问Redis内存数据库。

说明Redis操作数据的效率高,本章记录Redis Template, Redis Repository的用法。

配置连接工厂

  • LettuceConnectionFactory 与 JedisConnectionFactory
    • RedisStandaloneConfiguration
    • RedisSentinelConfiguration
    • RedisClusterConfiguration

RedisStandaloneConfiguration源码解析

package org.springframework.data.redis.connection;
// import ...

public class RedisStandaloneConfiguration
		implements RedisConfiguration, WithHostAndPort, WithPassword, WithDatabaseIndex {

	private static final String DEFAULT_HOST = "localhost";
	private static final int DEFAULT_PORT = 6379;

	private String hostName = DEFAULT_HOST;
	private int port = DEFAULT_PORT;
	private int database;
	private RedisPassword password = RedisPassword.none();

	/**
	 * Create a new default {@link RedisStandaloneConfiguration}.
	 */
	public RedisStandaloneConfiguration() {}

	/**
	 * Create a new {@link RedisStandaloneConfiguration} given {@code hostName}.
	 *
	 * @param hostName must not be {@literal null} or empty.
	 */
	public RedisStandaloneConfiguration(String hostName) {
		this(hostName, DEFAULT_PORT);
	}
	// ...
}

解析:主要配置Host, Port, password, database, 并且都有默认值,
还有灵活参数的构造函数。

RedisSentinelConfiguration源码解析

package org.springframework.data.redis.connection;
// import ...

public class RedisSentinelConfiguration implements RedisConfiguration, SentinelConfiguration {

	private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master";
	private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes";

	private @Nullable NamedNode master;
	private Set<RedisNode> sentinels;
	private int database;
	private RedisPassword password = RedisPassword.none();

	/**
	 * Creates new {@link RedisSentinelConfiguration}.
	 */
	public RedisSentinelConfiguration() {
		this(new MapPropertySource("RedisSentinelConfiguration", Collections.emptyMap()));
	}

	/**
	 * Creates {@link RedisSentinelConfiguration} looking up values in given {@link PropertySource}.
	 *
	 * 
	 * 
	 * spring.redis.sentinel.master=myMaster
	 * spring.redis.sentinel.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681
	 * 
	 * 
* * @param propertySource must not be {@literal null}. * @since 1.5 */
public RedisSentinelConfiguration(PropertySource<?> propertySource) { // ...

解析:master 存储为字符串名字,sentinels为多个对象的set,每个对象都包含host, port, database, 实际上master必须活跃,sentinels可以相互负载均衡。哨兵模式可以作为读写分离。

RedisClusterConfiguration源码解析

package org.springframework.data.redis.connection;
// import ...

public class RedisClusterConfiguration implements RedisConfiguration, ClusterConfiguration {

	private static final String REDIS_CLUSTER_NODES_CONFIG_PROPERTY = "spring.redis.cluster.nodes";
	private static final String REDIS_CLUSTER_MAX_REDIRECTS_CONFIG_PROPERTY = "spring.redis.cluster.max-redirects";

	private Set<RedisNode> clusterNodes;
	private @Nullable Integer maxRedirects;
	private RedisPassword password = RedisPassword.none();

	/**
	 * Creates {@link RedisClusterConfiguration} looking up values in given {@link PropertySource}.
	 *
	 * 
	 * 
	 * spring.redis.cluster.nodes=127.0.0.1:23679,127.0.0.1:23680,127.0.0.1:23681
	 * spring.redis.cluster.max-redirects=3
	 * 
	 * 
* * @param propertySource must not be {@literal null}. */
public RedisClusterConfiguration(PropertySource<?> propertySource) { // ...

解析:

  • clusterNodes实际上就是负载均衡,只要有一个节点存活都可以访问Redis。
  • spring.redis.cluster.max-redirects=3 重定向的次数不要随意修改默认值,太小会导致重定向失败。

RedisProperties源码解析

package org.springframework.boot.autoconfigure.data.redis;

// import ...

@ConfigurationProperties(
  prefix = "spring.redis"
)
public class RedisProperties {
  private int database = 0;
  private String url;
  private String host = "localhost";
  private String password;
  private int port = 6379;
  private boolean ssl;
  private Duration timeout;
  private RedisProperties.Sentinel sentinel;
  private RedisProperties.Cluster cluster;
  private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
  private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();

  public static class Pool {

		/**
		 * Maximum number of "idle" connections in the pool. Use a negative value to
		 * indicate an unlimited number of idle connections.
		 */
		private int maxIdle = 8;

		/**
		 * Target for the minimum number of idle connections to maintain in the pool. This
		 * setting only has an effect if it is positive.
		 */
		private int minIdle = 0;

		/**
		 * Maximum number of connections that can be allocated by the pool at a given
		 * time. Use a negative value for no limit.
		 */
		private int maxActive = 8;
		// ...
	}
	// ...

解析:SpringBoot通过application.yml配置文件配置RedisProperties,
比如host, port, password, pool 等。

读写分离

Lettuce 内置支持读写分离

  • 只读主、只读从
  • 优先读主、优先读从

LettuceClientConfiguration
LettucePoolingClientConfiguration
LettuceClientConfigurationBuilderCustomizer

通过 Docker 启动 Redis

如果已经存在 Redis的镜像,直接启动就好

  • docker start redis

更多知识请参考:第廿五篇:SpringBoot之Jedis访问Redis

进入Redis bash 命令行

% docker exec -it redis bash

打开redis客户端

root@8eb8d32453bb:/data# redis-cli

查看Redis中的keys值, 查看内容,删除掉key (ke值需要带双引号)

127.0.0.1:6379> keys *
1) "starbucks-menu"

127.0.0.1:6379> hgetall starbucks-menu
 1) "espresso"
 2) "2000"
 3) "latte"
 4) "2500"
 5) "capuccino"
 6) "2500"
 7) "mocha"
 8) "3000"
 9) "macchiato"
10) "3000"

127.0.0.1:6379> del "starbucks-menu"
(integer) 1

1.工程建立

下载已经创建好的Starbucks项目,
重命名根文件夹名字的JedisDemo,用Idea打开工程,运行后实际为JPA操作数据。
接下来就改造为Jedis操作Redis数据。

pom.xml增加, Redis, Pool2 连接池配置


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-pool2artifactId>
dependency>

这里要删掉下面的配置信息,否则会引起循环引用错误

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>

循环引用报错

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   servletEndpointRegistrar defined in class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration$WebMvcServletEndpointManagementContextConfiguration.class]
      ↓
   healthEndpoint defined in class path resource [org/springframework/boot/actuate/autoconfigure/health/HealthEndpointConfiguration.class]
      ↓
   healthIndicatorRegistry defined in class path resource [org/springframework/boot/actuate/autoconfigure/health/HealthIndicatorAutoConfiguration.class]
      ↓
   org.springframework.boot.actuate.autoconfigure.redis.RedisReactiveHealthIndicatorAutoConfiguration
┌─────┐
|  redisConnectionFactory defined in class path resource [org/springframework/boot/autoconfigure/data/redis/LettuceConnectionConfiguration.class]
↑     ↓
|  starbucksApplication (field private zgpeace.spring.starbucks.service.CoffeeService zgpeace.spring.starbucks.StarbucksApplication.coffeeService)
↑     ↓
|  coffeeService (field private org.springframework.data.redis.core.RedisTemplate zgpeace.spring.starbucks.service.CoffeeService.redisTemplate)
↑     ↓
|  redisTemplate defined in zgpeace.spring.starbucks.StarbucksApplication
└─────┘

1.1RedisTemplate

RedisTemplate

  • opsForXxx()

StringRedisTemplate

切记:一定注意设置过期时间!!!

1.2 application配置

src > main > resources > application.yml

spring:
  jpa:
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        show_sql: true
        format_sql: true
  redis:
    host: "localhost"
    lettuce:
      pool:
        max-active: 5
        max-idle: 5
management:
  endpoints:
    web:
      exposure:
        include: "*"
  

配置Redis的信息

1.3 Service

zgpeace.spring.starbucks.service.CoffeeService

package zgpeace.spring.starbucks.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import zgpeace.spring.starbucks.model.Coffee;
import zgpeace.spring.starbucks.repository.CoffeeRepository;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.exact;

@Slf4j
@Service
public class CoffeeService {
  private static final String CACHE = "starbucks-coffee";
  @Autowired
  private CoffeeRepository coffeeRepository;
  @Autowired
  private RedisTemplate<String, Coffee> redisTemplate;

  public List<Coffee> findAllCoffee() {
    return coffeeRepository.findAll();
  }

  public Optional<Coffee> findOneCoffee(String name) {
    HashOperations<String, String, Coffee> hashOperations = redisTemplate.opsForHash();
    if (redisTemplate.hasKey(CACHE) && hashOperations.hasKey(CACHE, name)) {
      log.info("Get coffee {} from Redis.", name);
      return Optional.of(hashOperations.get(CACHE, name));
    }
    ExampleMatcher matcher = ExampleMatcher.matching()
        .withMatcher("name", exact().ignoreCase());
    Optional<Coffee> coffee = coffeeRepository.findOne(
        Example.of(Coffee.builder().name(name).build(), matcher));
    log.info("Coffee Found: {}", coffee);
    if (coffee.isPresent()) {
      log.info("Put coffee {} to Redis.", name);
      hashOperations.put(CACHE, name, coffee.get());
      redisTemplate.expire(CACHE, 1, TimeUnit.MINUTES);
    }
    return coffee;
  }
}

解析:

  1. 先从Redis中读取,有数据则直接返回。
  2. Redis中没有数据,则从数据库中查询,接着存入Redis中,最后返回数据。
  3. redisTemplate.expire(CACHE, 1, TimeUnit.MINUTES); 缓存有效期为1分钟

1.4 Controller Redis CRUD

zgpeace.spring.starbucks.StarbucksApplication

package zgpeace.spring.starbucks;

import io.lettuce.core.ReadFrom;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import zgpeace.spring.starbucks.model.Coffee;
import zgpeace.spring.starbucks.service.CoffeeService;

import java.util.Optional;

@Slf4j
@EnableTransactionManagement
@SpringBootApplication
@EnableJpaRepositories
public class StarbucksApplication implements ApplicationRunner {
  @Autowired
  private CoffeeService coffeeService;

  public static void main(String[] args) {
    SpringApplication.run(StarbucksApplication.class, args);
  }

  @Bean
  public RedisTemplate<String, Coffee> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Coffee> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }

  @Bean
  public LettuceClientConfigurationBuilderCustomizer customizer() {
    return builder -> builder.readFrom(ReadFrom.MASTER_PREFERRED);
  }

  @Override
  public void run(ApplicationArguments args) throws Exception {
    Optional<Coffee> c = coffeeService.findOneCoffee("mocha");
    log.info("Coffee {}", c);

    for (int i = 0; i < 5; i++) {
      c = coffeeService.findOneCoffee("mocha");
    }

    log.info("Value from Redis: {}", c);
  }
}


解析:
ReadFrom.MASTER_PREFERRED 表示从主节点读取Redis。

运行结果如下:第一次读取数据库,然后存入Redis,后面5次查询读取Redis

Hibernate: 
    select
        coffee0_.id as id1_0_,
        coffee0_.create_time as create_t2_0_,
        coffee0_.update_time as update_t3_0_,
        coffee0_.name as name4_0_,
        coffee0_.price as price5_0_ 
    from
        t_coffee coffee0_ 
    where
        lower(coffee0_.name)=?
Coffee Found: Optional[Coffee(super=BaseEntity(id=4, createTime=2019-10-05 11:39:45.379, updateTime=2019-10-05 11:39:45.379), name=mocha, price=CNY 30.00)]
Put coffee mocha to Redis.
Coffee Optional[Coffee(super=BaseEntity(id=4, createTime=2019-10-05 11:39:45.379, updateTime=2019-10-05 11:39:45.379), name=mocha, price=CNY 30.00)]
Get coffee mocha from Redis.
Get coffee mocha from Redis.
Get coffee mocha from Redis.
Get coffee mocha from Redis.
Get coffee mocha from Redis.
Value from Redis: Optional[Coffee(super=BaseEntity(id=4, createTime=2019-10-05 11:39:45.379, updateTime=2019-10-05 11:39:45.379), name=mocha, price=CNY 30.00)]
Closing JPA EntityManagerFactory for persistence unit 'default'

1.5 Redis中查询数据

因为设置1分钟过期时间,所以1分钟后就查不到内容

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x12starbucks-coffee"
127.0.0.1:6379> hgetall "\xac\xed\x00\x05t\x00\x12starbucks-coffee"
1) "\xac\xed\x00\x05t\x00\x05mocha"
2) "\xac\xed\x00\x05sr\x00%zgpeace.spring.starbucks.model.Coffee\xfc|\x99\x99\xe0\x87\\H\x02\x00\x02L\x00\x04namet\x00\x12Ljava/lang/String;L\x00\x05pricet\x00\x16Lorg/joda/money/Money;xr\x00)zgpeace.spring.starbucks.model.BaseEntity\x15\xa6\x90\x00\x89\x0e\xff\x88\x02\x00\x03L\x00\ncreateTimet\x00\x10Ljava/util/Date;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\nupdateTimeq\x00~\x00\x04xpsr\x00\x12java.sql.Timestamp&\x18\xd5\xc8\x01S\xbfe\x02\x00\x01I\x00\x05nanosxr\x00\x0ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\b\x00\x00\x01m\x9a\x00w\xe8x\x16\x97\x14\xc0sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x04sq\x00~\x00\aw\b\x00\x00\x01m\x9a\x00w\xe8x\x16\x97\x14\xc0t\x00\x05mochasr\x00\x12org.joda.money.Serq\xd7\xfe\x1b\x88\xed\x97\x9c\x0c\x00\x00xpw\x14M\x00\x03CNY\x00\x9c\x00\x02\x00\x00\x00\x02\x0b\xb8\x00\x00\x00\x02x"

2.1 Redis Repository

实体注解

  • @RedisHash
  • @Id
  • @Indexed

处理不同类型数据源的 Repository
如何区分这些 Repository

  • 根据实体的注解
  • 根据继承的接口类型
  • 扫描不同的包

2.1 RedisTemplate

跟上面 1.1 RedisTemplate一模一样

2.2 application配置

跟上面 1.2 application配置一模一样

2.3 Cache Model

package zgpeace.spring.starbucks.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.joda.money.Money;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

@RedisHash(value = "starbucks-coffee", timeToLive = 60)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CoffeeCache {
  @Id
  private Long id;
  @Indexed
  private String name;
  private Money price;
}

解析:

  1. “starbucks-coffee” 存储类型为Hash
  2. timeToLive = 60 缓存有效期为60秒
  3. @Id 为主键
  4. @Indexed 为外键
  5. Money 接下来会有读取和写入类型转换

2.4 Money在Redis中写入和读取类型转换

从Redis中读数据
zgpeace.spring.starbucks.converter.BytesToMoneyConverter

package zgpeace.spring.starbucks.converter;

import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;

import java.nio.charset.StandardCharsets;

@ReadingConverter
public class BytesToMoneyConverter implements Converter<byte[], Money> {
  @Override
  public Money convert(byte[] bytes) {
    String value = new String(bytes, StandardCharsets.UTF_8);
    return Money.of(CurrencyUnit.of("CNY"), Long.parseLong(value));
  }
}

从Redis中写数据
zgpeace.spring.starbucks.converter.MoneyToBytesConverter

package zgpeace.spring.starbucks.converter;

import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;

import java.nio.charset.StandardCharsets;

@WritingConverter
public class MoneyToBytesConverter implements Converter<Money, byte[]> {
  @Override
  public byte[] convert(Money money) {
    String value = Long.toString(money.getAmountMinorLong());
    return value.getBytes(StandardCharsets.UTF_8);
  }
}

2.5 Cache Repository

zgpeace.spring.starbucks.repository.CoffeeCacheRepository

package zgpeace.spring.starbucks.repository;

import org.springframework.data.repository.CrudRepository;
import zgpeace.spring.starbucks.model.CoffeeCache;

import java.util.Optional;

public interface CoffeeCacheRepository extends CrudRepository<CoffeeCache, Long> {
  Optional<CoffeeCache> findOneByName(String name);
}

2.6 Cache Service

zgpeace.spring.starbucks.service.CoffeeService

package zgpeace.spring.starbucks.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.stereotype.Service;
import zgpeace.spring.starbucks.model.Coffee;
import zgpeace.spring.starbucks.model.CoffeeCache;
import zgpeace.spring.starbucks.repository.CoffeeCacheRepository;
import zgpeace.spring.starbucks.repository.CoffeeRepository;

import java.util.Optional;

import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.exact;

@Slf4j
@Service
public class CoffeeService {
  @Autowired
  private CoffeeRepository coffeeRepository;
  @Autowired
  private CoffeeCacheRepository cacheRepository;

  public Optional<Coffee> findSimpleCoffeeFromCache(String name) {
    Optional<CoffeeCache> cached = cacheRepository.findOneByName(name);
    if (cached.isPresent()) {
      CoffeeCache coffeeCache = cached.get();
      Coffee coffee = Coffee.builder()
          .name(coffeeCache.getName())
          .price(coffeeCache.getPrice())
          .build();
      log.info("Coffee {} found in cache.", coffeeCache);
      return Optional.of(coffee);
    } else {
      Optional<Coffee> raw = findOneCoffee(name);
      raw.ifPresent(c -> {
        CoffeeCache coffeeCache = CoffeeCache.builder()
            .id(c.getId())
            .name(c.getName())
            .price(c.getPrice())
            .build();
        log.info("Save Coffee {} to cache.", coffeeCache);
        cacheRepository.save(coffeeCache);
      });

      return raw;
    }
  }

  public Optional<Coffee> findOneCoffee(String name) {
      ExampleMatcher matcher = ExampleMatcher.matching()
          .withMatcher("name", exact().ignoreCase());
      Optional<Coffee> coffee = coffeeRepository.findOne(
          Example.of(Coffee.builder().name(name).build(), matcher));
      log.info("Coffee Found: {}", coffee);
      return coffee;
  }
}

解析:

  1. 通过Redis Repository获取cache,获取到则组装对象返回
  2. 如果cache为空,则读取数据库,并通过Redis Repository存储对象到cache。最后返回数据库读取到的对象

2.7 Cache Controller

zgpeace.spring.starbucks.StarbucksApplication

package zgpeace.spring.starbucks;

import io.lettuce.core.ReadFrom;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.LettuceClientConfigurationBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.redis.core.convert.RedisCustomConversions;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import zgpeace.spring.starbucks.converter.BytesToMoneyConverter;
import zgpeace.spring.starbucks.converter.MoneyToBytesConverter;
import zgpeace.spring.starbucks.model.Coffee;
import zgpeace.spring.starbucks.model.CoffeeOrder;
import zgpeace.spring.starbucks.model.OrderState;
import zgpeace.spring.starbucks.repository.CoffeeRepository;
import zgpeace.spring.starbucks.service.CoffeeOrderService;
import zgpeace.spring.starbucks.service.CoffeeService;

import java.util.Arrays;
import java.util.Optional;

@Slf4j
@EnableTransactionManagement
@EnableJpaRepositories
@EnableRedisRepositories
@SpringBootApplication
public class StarbucksApplication implements ApplicationRunner {
  @Autowired
  private CoffeeService coffeeService;

  @Bean
  public LettuceClientConfigurationBuilderCustomizer customizer() {
    return builder -> builder.readFrom(ReadFrom.MASTER_PREFERRED);
  }

  @Bean
  public RedisCustomConversions redisCustomConversions() {
    return  new RedisCustomConversions(
        Arrays.asList(new MoneyToBytesConverter(), new BytesToMoneyConverter())
    );
  }

  @Override
  public void run(ApplicationArguments args) throws Exception {
    Optional<Coffee> c = coffeeService.findSimpleCoffeeFromCache("mocha");
    log.info("Coffee: {}", c);

    for (int i = 0; i < 5; i++) {
      c = coffeeService.findSimpleCoffeeFromCache("mocha");
    }

    log.info("Value from Redis: {}", c);
  }

  public static void main(String[] args) {
    SpringApplication.run(StarbucksApplication.class, args);
  }

}

解析:

  1. LettuceClientConfigurationBuilderCustomizer customizer() 指定读取Redis从主机中读取
  2. RedisCustomConversions 定制Money转换的对象数组
  3. 第一次从数据库中读取,1分钟内(缓存过期时间)都是从Redis Cache中读取

源码解析
Money Converter可以自定义是因为RegisterBeansForRoot预留了定制方法registerIfNotAlreadyRegistered(…)

package org.springframework.data.redis.repository.configuration;

// import ...
public class RedisRepositoryConfigurationExtension extends KeyValueRepositoryConfigurationExtension {

	private static final String REDIS_CONVERTER_BEAN_NAME = "redisConverter";
	private static final String REDIS_REFERENCE_RESOLVER_BEAN_NAME = "redisReferenceResolver";
	private static final String REDIS_ADAPTER_BEAN_NAME = "redisKeyValueAdapter";
	private static final String REDIS_CUSTOM_CONVERSIONS_BEAN_NAME = "redisCustomConversions";

	// ...
	@Override
	public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConfigurationSource configurationSource) {
		// ...
		// register coustom conversions
		RootBeanDefinition customConversions = new RootBeanDefinition(RedisCustomConversions.class);
		registerIfNotAlreadyRegistered(customConversions, registry, REDIS_CUSTOM_CONVERSIONS_BEAN_NAME,
				configurationSource);

2.8 Redis中查询数据

查询keys值

127.0.0.1:6379> keys *
1) "starbucks-coffee:name:mocha"
2) "starbucks-coffee:4"
3) "starbucks-coffee:4:phantom"
4) "starbucks-coffee"
5) "starbucks-coffee:4:idx"

查看key对应value的类型,并用对应的数据类型查询

127.0.0.1:6379> type "starbucks-coffee:4"
hash
127.0.0.1:6379> hgetall "starbucks-coffee:4"
1) "_class"
2) "zgpeace.spring.starbucks.model.CoffeeCache"
3) "id"
4) "4"
5) "name"
6) "mocha"
7) "price"
8) "3000"
127.0.0.1:6379> type "starbucks-coffee:name:mocha"
set
127.0.0.1:6379> smembers "starbucks-coffee:name:mocha"
1) "4"
127.0.0.1:6379> hgetall "starbucks-coffee"
(error) WRONGTYPE Operation against a key holding the wrong kind of value

解析: 如果查询方法与真实数据不符,就会报错:
(error) WRONGTYPE Operation against a key holding the wrong kind of value

总结

恭喜你,学会了Redis template,Redis Repository操作数据。
代码下载:

https://github.com/zgpeace/Spring-Boot2.1/tree/master/Nosql/RedisTemplate

https://github.com/zgpeace/Spring-Boot2.1/tree/master/Nosql/RedisRepository

参考

https://redis.io
http://try.redis.io/
https://hub.docker.com/_/redis
https://www.runoob.com/redis/redis-tutorial.html
https://www.runoob.com/docker/docker-install-redis.html

https://github.com/zgpeace/Spring-Boot2.1/tree/master/db/DemoDBStarbucks

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%204/redis-demo

https://github.com/geektime-geekbang/geektime-spring-family/tree/master/Chapter%204/redis-repository-demo

你可能感兴趣的:(易实战Sprint,Boot,2.1,SpringBoot,Java,Spring)