Spring Data Redis

当我有一个想法,使用Spring Data Redis的时候,我想到了JPA, 想到了Spring Data Mongo,一想Spring Data统一了Dao层的编程模型,我就觉得我可以轻松驾驭Spring Data Redis,果然想法是天真的。当我准备启动项目的时候,就报了一个错误:

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

Description:

A component required a bean of type 'com.example.demo.repository.VocabularyEntityRepository' that could not be found.

然而我明明看到了

我也是加了@Repository注解,同时也看到了Bean的图标,但是确告诉我没有注入这样的Bean,就很疑惑,同样我也没有办法直接判断为什么没有注入成功。一想到以前没有注入成功的原因大半是因为package的问题导致没有扫描到,现在我再三检查了一下,也没有问题。第二就是我想到是不是命名的问题导致的,几经折磨,发现并不是。

这时时间已经过去了许久。我想这时候就开始看官网了,首先是找到Spring Data Redis 的文档看了一番,然而并没有看到什么问题,一想到Spring Data Redis肯定是开箱即用的,也就是我不需要额外的配置,除非我使用的不是默认的配置,显然我是完全按照默认的配置来的,所以我排除了配置的原因。

接着,我想到了Spring Data Example这个项目,这是一个示例项目,也就是怎么使用的问题。果然,还是这个好使。一会我就找到了答案:

原来我少的一个关键的注解,那便是@RedisHash(“xxx”), 这一下就让我想起了以前用关系型数据库mysql,postgresql,时,我会在实体上标注@Entity,使用mongo或者es的时候,我会用@Document。

当然,我第一次还看到了下面的配置:

一看到@EnableRedisRepositories再结合错误,没有找到Repository,自然一下子就看到了曙光,一顿操作,果然服务起来了。

然后,我理解Spring Data Redis也应该是默认就开启了@EnableRedisRepositories,所以我尝试删除了这个配置类也是可以启动了,验证了我的想法。

最后,我想这次解决没有找到Repository的问题,其实是个人经验总结和摸索出来的,但是我看到了一个更好的解决问题的模型——那便除了文档之外,我们可以直接看别人的示例源码,因为在我们用之前,它肯定提供了示例。

当然,我在看到官方提供的示例源码之前,也看到了一些别人写的代码,但是,就算源码中包含了解决方案,也会被更多的变量所覆盖,比如版本问题,以及版本带来的依赖问题,Api变化问题,等等,所以一下子很难看到是哪个原因导致的。所以在分析原因的时候,我们要控制好变量。不然,很容易就被带歪了,比如,我看到了一个示例代码就有:

但是,我那时并没有关注到这个注解@RedisHash(“xxx”)。

敏锐的目光需要不断地打磨,因为你要知道Repository的原理的话,估计就不会出现这些问题。但是平时大都在应用层,我们更多的关注点都在使用Api,并没有深层次的了解那些看起来不起眼的注解上,比如在Feign API时,就经常出现项目没有办法启动,因为就是少了注解,比如@RequestParam.

记在最后,当使用的示例是最好的学习方式,应该这样能最快地解决应用问题。

原本,文章到此就结束了,但是我想起来今天看文档地一点收获也顺便记一下。

我发现Redis也是可以支持Geo的,这让我联想起了Mongo也是支持同样的操作的,然后就顺便看了一下例子:

@RedisHash("people")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address hometown;
}

public class Address {

  @GeoIndexed Point location;
}

再看@GeoIndexed注解就很亲切。

Find using Geo Index:

repository.findByHometownLocationNear(new Point(15, 37), new Distance(200, KILOMETERS));
List findByAddressLocationWithin(Circle circle)

这行代码看上去就很美,此时的心情也很美。

Junit-In-Action

Part 1. Junit

1. JUnit jump-start

1.1 Proving that a program works

Framework Definition

Unit Test Difinition

API Contract Definition

