repository Archives - Piotr's TechBlog https://piotrminkowski.com/tag/repository/ Java, Spring, Kotlin, microservices, Kubernetes, containers Sat, 19 Dec 2020 08:07:05 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 repository Archives - Piotr's TechBlog https://piotrminkowski.com/tag/repository/ 32 32 181738725 JPA Data Access with Micronaut Data https://piotrminkowski.com/2019/07/25/jpa-data-access-with-micronaut-predator/ https://piotrminkowski.com/2019/07/25/jpa-data-access-with-micronaut-predator/#respond Thu, 25 Jul 2019 11:58:39 +0000 https://piotrminkowski.wordpress.com/?p=7196 When I have been writing some articles comparing Spring and Micronaut frameworks recently, I have taken note of many comments about the lack of built-in ORM and data repositories supported by Micronaut. Spring provides this feature for a long time through the Spring Data project. The good news is that the Micronaut team is close […]

The post JPA Data Access with Micronaut Data appeared first on Piotr's TechBlog.

]]>
When I have been writing some articles comparing Spring and Micronaut frameworks recently, I have taken note of many comments about the lack of built-in ORM and data repositories supported by Micronaut. Spring provides this feature for a long time through the Spring Data project. The good news is that the Micronaut team is close to complete work on the first version of their project with ORM support. The project called Micronaut Data (old Micronaut Predator) (short for Precomputed Data Repositories) is still under active development, and currently we may access just the snapshot version. However, the authors are introducing it as more efficient with reduced memory consumption than competitive solutions like Spring Data or Grails GORM. In short, this could be achieved thanks to Ahead of Time (AoT) compilation to pre-compute queries for repository interfaces that are then executed by a thin, lightweight runtime layer, and avoiding usage of reflection or runtime proxies.

Currently, Micronaut Predator provides runtime support for JPA (Hibernate) and SQL (JDBC). Some other implementations are planned in the future. In this article I’m going to show you how to include Micronaut Data in your application and use its main features for providing JPA data access.

1. Dependencies

The snapshot dependency of Micronaut Predator is available at https://oss.sonatype.org/content/repositories/snapshots/, so first we need to include it to the repository list in our pom.xml together with jcenter:

<repositories>
   <repository>
      <id>jcenter.bintray.com</id>
      <url>https://jcenter.bintray.com</url>
   </repository>
   <repository>
      <id>sonatype-snapshots</id>
      <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
   </repository>
</repositories>

In addition to the standard libraries included for building a web application with Micronaut, we have to add the following dependencies: database driver (we will use PostgreSQL as the database for our sample application) and micronaut-predator-hibernate-jpa.

<dependency>
   <groupId>io.micronaut.data</groupId>
   <artifactId>micronaut-predator-hibernate-jpa</artifactId>
   <version>${predator.version}</version>
   <scope>compile</scope>
</dependency>
<dependency>
   <groupId>io.micronaut.configuration</groupId>
   <artifactId>micronaut-jdbc-tomcat</artifactId>
   <scope>runtime</scope>
</dependency>    
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.6</version>
</dependency> 

Some Micronaut libraries including micronaut-predator-processor have to be added to the annotation processor path. Such a configuration should be provided inside Maven Compiler Plugin configuration:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.7.0</version>
   <configuration>
      <source>${jdk.version}</source>
      <target>${jdk.version}</target>
      <encoding>UTF-8</encoding>
      <compilerArgs>
         <arg>-parameters</arg>
      </compilerArgs>
      <annotationProcessorPaths>
         <path>
            <groupId>io.micronaut</groupId>
            <artifactId>micronaut-inject-java</artifactId>
            <version>${micronaut.version}</version>
         </path>
         <path>
            <groupId>io.micronaut.data</groupId>
            <artifactId>micronaut-predator-processor</artifactId>
            <version>${predator.version}</version>
         </path>
         <path>
            <groupId>io.micronaut</groupId>
            <artifactId>micronaut-validation</artifactId>
            <version>${micronaut.version}</version>
         </path>
      </annotationProcessorPaths>
   </configuration>
</plugin>

The current newest RC version of Micronaut is 1.2.0.RC:

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>io.micronaut</groupId>
         <artifactId>micronaut-bom</artifactId>
         <version>1.2.0.RC2</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>

