查看原文
其他

Spring Data Redis 简介

Darren Luo SpringForAll社区 2020-10-17

Redis 是一个内存数据接口存储,具有可选的持久性,通常用作数据库、缓存和消息代理。目前,它是键值存储分类中最受欢迎的工具:https://db-engines.com/en/ranking/key-value+store。将应用程序与 redis 集成的最简单方式是通过 Spring Data Redis。你可以直接使用 Spring RedisTemplate 进行集成,你也可以使用 Spring Data Redis repositories。当你通过 Spring Data Redis repositories 集成 Redis 时,存在着一些限制。它们至少需要 2.8.0 版本的 Redis 服务器并且不能使用事务。因此,你需要禁用 RedisTemplate 的事务支持,这是由 Redis repositories 利用的。

Redis 通常用于缓存存储在关系型数据库种的数据。在当前示例中,它将被视作主数据库——仅为了简化。Spring Data repositories 不需要开发者有任何深入的了解。你只需要正确的对你的领域类使用注解。和往常一样,我们将基于示例应用程序检查 Spring Data Redis 的主要功能。假设我们有这样一个系统,它由三个领域对象构成: CustomerAccount 以及 Transaction,这里的图片说明了系统元素之间的关系。 Transaction 始终与两个账户相关:sender( fromAccountId)和 receiver( toAccountId)。每个 customer 可能有许多账户。

虽然上面显示的图片展示了三个独立的领域模型,customer 和 account 存储在相同的独立结构中。所有的 customer 的 account 都作为列表存储在 customer 对象中。在继续实现示例应用程序细节之前,让我们从启动 Redis 数据库开始。

1. 在 Docker 上运行 Redis

我们将使用 Docker 容器在本地运行 Redis 独立实例。你可以用内存模式或持久化存储来启动它。这是在 Docker 容器上运行单个 Redis 内存实例的命令。它对外暴露在默认的 6379 端口。

  1. $ docker run -d --name redis -p 6379:6379 redis

2. 启用 Redis Repositories 并配置连接

我正在使用 Docker Toolbox,因此每个容器我都可以在地址 192.168.99.100 下使用。这是我需要在配置设置( application.yml)中覆盖的唯一属性。

  1. spring:

  2. application:

  3. name: sample-spring-redis

  4. redis:

  5. host: 192.168.99.100

要为 Spring Boot 应用程序启用 Redis repositories,我们只需要引入单独的 starter spring-boot-starter-data-redis

  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-data-redis</artifactId>

  4. </dependency>

  5. <dependency>

  6. <groupId>org.springframework.boot</groupId>

  7. <artifactId>spring-boot-starter-web</artifactId>

  8. </dependency>

我们可以在两个支持的连接器中进行选择:Lettuce 和 Jedis。对于 Jedis ,我必须引入一个额外的客户端库到依赖项,因此我决定使用更简单的选项—— Lettuce,它不需要任何额外的库就能正常运行。要启用 Spring Data Redis repositories,我们还需要对主类或配置类使用 @EnableRedisRepositories 注解并声明 RedisTemplate bean。虽然我们不直接使用 RedisTemplate,但我们任然需要声明它,它用于与 Redis 集成的 CRUD repositories。

  1. @Configuration

  2. @EnableRedisRepositories

  3. public class SampleSpringRedisConfiguration {


  4. @Bean

  5. public LettuceConnectionFactory redisConnectionFactory() {

  6. return new LettuceConnectionFactory();

  7. }


  8. @Bean

  9. public RedisTemplate<?, ?> redisTemplate() {

  10. RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();

  11. template.setConnectionFactory(redisConnectionFactory());

  12. return template;

  13. }


  14. }

3. 实现领域实体