The notion of an API contract arises from the practice of Design by Contract,popularized by the Eiffel programming language (http://archive.eiffel.com/doc/manuals/technology/contract).

1.2.1 Understanding unit testing frameworks

Unit testing has several best practices that frameworks should follow. Three rules that (in my experience) all unit testing frameworks should follow:

  1. Each unit test should run independently of all other unit tests.
  2. The framework should detect and report errors test by test.
  3. It should be easy to define which unit tests will run.

1.4 Testing with JUnit

JUnit annotations to provide resource initialization and cleanup methods:
@BeforeEach, @BeforeAll, @AfterEach, and @AfterAll (starting from version 5);
and @Before, @BeforeClass, @After, and @AfterClass (up to version 4)

Summary

This chapter has covered the following:
 Why every developer should perform some type of test to see if code actually
works. Developers who use automatic unit tests can repeat these tests on
demand to ensure that new code works and does not break existing tests.
 Writing simple unit tests, which are not difficult to create without JUnit.
 As tests are added and become more complex, writing and maintaining tests
becomes more difficult.
 Introduction to JUnit as a unit testing framework that makes it easier to create,
run, and revise unit tests.
 Stepping through a simple JUnit test.

2. Exploring core JUnit

2.1 Core annotations

These are the most important concepts:

  • A test class may be a top-level class, a static member class, or an inner class annotated as @Nested that contains one or more test methods.
  • Test classes cannot be abstract and must have a single constructor.
  • The constructor must have no arguments, or arguments that can be dynamically resolved at runtime through
  • dependency injection.
  • A test class is allowed to be package-private as a minimum requirement for visibility. It is no longer required that test classes be public, as was the case up to JUnit 4.x.
  • A test method is an instance method that is annotated with @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate.
  • A life cycle method is a method that is annotated with @BeforeAll, @AfterAll,@BeforeEach, or @AfterEach.

2.1.1 The @DisplayName annotation

2.1.2 The @Disabled annotation

It signals that the annotated test class or test method is disabled and should not be executed.

2.2 Nested tests

The typical use case is when two classes are tightly coupled, and it’s logical to provide direct
access from the inner class to all instance variables of the outer class.

2.3 Tagged tests

If you are familiar with JUnit 4, tagged tests are replacements for JUnit 4 categories.

2.4 Assertions

To perform test validation, you use the assert methods provided by the JUnit
Assertions class.

After the heading parameter from the assertAll method, we provide the rest of
the arguments as a collection of executables—a shorter, more convenient way to assert
that supplied executables do not throw exceptions.

The assertAll method will always check all the assertions that are provided to it, even
if some of them fail—if any of the executables fail, the remaining ones will still be run.

The advantage of using lambda expressions as arguments for assertion methods is that
all of them are lazily created, resulting in improved performance.

2.5 Assumptions

Sometimes tests fail due to an external environment configuration or a date or time
zone issue that we cannot control. We can prevent our tests from being executed
under inappropriate conditions.

2.6 Dependency injection in JUnit 5

JUnit 5 allows test constructors and methods to have parameters, but they
need to be resolved through dependency injection.

2.6.1 TestInfoParameterResolver

2.6.2 TestReporterParameterResolver

2.6.3 RepetitionInfoParameterResolver

If a parameter in a method annotated with @RepeatedTest, @BeforeEach, or
@AfterEach is of type RepetitionInfo, RepetitionInfoParameterResolver
supplies an instance of this type. Then RepetitionInfo gets information about the
current repetition and the total number of repetitions for a test annotated with
@RepeatedTest.

2.7 Repeated test

2.8 Parameterized tests

Parameterized tests allow a test to run multiple times with different arguments. The
great benefit is that we can write a single test to be performed using arguments that
check various input data.

@ValueSource lets us specify a single array of literal values.

@EnumSource enables us to use enum instances.

@CsvSource to express argument lists as comma-separated values (CSV)

@CsvFileSource allows us to use CSV files from the classpath.

2.9 Dynamic tests

JUnit 5 introduces a dynamic new programming model that can generate tests at runtime. We write a factory method, and at runtime, it creates a series of tests to be executed. Such a factory method must be annotated with @TestFactory.

A dynamic test has a different life cycle than a standard test annotated with @Test.
The methods annotated with @BeforeEach and @AfterEach are executed for the
@TestFactory method but not for each dynamic test; other than these methods,
there are no life cycle callbacks for individual dynamic tests. The behavior of
@BeforeAll and @AfterAll remains the same; they are executed before all tests
and at the end of all tests.

2.10 Using Hamcrest matchers

As we write more unit tests and assertions, we inevitably find that some assertions
are big and hard to read.The goal is to simplify the assertion made in the test method.

此图像的alt属性为空;文件名为image-14-1024x525.png

The Hamcrest library
Hamcrest is not a testing framework itself, but it helps us declaratively specify simple
matching rules. These matching rules can be used in many situations, but they are
particularly helpful for unit testing.

Summary

  • The core JUnit 5 classes related to assertions and assumptions.
  • Using JUnit 5 methods and annotations: the methods from the assertions and assumptions classes, and annotations like @Test, @DisplayName, and @Disabled
  • The life cycle of a JUnit 5 test, and controlling it through the @BeforeEach, @AfterEach, @BeforeAll, and @AfterAll annotations
  • Applying JUnit 5 capabilities to create nested tests and tagged tests (annotation:@Tag)
  • Implementing dependency injection with the help of test constructors and methods with parameters
  • Applying dependency injection by using different parameter resolvers (TestInfoParameterResolver, TestReporterParameterResolver)
  • Implementing repeated tests (annotation: @RepeatedTest) as another application of dependency injection
  • Parameterized tests, a very flexible tool for testing that consumes different data sets and dynamic tests created at runtime (annotations: @ParameterizedTest, @TestFactory)
  • Using Hamcrest matchers to simplify assertions