2. Domain Model

Our database model consists of four tables as shown below. The same database model has been used for some of my previous examples including those for Spring Data usage. We have employee table. Each employee is assigned to the exactly one department and one organization. Each department is assigned to exactly one organization. There is also table employment, which provides a history of employment for every single employee.

micronaut-data-jpa-1

Here is the implementation of entity classes corresponding to the database model. Let’s start from Employee class:

@Entity
public class Employee {

   @Id
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "employee_id_seq")
   @SequenceGenerator(name = "employee_id_seq", sequenceName = "employee_id_seq", allocationSize = 1)
   private Long id;
   private String name;
   private int age;
   private String position;
   private int salary;
   @ManyToOne
   private Organization organization;
   @ManyToOne
   private Department department;
   @OneToMany
   private Set<Employment> employments;
   
   // ... GETTERS AND SETTERS
}

Here’s the implementation of Department class:


@Entity
public class Department {

   @Id
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "department_id_seq")
   @SequenceGenerator(name = "department_id_seq", sequenceName = "department_id_seq", allocationSize = 1)
   private Long id;
   private String name;
   @OneToMany
   private Set<Employee> employees;
   @ManyToOne
   private Organization organization;
   
   // ... GETTERS AND SETTERS
}

And here’s Organization entity:


@Entity
public class Organization {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "organization_id_seq")
    @SequenceGenerator(name = "organization_id_seq", sequenceName = "organization_id_seq", allocationSize = 1)
    private Long id;
    private String name;
    private String address;
    @OneToMany
    private Set<Department> departments;
    @OneToMany
    private Set<Employee> employees;
   
   // ... GETTERS AND SETTERS
}

And the last entity Employment:

@Entity
public class Employment {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "employment_id_seq")
    @SequenceGenerator(name = "employment_id_seq", sequenceName = "employment_id_seq", allocationSize = 1)
    private Long id;
    @ManyToOne
    private Employee employee;
    @ManyToOne
    private Organization organization;
    @Temporal(TemporalType.DATE)
    private Date start;
    @Temporal(TemporalType.DATE)
    private Date end;
   
   // ... GETTERS AND SETTERS
}

3. Creating JPA repositories with Micronaut Data

If you are familiar with the Spring Data repositories pattern, you won’t have any problems when using Micronaut repositories. The approach to declaring repositories and building queries is the same as in Spring Data. You need to declare an interface (or an abstract class) annotated with @Repository that extends interface CrudRepository. CrudRepository is not the only one interface that can be extended. You can also use GenericRepository, AsyncCrudRepository for asynchronous operations, ReactiveStreamsCrudRepository for reactive CRUD execution or PageableRepository that adds methods for pagination. The typical repository declaration looks like as shown below.

@Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long> {

    Set<EmployeeDTO> findBySalaryGreaterThan(int salary);

    Set<EmployeeDTO> findByOrganization(Organization organization);

    int findAvgSalaryByAge(int age);

    int findAvgSalaryByOrganization(Organization organization);

}

I have declared there some additional find methods. The most common query prefix is found, but you can also use search, query, get, read, or retrieve. The first two queries return all employees with a salary greater than a given value and all employees assigned to a given organization. The Employee entity is in many-to-one relation with Organization, so we may also use relational fields as query parameters. It is noteworthy that both two queries return DTO objects as a result inside the collection. That’s possible because Micronaut Predator supports reflection-free Data Transfer Object (DTO) projections if the return type is annotated with @Introspected. Here’s the declaration of EmployeeDTO.

@Introspected
public class EmployeeDTO {

    private String name;
    private int age;
    private String position;
    private int salary;
   
    // ... GETTERS AND SETTERS
}

The EmployeeRepository contains two other methods using aggregation expressions. Method findAvgSalaryByAge counts average salary by a given age of employees, while findAvgSalaryByOrganization counts avarage salary by a given organization.
For comparison, let’s take a look on another repository implementation EmploymentRepository. We need two additional find methods. First findByEmployeeOrderByStartDesc for searching employment history for a given employee ordered by start date. The second method finds employment without an end date set, which in fact means that’s the employment for a current job.

@Repository
public interface EmploymentRepository extends CrudRepository<Employment, Long> {