每个领域实体至少要用 @RedisHash 注解,并包含使用 @Id 注解的属性。这两项负责创建用于持久化 hash 的实际 key。除了带有 @Id 注解的标识符属性外,你也可以使用二级索引。好消息是它不仅可以是依赖的单个对象,还可以是 list 和 map。这是 Customer 实体的定义。它可以在 Redis 的 customer key 下使用。它包含了 Account 实体的列表。

  1. @RedisHash("customer")

  2. public class Customer {


  3. @Id private Long id;

  4. @Indexed private String externalId;

  5. private String name;

  6. private List<Account> accounts = new ArrayList<>();


  7. public Customer(Long id, String externalId, String name) {

  8. this.id = id;

  9. this.externalId = externalId;

  10. this.name = name;

  11. }


  12. public Long getId() {

  13. return id;

  14. }


  15. public void setId(Long id) {

  16. this.id = id;

  17. }


  18. public String getExternalId() {

  19. return externalId;

  20. }


  21. public void setExternalId(String externalId) {

  22. this.externalId = externalId;

  23. }


  24. public String getName() {

  25. return name;

  26. }


  27. public void setName(String name) {

  28. this.name = name;

  29. }


  30. public List<Account> getAccounts() {

  31. return accounts;

  32. }


  33. public void setAccounts(List<Account> accounts) {

  34. this.accounts = accounts;

  35. }


  36. public void addAccount(Account account) {

  37. this.accounts.add(account);

  38. }


  39. }

Account 没有自己的 hash。它作为对象列表被包含在 Customer 中。属性 id 被拿来在 Redis 上制作索引以提升基于数据的查询的速度。

  1. public class Account {


  2. @Indexed private Long id;

  3. private String number;

  4. private int balance;


  5. public Account(Long id, String number, int balance) {

  6. this.id = id;

  7. this.number = number;

  8. this.balance = balance;

  9. }


  10. public Long getId() {

  11. return id;

  12. }


  13. public void setId(Long id) {

  14. this.id = id;

  15. }


  16. public String getNumber() {

  17. return number;

  18. }


  19. public void setNumber(String number) {

  20. this.number = number;

  21. }


  22. public int getBalance() {

  23. return balance;

  24. }


  25. public void setBalance(int balance) {

  26. this.balance = balance;

  27. }


  28. }

最后,让我们看看 Transaction 实体的实现。它只使用 account 的 id,而不是整个对象。

  1. @RedisHash("transaction")

  2. public class Transaction {


  3. @Id

  4. private Long id;

  5. private int amount;

  6. private Date date;

  7. @Indexed

  8. private Long fromAccountId;

  9. @Indexed

  10. private Long toAccountId;


  11. public Transaction(Long id, int amount, Date date, Long fromAccountId, Long toAccountId) {

  12. this.id = id;

  13. this.amount = amount;

  14. this.date = date;

  15. this.fromAccountId = fromAccountId;

  16. this.toAccountId = toAccountId;

  17. }


  18. public Long getId() {

  19. return id;

  20. }


  21. public void setId(Long id) {

  22. this.id = id;

  23. }


  24. public int getAmount() {

  25. return amount;

  26. }


  27. public void setAmount(int amount) {

  28. this.amount = amount;

  29. }


  30. public Date getDate() {

  31. return date;

  32. }


  33. public void setDate(Date date) {

  34. this.date = date;

  35. }


  36. public Long getFromAccountId() {

  37. return fromAccountId;

  38. }


  39. public void setFromAccountId(Long fromAccountId) {

  40. this.fromAccountId = fromAccountId;

  41. }


  42. public Long getToAccountId() {

  43. return toAccountId;

  44. }


  45. public void setToAccountId(Long toAccountId) {

  46. this.toAccountId = toAccountId;

  47. }


  48. }

4. 实现 repositories

repositories 的实现是我们练习中最令人愉悦的部分。。和往常一样使用 Spring Data 项目,最常见的方法如 savedeletefindById 都已经实现。因此,如果需要,我们只用创建我们自定义的查找方法。由于 findByExternalId 方法的使用和实现相当明显,而方法 findByAccountsId 可能不明显。让我们回到模型定义来明确该方法的用法。 Transaction 只包含 account 的 id,而没有和 Customer 的直接关系。如果我们需要了解 customer 作为指定 transaction 的一方的详细信息,该怎么做?我们可以通过列表中它的 account 找到 customer。

  1. public interface CustomerRepository extends CrudRepository {


  2. Customer findByExternalId(String externalId);

  3. List findByAccountsId(Long id);


  4. }

