├── .circleci └── config.yml ├── pom.xml ├── readme.md ├── renovate.json └── src ├── main ├── java │ └── pl │ │ └── piomin │ │ └── services │ │ └── redis │ │ ├── SampleSpringRedisApplication.java │ │ ├── model │ │ ├── Account.java │ │ ├── Customer.java │ │ └── Transaction.java │ │ ├── repository │ │ ├── CustomerRepository.java │ │ └── TransactionRepository.java │ │ └── web │ │ ├── CustomerController.java │ │ └── TransactionController.java └── resources │ └── application.yml └── test └── java └── pl └── piomin └── services └── redis ├── CustomerIntegrationTests.java ├── RedisContainerDevMode.java ├── RedisCustomerRepositoryTest.java ├── RedisTransactionRepositoryTest.java └── SampleSpringRedisApplicationTest.java /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | jobs: 4 | analyze: 5 | docker: 6 | - image: 'cimg/openjdk:21.0.6' 7 | steps: 8 | - checkout 9 | - run: 10 | name: Analyze on SonarCloud 11 | command: mvn verify sonar:sonar -DskipTests 12 | test: 13 | executor: machine_executor_amd64 14 | steps: 15 | - checkout 16 | - run: 17 | name: Install OpenJDK 21 18 | command: | 19 | java -version 20 | sudo apt-get update && sudo apt-get install openjdk-21-jdk 21 | sudo update-alternatives --set java /usr/lib/jvm/java-21-openjdk-amd64/bin/java 22 | sudo update-alternatives --set javac /usr/lib/jvm/java-21-openjdk-amd64/bin/javac 23 | java -version 24 | export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 25 | - run: 26 | name: Maven Tests 27 | command: mvn test 28 | 29 | orbs: 30 | maven: circleci/maven@2.0.0 31 | 32 | executors: 33 | machine_executor_amd64: 34 | machine: 35 | image: ubuntu-2204:2023.10.1 36 | environment: 37 | architecture: "amd64" 38 | platform: "linux/amd64" 39 | 40 | workflows: 41 | maven_test: 42 | jobs: 43 | - test 44 | - analyze: 45 | context: SonarCloud -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.5.0 11 | 12 | 13 | pl.piomin.services 14 | sample-spring-redis 15 | 1.1-SNAPSHOT 16 | 17 | 18 | 21 19 | piomin_sample-spring-redis 20 | piomin 21 | https://sonarcloud.io 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-data-redis 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | 1.18.38 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | org.testcontainers 45 | testcontainers 46 | 1.21.1 47 | test 48 | 49 | 50 | org.testcontainers 51 | junit-jupiter 52 | 1.21.1 53 | test 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-testcontainers 58 | test 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | 68 | 69 | org.jacoco 70 | jacoco-maven-plugin 71 | 0.8.13 72 | 73 | 74 | 75 | prepare-agent 76 | 77 | 78 | 79 | report 80 | test 81 | 82 | report 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction to Spring Data Redis [![Twitter](https://img.shields.io/twitter/follow/piotr_minkowski.svg?style=social&logo=twitter&label=Follow%20Me)](https://twitter.com/piotr_minkowski) 2 | 3 | [![CircleCI](https://circleci.com/gh/piomin/sample-spring-redis.svg?style=svg)](https://circleci.com/gh/piomin/sample-spring-redis) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-redis) 6 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-redis&metric=bugs)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-redis) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-redis&metric=coverage)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-redis) 8 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-redis&metric=ncloc)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-redis) 9 | 10 | Detailed description can be found here: [Introduction to Spring Data Redis](https://piotrminkowski.com/2019/03/05/introduction-to-spring-data-redis/) 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base",":dependencyDashboard" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | } 11 | ], 12 | "prCreation": "not-pending" 13 | } -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/SampleSpringRedisApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 6 | 7 | @SpringBootApplication 8 | @EnableRedisRepositories 9 | public class SampleSpringRedisApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SampleSpringRedisApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/model/Account.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.springframework.data.redis.core.index.Indexed; 8 | 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Getter 12 | @Setter 13 | public class Account { 14 | @Indexed private Long id; 15 | private String number; 16 | private int balance; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/model/Customer.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.redis.core.RedisHash; 9 | import org.springframework.data.redis.core.index.Indexed; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @RedisHash("customer") 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | public class Customer { 20 | 21 | @Id private Long id; 22 | @Indexed private String externalId; 23 | private String name; 24 | private List accounts = new ArrayList<>(); 25 | 26 | public Customer(Long id, String externalId, String name) { 27 | this.id = id; 28 | this.externalId = externalId; 29 | this.name = name; 30 | } 31 | 32 | public void addAccount(Account account) { 33 | this.accounts.add(account); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/model/Transaction.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.springframework.data.annotation.Id; 8 | import org.springframework.data.redis.core.RedisHash; 9 | import org.springframework.data.redis.core.index.Indexed; 10 | 11 | import java.util.Date; 12 | 13 | @RedisHash("transaction") 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | public class Transaction { 19 | @Id 20 | private Long id; 21 | private int amount; 22 | private Date date; 23 | @Indexed 24 | private Long fromAccountId; 25 | @Indexed 26 | private Long toAccountId; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.services.redis.model.Customer; 5 | 6 | import java.util.List; 7 | 8 | public interface CustomerRepository extends CrudRepository { 9 | 10 | Customer findByExternalId(String externalId); 11 | List findByAccountsId(Long id); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/repository/TransactionRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.services.redis.model.Transaction; 5 | 6 | import java.util.List; 7 | 8 | public interface TransactionRepository extends CrudRepository { 9 | 10 | List findByFromAccountId(Long fromAccountId); 11 | List findByToAccountId(Long toAccountId); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/web/CustomerController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.*; 5 | import pl.piomin.services.redis.model.Customer; 6 | import pl.piomin.services.redis.model.Transaction; 7 | import pl.piomin.services.redis.repository.CustomerRepository; 8 | 9 | import java.util.Optional; 10 | 11 | @RestController 12 | @RequestMapping("/customers") 13 | public class CustomerController { 14 | 15 | @Autowired 16 | CustomerRepository repository; 17 | 18 | @PostMapping 19 | public Customer add(@RequestBody Customer customer) { 20 | return repository.save(customer); 21 | } 22 | 23 | @GetMapping("/{id}") 24 | public Customer findById(@PathVariable("id") Long id) { 25 | Optional optCustomer = repository.findById(id); 26 | return optCustomer.orElse(null); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/services/redis/web/TransactionController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.*; 5 | import pl.piomin.services.redis.model.Transaction; 6 | import pl.piomin.services.redis.repository.TransactionRepository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | @RestController 12 | @RequestMapping("/transactions") 13 | public class TransactionController { 14 | 15 | @Autowired 16 | TransactionRepository repository; 17 | 18 | @PostMapping 19 | public Transaction add(@RequestBody Transaction transaction) { 20 | return repository.save(transaction); 21 | } 22 | 23 | @GetMapping("/{id}") 24 | public Transaction findById(Long id) { 25 | Optional optTransaction = repository.findById(id); 26 | return optTransaction.orElse(null); 27 | } 28 | 29 | @GetMapping("/from/{accountId}") 30 | public List findByFromAccountId(Long accountId) { 31 | return repository.findByFromAccountId(accountId); 32 | } 33 | 34 | @GetMapping("/to/{accountId}") 35 | public List findByToAccountId(Long accountId) { 36 | return repository.findByToAccountId(accountId); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-redis 4 | data: 5 | redis: 6 | host: localhost 7 | server: 8 | port: 8090 -------------------------------------------------------------------------------- /src/test/java/pl/piomin/services/redis/CustomerIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.web.client.TestRestTemplate; 7 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 8 | import org.testcontainers.containers.GenericContainer; 9 | import org.testcontainers.junit.jupiter.Container; 10 | import org.testcontainers.junit.jupiter.Testcontainers; 11 | import pl.piomin.services.redis.model.Account; 12 | import pl.piomin.services.redis.model.Customer; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 17 | @Testcontainers 18 | public class CustomerIntegrationTests { 19 | 20 | @Autowired 21 | TestRestTemplate template; 22 | 23 | @Container 24 | @ServiceConnection 25 | static final GenericContainer redis = new GenericContainer("redis:latest") 26 | .withExposedPorts(6379); 27 | 28 | @Test 29 | void testAddAndFind() { 30 | assertTrue(redis.isRunning()); 31 | Customer customer = new Customer(1L, "123456", "John Smith"); 32 | customer.addAccount(new Account(1L, "1234567890", 2000)); 33 | customer.addAccount(new Account(2L, "1234567891", 4000)); 34 | customer = template.postForObject("/customers", customer, Customer.class); 35 | assertNotNull(customer); 36 | customer = template.getForObject("/customers/{id}", Customer.class, 1L); 37 | assertNotNull(customer); 38 | assertEquals("123456", customer.getExternalId()); 39 | assertEquals(2, customer.getAccounts().size()); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/services/redis/RedisContainerDevMode.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 5 | import org.springframework.context.annotation.Bean; 6 | import org.testcontainers.containers.GenericContainer; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | @TestConfiguration 10 | public class RedisContainerDevMode { 11 | 12 | @Bean 13 | @ServiceConnection("redis") 14 | GenericContainer redisContainer() { 15 | return new GenericContainer(DockerImageName.parse("redis").withTag("7.0")) 16 | .withExposedPorts(6379); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/services/redis/RedisCustomerRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.jupiter.api.MethodOrderer; 5 | import org.junit.jupiter.api.Order; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.TestMethodOrder; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; 10 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 11 | import org.testcontainers.containers.GenericContainer; 12 | import org.testcontainers.junit.jupiter.Container; 13 | import org.testcontainers.junit.jupiter.Testcontainers; 14 | import pl.piomin.services.redis.model.Account; 15 | import pl.piomin.services.redis.model.Customer; 16 | import pl.piomin.services.redis.repository.CustomerRepository; 17 | 18 | import java.util.List; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertEquals; 21 | import static org.junit.jupiter.api.Assertions.assertNotNull; 22 | 23 | @DataRedisTest 24 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 25 | @Testcontainers 26 | public class RedisCustomerRepositoryTest { 27 | 28 | @Container 29 | @ServiceConnection 30 | static final GenericContainer redis = new GenericContainer("redis:latest") 31 | .withExposedPorts(6379); 32 | 33 | @Autowired 34 | CustomerRepository repository; 35 | 36 | @Test 37 | @Order(1) 38 | void shouldAdd() { 39 | Customer customer = new Customer(1L, "80010121098", "John Smith"); 40 | customer.addAccount(new Account(1L, "1234567890", 2000)); 41 | customer.addAccount(new Account(2L, "1234567891", 4000)); 42 | customer.addAccount(new Account(3L, "1234567892", 6000)); 43 | customer = repository.save(customer); 44 | assertNotNull(customer); 45 | } 46 | 47 | // @Test 48 | // @Order(2) 49 | void shouldFindByAccounts() { 50 | List customers = repository.findByAccountsId(3L); 51 | assertEquals(1, customers.size()); 52 | Customer customer = customers.get(0); 53 | assertNotNull(customer); 54 | assertEquals(1, customer.getId().longValue()); 55 | } 56 | 57 | @Test 58 | @Order(3) 59 | void shouldFindByExternal() { 60 | Customer customer = repository.findByExternalId("80010121098"); 61 | assertNotNull(customer); 62 | assertEquals(1, customer.getId().longValue()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/services/redis/RedisTransactionRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.junit.jupiter.api.MethodOrderer; 4 | import org.junit.jupiter.api.Order; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.TestMethodOrder; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest; 9 | import org.springframework.boot.testcontainers.service.connection.ServiceConnection; 10 | import org.testcontainers.containers.GenericContainer; 11 | import org.testcontainers.junit.jupiter.Container; 12 | import org.testcontainers.junit.jupiter.Testcontainers; 13 | import pl.piomin.services.redis.model.Transaction; 14 | import pl.piomin.services.redis.repository.TransactionRepository; 15 | 16 | import java.util.Date; 17 | import java.util.List; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertNotNull; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | @DataRedisTest 23 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 24 | @Testcontainers 25 | public class RedisTransactionRepositoryTest { 26 | 27 | @Container 28 | @ServiceConnection 29 | static final GenericContainer redis = new GenericContainer("redis:latest") 30 | .withExposedPorts(6379); 31 | 32 | @Autowired 33 | TransactionRepository repository; 34 | 35 | @Test 36 | @Order(1) 37 | void shouldAdd() { 38 | Transaction transaction = new Transaction(1L, 1000, new Date(), 20L, 40L); 39 | transaction = repository.save(transaction); 40 | assertNotNull(transaction); 41 | } 42 | 43 | @Test 44 | @Order(2) 45 | public void shouldFindByFromAccountId() { 46 | List transactions = repository.findByFromAccountId(20L); 47 | assertTrue(transactions.size() == 1); 48 | } 49 | 50 | @Test 51 | @Order(3) 52 | public void shouldFindByToAccountId() { 53 | List transactions = repository.findByToAccountId(40L); 54 | assertTrue(transactions.size() == 1); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/services/redis/SampleSpringRedisApplicationTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.redis; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | 5 | public class SampleSpringRedisApplicationTest { 6 | 7 | public static void main(String[] args) { 8 | SpringApplication.from(SampleSpringRedisApplication::main) 9 | .with(RedisContainerDevMode.class) 10 | .run(args); 11 | } 12 | } 13 | --------------------------------------------------------------------------------