    Set<EmploymentDTO> findByEmployeeOrderByStartDesc(Employee employee);

    Employment findByEmployeeAndEndIsNull(Employee employee);

}

Micronaut Predator is able to automatically manage transactions. You just need to annotate your method with @Transactional. In the source code fragment visible below you may see the method used for changing a job by an employee. We are performing a bunch of save operations inside that method. First, we change the target department and organization for a given employee, then we are creating new employment history record for a new job, and also setting end date for the previous employment entity (found using repository method findByEmployeeAndEndIsNull).

@Inject
DepartmentRepository departmentRepository;
@Inject
EmployeeRepository employeeRepository;
@Inject
EmploymentRepository employmentRepository;

@Transactional
public void changeJob(Long employeeId, Long targetDepartmentId) {
   Optional<Employee> employee = employeeRepository.findById(employeeId);
   employee.ifPresent(employee1 -> {
      Optional<Department> department = departmentRepository.findById(targetDepartmentId);
      department.ifPresent(department1 -> {
         employee1.setDepartment(department1);
         employee1.setOrganization(department1.getOrganization());
         Employment employment = new Employment(employee1, department1.getOrganization(), new Date());
         employmentRepository.save(employment);
         Employment previousEmployment = employmentRepository.findByEmployeeAndEndIsNull(employee1);
         previousEmployment.setEnd(new Date());
         employmentRepository.save(previousEmployment);
      });
   });
}

Ok, now let’s move on to the last repository implementation discussed in this section – OrganizationRepository. Since Organization entity is in lazy load one-to-many relation with Employee and Department, we need to fetch data to present dependencies in the output. To achieve that we can use @Join annotation on the repository interface with specifying JOIN FETCH. Since the @Join annotation is repeatable it can be specified multiple times for different associations as shown below.

@Repository
public interface OrganizationRepository extends CrudRepository<Organization, Long> {

    @Join(value = "departments", type = Join.Type.LEFT_FETCH)
    @Join(value = "employees", type = Join.Type.LEFT_FETCH)
    Optional<Organization> findByName(String name);

}

4. Batch operations

Micronaut Predator repositories support batch operations. It can be sometimes useful, for example in automatic tests. Here’s my simple JUnit test that adds multiple employees into a single department inside an organization:

@Test
public void addMultiple() {
   List<Employee> employees = Arrays.asList(
      new Employee("Test1", 20, "Developer", 5000),
      new Employee("Test2", 30, "Analyst", 15000),
      new Employee("Test3", 40, "Manager", 25000),
      new Employee("Test4", 25, "Developer", 9000),
      new Employee("Test5", 23, "Analyst", 8000),
      new Employee("Test6", 50, "Developer", 12000),
      new Employee("Test7", 55, "Architect", 25000),
      new Employee("Test8", 43, "Manager", 15000)
   );

   Organization organization = new Organization("TestWithEmployees", "TestAddress");
   Organization organizationSaved = organizationRepository.save(organization);
   Assertions.assertNotNull(organization.getId());
   Department department = new Department("TestWithEmployees");
   department.setOrganization(organization);
   Department departmentSaved = departmentRepository.save(department);
   Assertions.assertNotNull(department.getId());
   employeeRepository.saveAll(employees.stream().map(employee -> {
      employee.setOrganization(organizationSaved);
      employee.setDepartment(departmentSaved);
      return employee;
   }).collect(Collectors.toList()));
}

5. Controllers

Finally, the last implementation step – building REST controllers. OrganizationController is pretty simple. It injects OrganizationRepository and uses it for saving entities and searching their by name. Here’s the implementation:

@Controller("organizations")
public class OrganizationController {

    @Inject
    OrganizationRepository repository;

    @Post("/organization")
    public Long addOrganization(@Body Organization organization) {
        Organization organization1 = repository.save(organization);
        return organization1.getId();
    }

    @Get("/organization/name/{name}")
    public Optional<Organization> findOrganization(@NotNull String name) {
        return repository.findByName(name);
    }

}

EmployeeController is a little bit more complicated. We have an implementation that exposes four additional find methods defined in EmployeeRepository. There is also a method for adding a new employee and assigning it to the department, and changing the job implemented inside SampleService bean.