这是 Transaction 实体 repository 的实现。

  1. public interface TransactionRepository extends CrudRepository {


  2. List findByFromAccountId(Long fromAccountId);

  3. List findByToAccountId(Long toAccountId);


  4. }

5. 构建 repository 测试

我们可以是使用带有 @DataRedisTest 的 Spring Boot Test 项目轻松测试 Redis repositories 的功能。此测试假设你已经在已配置好的地址 192.168.99.100 上运行了 Redis 服务实例。

  1. @RunWith(SpringRunner.class)

  2. @DataRedisTest

  3. @FixMethodOrder(MethodSorters.NAME_ASCENDING)

  4. public class RedisCustomerRepositoryTest {


  5. @Autowired

  6. CustomerRepository repository;


  7. @Test

  8. public void testAdd() {

  9. Customer customer = new Customer(1L, "80010121098", "John Smith");

  10. customer.addAccount(new Account(1L, "1234567890", 2000));

  11. customer.addAccount(new Account(2L, "1234567891", 4000));

  12. customer.addAccount(new Account(3L, "1234567892", 6000));

  13. customer = repository.save(customer);

  14. Assert.assertNotNull(customer);

  15. }


  16. @Test

  17. public void testFindByAccounts() {

  18. List<Customer> customers = repository.findByAccountsId(3L);

  19. Assert.assertEquals(1, customers.size());

  20. Customer customer = customers.get(0);

  21. Assert.assertNotNull(customer);

  22. Assert.assertEquals(1, customer.getId().longValue());

  23. }


  24. @Test

  25. public void testFindByExternal() {

  26. Customer customer = repository.findByExternalId("80010121098");

  27. Assert.assertNotNull(customer);

  28. Assert.assertEquals(1, customer.getId().longValue());

  29. }

  30. }

6. 使用 Testcontainers 进行更高级的测试

你可以使用 Redis 进行一些高级的集成测试,因为在 Testcontainer 库在测试期间启动了 Docker 容器。我已经发表了一些关于 Testcontrainer 框架的文章。如果你想了解更详细的信息,请参考我以前的文章:《Microservices Integration Tests with Hoverfly and Testcontainers》和《Testing Spring Boot Integration with Vault and Postgres using Testcontainers Framework》。

  1. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

  2. @RunWith(SpringRunner.class)

  3. public class CustomerIntegrationTests {


  4. @Autowired

  5. TestRestTemplate template;


  6. @ClassRule

  7. public static GenericContainer redis = new GenericContainer("redis:5.0.3").withExposedPorts(6379);


  8. @Before

  9. public void init() {

  10. int port = redis.getFirstMappedPort();

  11. System.setProperty("spring.redis.host", String.valueOf(port));

  12. }


  13. @Test

  14. public void testAddAndFind() {

  15. Customer customer = new Customer(1L, "123456", "John Smith");

  16. customer.addAccount(new Account(1L, "1234567890", 2000));

  17. customer.addAccount(new Account(2L, "1234567891", 4000));

  18. customer = template.postForObject("/customers", customer, Customer.class);

  19. Assert.assertNotNull(customer);

  20. customer = template.getForObject("/customers/{id}", Customer.class, 1L);

  21. Assert.assertNotNull(customer);

  22. Assert.assertEquals("123456", customer.getExternalId());

  23. Assert.assertEquals(2, customer.getAccounts().size());

  24. }


  25. }

7. 查看数据

现在,在我们的 JUnit 测试后分析存储在 Redis 中的数据。我们可以使用任何 GUI 工具来做这个。我决定在网站 https://rdbtools.com 上安装 RDBTools。你可以使用此工具轻松浏览存储在 Redis 上的数据。这是运行 JUnit 测试后带有 id=1customer 实体的结果。

这是带有 id=1transaction 实体的类似结果。

源码

示例应用程序的源码在 Github 上的 sample-spring-redis repository。

原文链接:https://piotrminkowski.wordpress.com/2019/03/05/introduction-to-spring-data-redis/

作者:piotrminkowski

译者:Darren Luo


推荐: Spring Cloud Alibaba基础教程:使用Nacos实现服务注册与发现

上一篇:Nacos如何进行集群部署




 关注公众号

点击原文阅读更多



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存