@Controller("employees")
public class EmployeeController {

    @Inject
    EmployeeRepository repository;
    @Inject
    OrganizationRepository organizationRepository;
    @Inject
    SampleService service;

    @Get("/salary/{salary}")
    public Set<EmployeeDTO> findEmployeesBySalary(int salary) {
        return repository.findBySalaryGreaterThan(salary);
    }

    @Get("/organization/{organizationId}")
    public Set<EmployeeDTO> findEmployeesByOrganization(Long organizationId) {
        Optional<Organization> organization = organizationRepository.findById(organizationId);
        return repository.findByOrganization(organization.get());
    }

    @Get("/salary-avg/age/{age}")
    public int findAvgSalaryByAge(int age) {
        return repository.findAvgSalaryByAge(age);
    }

    @Get("/salary-avg/organization/{organizationId}")
    public int findAvgSalaryByAge(Long organizationId) {
        Optional<Organization> organization = organizationRepository.findById(organizationId);
        return repository.findAvgSalaryByOrganization(organization.get());
    }

    @Post("/{departmentId}")
    public void addNewEmployee(@Body Employee employee, Long departmentId) {
        service.hireEmployee(employee, departmentId);
    }

    @Put("/change-job")
    public void changeJob(@Body ChangeJobRequest request) {
        service.changeJob(request.getEmployeeId(), request.getTargetOrganizationId());
    }

}

6. Configuring database connection

As usual we use Docker image for running database instances locally. Here’s the command that runs container with Postgres and expose it on port 5432:

$ docker run -d --name postgres -p 5432:5432 -e POSTGRES_USER=predator -e POSTGRES_PASSWORD=predator123 -e POSTGRES_DB=predator postgres

After startup my Postgres instance is available on the virtual address 192.168.99.100, so I have to set it in the Micronaut application.yml. Besides database connection settings we will also set some JPA properties, that enable SQL logging and automatically applies model changes into a database schema. Here’s full configuration of our sample application inside application.yml:

micronaut:
  application:
    name: sample-micronaut-jpa

jackson:
  bean-introspection-module: true

datasources:
  default:
    url: jdbc:postgresql://192.168.99.100:5432/predator?ssl=false
    driverClassName: org.postgresql.Driver
    username: predator
    password: predator123

jpa:
  default:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        show_sql: true

Conclusion

The support for ORM was one of the most expected features for the Micronaut Framework. Not only it will be available in the release version soon, but it is almost 1.5x faster than Spring Data JPA – following this article https://objectcomputing.com/news/2019/07/18/unleashing-predator-precomputed-data-repositories created by the leader of Micronaut Project Graeme Rocher. In my opinion, the support for ORM via project Predator may be the reason that developers decide to use Micronaut instead of Spring Boot.
In this article, I have demonstrated the most interesting features of Micronaut Data JPA. I think that it will be continuously improved, and we see some new useful features soon. The sample application source code snippet is, as usual, available on GitHub: https://github.com/piomin/sample-micronaut-jpa.git. Before starting with Micronaut Data it is worth reading about the basics: Micronaut Tutorial: Beans and scopes.

The post JPA Data Access with Micronaut Data appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2019/07/25/jpa-data-access-with-micronaut-predator/feed/ 0 7196
Introduction to Spring Data Redis https://piotrminkowski.com/2019/03/05/introduction-to-spring-data-redis/ https://piotrminkowski.com/2019/03/05/introduction-to-spring-data-redis/#comments Tue, 05 Mar 2019 13:17:24 +0000 https://piotrminkowski.wordpress.com/?p=7026 Redis is an in-memory data structure store with optional durability, used as a database, cache, and message broker. Currently, it is the most most popular tool in the key/value stores category: https://db-engines.com/en/ranking/key-value+store. The easiest way to integrate your application with Redis is through the Spring Data Redis project. You can use Spring RedisTemplate directly for […]

The post Introduction to Spring Data Redis appeared first on Piotr's TechBlog.

]]>
Redis is an in-memory data structure store with optional durability, used as a database, cache, and message broker. Currently, it is the most most popular tool in the key/value stores category: https://db-engines.com/en/ranking/key-value+store. The easiest way to integrate your application with Redis is through the Spring Data Redis project. You can use Spring RedisTemplate directly for that or you might as well use Spring Data Redis repositories. There are some limitations when you integrate with Redis via Spring Data Redis repositories. They require at least Redis Server version 2.8.0 and do not work with transactions. Therefore you need to disable transaction support for RedisTemplate, which is leveraged by Redis repositories.

Redis is usually used for caching data stored in a relational database. In the current example, it will be treated as a primary database – just for simplification. Spring Data repositories do not require any deeper knowledge about Redis from a developer. You just need to annotate your domain class properly. As usual, we will examine the main features of Spring Data Redis based on the sample application. Supposing we have the system, which consists of three domain objects: Customer, Account and Transaction, here’s the picture that illustrates relationships between elements of that system. Transaction is always related with two accounts: sender (fromAccountId) and receiver (toAccountId). Each customer may have many accounts.

spring-data-redis-1.png

Although the picture visible above shows three independent domain models, customer and account are stored in the same, single structure. All customer’s accounts are stored as a list inside a customer object. Before proceeding to the sample application implementation details let’s begin by starting the Redis database.

1. Running Redis on Docker

We will run Redis standalone instance locally using its Docker container. You can start it in in-memory mode or with a persistence store. Here’s the command that runs a single, in-memory instance of Redis on a Docker container. It is exposed outside on default 6379 port.

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

2. Enabling Spring Data Redis Repositories and Configuring Connection

I’m using Docker Toolbox, so each container is available for me under address 192.168.99.100. Here’s the only one property that I need to override inside configuration settings (application.yml).

spring:
  application:
    name: sample-spring-redis
  redis:
    host: 192.168.99.100

To enable Redis repositories for Spring Boot application we just need to include the single starter <code>spring-boot-starter-data-redis</code>.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

We may choose between two supported connectors: Lettuce and Jedis. For Jedis I had to include one additional client’s library to dependencies, so I decided to use a simpler option – Lettuce, that does not require any additional libraries to work properly. To enable Spring Data Redis repositories we also need to annotate the main or the configuration class with @EnableRedisRepositories and declare RedisTemplate bean. Although we do not use RedisTemplate directly, we still need to declare it, while it is used by CRUD repositories for integration with Redis.

@Configuration
@EnableRedisRepositories
public class SampleSpringRedisConfiguration {

    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate() {
        RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        return template;
    }

}

3. Implementing domain entities

Each domain entity has at least to be annotated with @RedisHash, and contains property annotated with @Id. Those two items are responsible for creating the actual key used to persist the hash. Besides identifier properties annotated with @Id you may also use secondary indices. The good news about it is that it can be not only with dependent single objects, but also on lists and maps. Here’s the definition of Customer entity. It is available on Redis under customer key. It contains a list of Account entities.

@RedisHash("customer")
public class Customer {

    @Id private Long id;
    @Indexed private String externalId;
    private String name;
    private List<Account> accounts = new ArrayList<>();

    public Customer(Long id, String externalId, String name) {
        this.id = id;
        this.externalId = externalId;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getExternalId() {
        return externalId;
    }

    public void setExternalId(String externalId) {
        this.externalId = externalId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Account> getAccounts() {
        return accounts;
    }

    public void setAccounts(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void addAccount(Account account) {
        this.accounts.add(account);
    }

}

Account does not have its own hash. It is contained by Customer has a list of objects. The property id is indexed on Redis in order to speed-up the search based on the property.

public class Account {

    @Indexed private Long id;
    private String number;
    private int balance;

    public Account(Long id, String number, int balance) {
        this.id = id;
        this.number = number;
        this.balance = balance;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

}

Finally, let’s take a look on Transaction entity implementation. It uses only account ids, not the whole object.

@RedisHash("transaction")
public class Transaction {

    @Id
    private Long id;
    private int amount;
    private Date date;
    @Indexed
    private Long fromAccountId;
    @Indexed
    private Long toAccountId;

    public Transaction(Long id, int amount, Date date, Long fromAccountId, Long toAccountId) {
        this.id = id;
        this.amount = amount;
        this.date = date;
        this.fromAccountId = fromAccountId;
        this.toAccountId = toAccountId;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Long getFromAccountId() {
        return fromAccountId;
    }

    public void setFromAccountId(Long fromAccountId) {
        this.fromAccountId = fromAccountId;
    }

    public Long getToAccountId() {
        return toAccountId;
    }

    public void setToAccountId(Long toAccountId) {
        this.toAccountId = toAccountId;
    }

}

4. Implementing Spring Data Redis repositories

The implementation of repositories is the most pleasant part of our exercise. As usual with Spring Data projects, the most common methods like save, delete or findById are already implemented. So we only have to create our custom find methods if needed. Since usage and implementation of findByExternalId method is rather obvious, the method findByAccountsId may be not. Let’s move back to a model definition to clarify usage of that method. Transaction contains only account ids, it does not have direct relationship with Customer. What if we need to know the details about customers being a side of a given transaction? We can find a customer by one of its accounts from the list.

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    Customer findByExternalId(String externalId);
    List<Customer> findByAccountsId(Long id);

}

Here’s implementation of repository for Transaction entity.

public interface TransactionRepository extends CrudRepository<Transaction, Long> {

    List<Transaction> findByFromAccountId(Long fromAccountId);
    List<Transaction> findByToAccountId(Long toAccountId);

}

5. Building repository tests

We can easily test Redis repositories functionality using Spring Boot Test project with @DataRedisTest. This test assumes you have a running instance of Redis server on the already configured address 192.168.99.100.

@RunWith(SpringRunner.class)
@DataRedisTest
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class RedisCustomerRepositoryTest {

    @Autowired
    CustomerRepository repository;

    @Test
    public void testAdd() {
        Customer customer = new Customer(1L, "80010121098", "John Smith");
        customer.addAccount(new Account(1L, "1234567890", 2000));
        customer.addAccount(new Account(2L, "1234567891", 4000));
        customer.addAccount(new Account(3L, "1234567892", 6000));
        customer = repository.save(customer);
        Assert.assertNotNull(customer);
    }

    @Test
    public void testFindByAccounts() {
        List<Customer> customers = repository.findByAccountsId(3L);
        Assert.assertEquals(1, customers.size());
        Customer customer = customers.get(0);
        Assert.assertNotNull(customer);
        Assert.assertEquals(1, customer.getId().longValue());
    }

    @Test
    public void testFindByExternal() {
        Customer customer = repository.findByExternalId("80010121098");
        Assert.assertNotNull(customer);
        Assert.assertEquals(1, customer.getId().longValue());
    }
}

6. More advanced testing with Testcontainers

You may provide some advanced integration tests using Redis as Docker container started during the test by Testcontainer library. I have already published some articles about the Testcontainers framework. If you would like read more details about it please refer to my previous articles: Microservices Integration Tests with Hoverfly and Testcontainers and Testing Spring Boot Integration with Vault and Postgres using Testcontainers Framework.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class CustomerIntegrationTests {

    @Autowired
    TestRestTemplate template;

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

    @Before
    public void init() {
        int port = redis.getFirstMappedPort();
        System.setProperty("spring.redis.host", String.valueOf(port));
    }

    @Test
    public void testAddAndFind() {
        Customer customer = new Customer(1L, "123456", "John Smith");
        customer.addAccount(new Account(1L, "1234567890", 2000));
        customer.addAccount(new Account(2L, "1234567891", 4000));
        customer = template.postForObject("/customers", customer, Customer.class);
        Assert.assertNotNull(customer);
        customer = template.getForObject("/customers/{id}", Customer.class, 1L);
        Assert.assertNotNull(customer);
        Assert.assertEquals("123456", customer.getExternalId());
        Assert.assertEquals(2, customer.getAccounts().size());
    }

}

7. Viewing data in Redis

Now, let’s analyze the data stored in Redis after our JUnit tests. We may use one of the GUI tools for that. I decided to install RDBTools available on site https://rdbtools.com. You can easily browse data stored on Redis using this tool. Here’s the result for customer entity with id=1 after running JUnit test.

spring-data-redis-2

Here’s the similar result for transaction entity with id=1.

redis-3

Source Code

The sample application source code is available on GitHub in the repository sample-spring-redis

The post Introduction to Spring Data Redis appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2019/03/05/introduction-to-spring-data-redis/feed/ 6 7026