├── employee-reactive-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── logback-test.xml │ │ │ └── application.yml │ │ └── java │ │ │ └── pl │ │ │ └── piomin │ │ │ └── services │ │ │ └── elasticsearch │ │ │ ├── repository │ │ │ └── EmployeeRepository.java │ │ │ ├── SampleApplication.java │ │ │ ├── model │ │ │ ├── Department.java │ │ │ ├── Organization.java │ │ │ └── Employee.java │ │ │ ├── controller │ │ │ └── EmployeeController.java │ │ │ └── SampleDataSet.java │ └── test │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── elasticsearch │ │ ├── EmployeeRepositoryTest.java │ │ └── EmployeeRepositoryPerformanceTest.java └── pom.xml ├── employee-service ├── src │ ├── test │ │ ├── resources │ │ │ ├── logback-test.xml │ │ │ └── application.yml │ │ └── java │ │ │ └── pl │ │ │ └── piomin │ │ │ └── services │ │ │ └── elasticsearch │ │ │ └── EmployeeRepositoryTest.java │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── pl │ │ └── piomin │ │ └── services │ │ └── elasticsearch │ │ ├── repository │ │ └── EmployeeRepository.java │ │ ├── model │ │ ├── Department.java │ │ ├── Organization.java │ │ └── Employee.java │ │ ├── SampleApplication.java │ │ ├── controller │ │ └── EmployeeController.java │ │ └── SampleDataSet.java └── pom.xml ├── renovate.json ├── .circleci └── config.yml ├── readme.md └── pom.xml /employee-reactive-service/src/main/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /employee-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /employee-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-elasticsearch 4 | # data: 5 | # elasticsearch: 6 | # cluster-name: docker-cluster 7 | # cluster-nodes: localhost:9300 8 | 9 | elasticsearch: 10 | rest: 11 | uris: http://localhost:9200 12 | 13 | initial-import: 14 | enabled: false -------------------------------------------------------------------------------- /employee-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-elasticsearch 4 | data: 5 | elasticsearch: 6 | cluster-name: docker-cluster 7 | cluster-nodes: 192.168.99.100:9300 8 | 9 | elasticsearch: 10 | rest: 11 | uris: http://192.168.99.100:9200 12 | 13 | initial-import: 14 | enabled: false -------------------------------------------------------------------------------- /employee-reactive-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-elasticsearch 4 | data: 5 | elasticsearch: 6 | client: 7 | reactive: 8 | endpoints: 192.168.99.100:9200 9 | elasticsearch: 10 | rest: 11 | uris: http://192.168.99.100:9200 12 | 13 | initial-import: 14 | enabled: false -------------------------------------------------------------------------------- /employee-reactive-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-elasticsearch 4 | data: 5 | elasticsearch: 6 | cluster-name: docker-cluster 7 | cluster-nodes: 192.168.99.100:9300 8 | 9 | elasticsearch: 10 | rest: 11 | uris: http://192.168.99.100:9200 12 | 13 | initial-import: 14 | enabled: false -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.repository; 2 | 3 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 4 | import org.springframework.stereotype.Repository; 5 | import pl.piomin.services.elasticsearch.model.Employee; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface EmployeeRepository extends ElasticsearchRepository { 11 | 12 | List findByOrganizationName(String name); 13 | List findByName(String name); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.repository; 2 | 3 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 4 | import org.springframework.stereotype.Repository; 5 | import pl.piomin.services.elasticsearch.model.Employee; 6 | import reactor.core.publisher.Flux; 7 | 8 | @Repository 9 | public interface EmployeeRepository extends ReactiveCrudRepository { 10 | 11 | Flux findByOrganizationName(String name); 12 | Flux findByName(String name); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/model/Department.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | public class Department { 4 | 5 | private Long id; 6 | private String name; 7 | 8 | public Department() { 9 | } 10 | 11 | public Department(Long id, String name) { 12 | this.id = id; 13 | this.name = name; 14 | } 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Long id) { 21 | this.id = id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Department{" + 35 | "id=" + id + 36 | ", name='" + name + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories; 8 | 9 | @SpringBootApplication 10 | @EnableReactiveElasticsearchRepositories 11 | public class SampleApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SampleApplication.class, args); 15 | } 16 | 17 | @Bean 18 | @ConditionalOnProperty("initial-import.enabled") 19 | public SampleDataSet dataSet() { 20 | return new SampleDataSet(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/model/Department.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | public class Department { 4 | 5 | private Long id; 6 | private String name; 7 | 8 | public Department() { 9 | } 10 | 11 | public Department(Long id, String name) { 12 | this.id = id; 13 | this.name = name; 14 | } 15 | 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Long id) { 21 | this.id = id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Department{" + 35 | "id=" + id + 36 | ", name='" + name + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/model/Organization.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | public class Organization { 4 | 5 | private Long id; 6 | private String name; 7 | private String address; 8 | 9 | public Organization() { 10 | } 11 | 12 | public Organization(Long id, String name, String address) { 13 | this.id = id; 14 | this.name = name; 15 | this.address = address; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getAddress() { 35 | return address; 36 | } 37 | 38 | public void setAddress(String address) { 39 | this.address = address; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Organization{" + 45 | "id=" + id + 46 | ", name='" + name + '\'' + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/model/Organization.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | public class Organization { 4 | 5 | private Long id; 6 | private String name; 7 | private String address; 8 | 9 | public Organization() { 10 | } 11 | 12 | public Organization(Long id, String name, String address) { 13 | this.id = id; 14 | this.name = name; 15 | this.address = address; 16 | } 17 | 18 | public Long getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Long id) { 23 | this.id = id; 24 | } 25 | 26 | public String getName() { 27 | return name; 28 | } 29 | 30 | public void setName(String name) { 31 | this.name = name; 32 | } 33 | 34 | public String getAddress() { 35 | return address; 36 | } 37 | 38 | public void setAddress(String address) { 39 | this.address = address; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "Organization{" + 45 | "id=" + id + 46 | ", name='" + name + '\'' + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.core.task.TaskExecutor; 8 | import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories; 9 | import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; 10 | 11 | import java.util.concurrent.Executors; 12 | 13 | @SpringBootApplication 14 | @EnableElasticsearchRepositories 15 | public class SampleApplication { 16 | 17 | public static void main(String[] args) { 18 | SpringApplication.run(SampleApplication.class, args); 19 | } 20 | 21 | @Bean 22 | @ConditionalOnProperty("initial-import.enabled") 23 | public SampleDataSet dataSet() { 24 | return new SampleDataSet(); 25 | } 26 | 27 | @Bean(name = "ConcurrentTaskExecutor") 28 | public TaskExecutor taskExecutor () { 29 | return new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: '2.1' 2 | 3 | jobs: 4 | analyze: 5 | docker: 6 | - image: 'cimg/openjdk:21.0.9' 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.1.1 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 -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Elasticsearch with Spring Boot [![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-elasticsearch.svg?style=svg)](https://circleci.com/gh/piomin/sample-spring-elasticsearch) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch) 6 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-elasticsearch&metric=bugs)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-elasticsearch&metric=coverage)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch) 8 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-elasticsearch&metric=ncloc)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch) 9 | 10 | 1. Detailed description for a standard option can be found here: [Elasticsearch with Spring Boot](https://piotrminkowski.com/2019/03/29/elasticsearch-with-spring-boot/) 11 | 2. Detailed description for a reactive option can be found here: [Reactive Elasticsearch with Spring Boot](https://piotrminkowski.wordpress.com/2019/10/25/reactive-elasticsearch-with-spring-boot/) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.7.18 11 | 12 | 13 | 14 | pl.piomin.services 15 | sample-spring-elasticsearch 16 | 1.1.0 17 | pom 18 | 19 | 20 | employee-service 21 | employee-reactive-service 22 | 23 | 24 | 25 | 21 26 | piomin_sample-spring-elasticsearch 27 | piomin 28 | https://sonarcloud.io 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-maven-plugin 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/controller/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.controller; 2 | 3 | import java.util.List; 4 | 5 | import pl.piomin.services.elasticsearch.model.Employee; 6 | import pl.piomin.services.elasticsearch.repository.EmployeeRepository; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | @RestController 17 | @RequestMapping("/employees") 18 | public class EmployeeController { 19 | 20 | @Autowired 21 | EmployeeRepository repository; 22 | 23 | @PostMapping 24 | public Employee add(@RequestBody Employee employee) { 25 | return repository.save(employee); 26 | } 27 | 28 | @GetMapping("/{name}") 29 | public List findByName(@PathVariable("name") String name) { 30 | return repository.findByName(name); 31 | } 32 | 33 | @GetMapping("/organization/{organizationName}") 34 | public List findByOrganizationName(@PathVariable("organizationName") String organizationName) { 35 | return repository.findByOrganizationName(organizationName); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/model/Employee.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.elasticsearch.annotations.Document; 5 | import org.springframework.data.elasticsearch.annotations.Field; 6 | import org.springframework.data.elasticsearch.annotations.FieldType; 7 | 8 | @Document(indexName = "employees") 9 | public class Employee { 10 | 11 | @Id 12 | private String id; 13 | @Field(type = FieldType.Object) 14 | private Organization organization; 15 | @Field(type = FieldType.Object) 16 | private Department department; 17 | private String name; 18 | private int age; 19 | private String position; 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public void setId(String id) { 26 | this.id = id; 27 | } 28 | 29 | public Organization getOrganization() { 30 | return organization; 31 | } 32 | 33 | public void setOrganization(Organization organization) { 34 | this.organization = organization; 35 | } 36 | 37 | public Department getDepartment() { 38 | return department; 39 | } 40 | 41 | public void setDepartment(Department department) { 42 | this.department = department; 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | 53 | public int getAge() { 54 | return age; 55 | } 56 | 57 | public void setAge(int age) { 58 | this.age = age; 59 | } 60 | 61 | public String getPosition() { 62 | return position; 63 | } 64 | 65 | public void setPosition(String position) { 66 | this.position = position; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "Employee{" + 72 | "id=" + id + 73 | ", organization=" + organization + 74 | ", department=" + department + 75 | ", name='" + name + '\'' + 76 | ", position='" + position + '\'' + 77 | '}'; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/model/Employee.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.model; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.elasticsearch.annotations.Document; 5 | import org.springframework.data.elasticsearch.annotations.Field; 6 | import org.springframework.data.elasticsearch.annotations.FieldType; 7 | 8 | @Document(indexName = "employees") 9 | public class Employee { 10 | 11 | @Id 12 | private String id; 13 | @Field(type = FieldType.Object) 14 | private Organization organization; 15 | @Field(type = FieldType.Object) 16 | private Department department; 17 | private String name; 18 | private int age; 19 | private String position; 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public void setId(String id) { 26 | this.id = id; 27 | } 28 | 29 | public Organization getOrganization() { 30 | return organization; 31 | } 32 | 33 | public void setOrganization(Organization organization) { 34 | this.organization = organization; 35 | } 36 | 37 | public Department getDepartment() { 38 | return department; 39 | } 40 | 41 | public void setDepartment(Department department) { 42 | this.department = department; 43 | } 44 | 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | public void setName(String name) { 50 | this.name = name; 51 | } 52 | 53 | public int getAge() { 54 | return age; 55 | } 56 | 57 | public void setAge(int age) { 58 | this.age = age; 59 | } 60 | 61 | public String getPosition() { 62 | return position; 63 | } 64 | 65 | public void setPosition(String position) { 66 | this.position = position; 67 | } 68 | 69 | @Override 70 | public String toString() { 71 | return "Employee{" + 72 | "id=" + id + 73 | ", organization=" + organization + 74 | ", department=" + department + 75 | ", name='" + name + '\'' + 76 | ", position='" + position + '\'' + 77 | '}'; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /employee-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | pl.piomin.services 8 | sample-spring-elasticsearch 9 | 1.1.0 10 | 11 | 12 | pl.piomin 13 | employee-service 14 | 15 | 16 | ${project.artifactId} 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-actuator 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-elasticsearch 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | org.testcontainers 39 | elasticsearch 40 | 1.21.4 41 | test 42 | 43 | 44 | org.testcontainers 45 | junit-jupiter 46 | 1.21.4 47 | test 48 | 49 | 50 | com.carrotsearch 51 | junit-benchmarks 52 | 0.7.2 53 | test 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /employee-reactive-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | pl.piomin.services 8 | sample-spring-elasticsearch 9 | 1.1.0 10 | 11 | 12 | pl.piomin 13 | employee-reactive-service 14 | 15 | 16 | ${project.artifactId} 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-webflux 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-actuator 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-elasticsearch 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-test 35 | test 36 | 37 | 38 | org.testcontainers 39 | elasticsearch 40 | 1.21.4 41 | test 42 | 43 | 44 | com.carrotsearch 45 | junit-benchmarks 46 | 0.7.2 47 | test 48 | 49 | 50 | net.jodah 51 | concurrentunit 52 | 0.4.6 53 | test 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /employee-reactive-service/src/test/java/pl/piomin/services/elasticsearch/EmployeeRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import org.junit.*; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.MethodSorters; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import org.testcontainers.elasticsearch.ElasticsearchContainer; 10 | import pl.piomin.services.elasticsearch.model.Department; 11 | import pl.piomin.services.elasticsearch.model.Employee; 12 | import pl.piomin.services.elasticsearch.model.Organization; 13 | import pl.piomin.services.elasticsearch.repository.EmployeeRepository; 14 | import reactor.core.publisher.Flux; 15 | import reactor.core.publisher.Mono; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 20 | public class EmployeeRepositoryTest { 21 | 22 | @ClassRule 23 | public static ElasticsearchContainer container = new ElasticsearchContainer(); 24 | @Autowired 25 | EmployeeRepository repository; 26 | 27 | @BeforeClass 28 | public static void before() { 29 | System.setProperty("spring.data.elasticsearch.client.reactive.endpoints", container.getContainerIpAddress() + ":" + container.getMappedPort(9200)); 30 | } 31 | 32 | @Test 33 | public void testAdd() { 34 | Employee employee = new Employee(); 35 | employee.setId("1"); 36 | employee.setName("John Smith"); 37 | employee.setAge(33); 38 | employee.setPosition("Developer"); 39 | employee.setDepartment(new Department(1L, "TestD")); 40 | employee.setOrganization(new Organization(1L, "TestO", "Test Street No. 1")); 41 | Mono employeeSaved = repository.save(employee); 42 | Assert.assertNotNull(employeeSaved.block()); 43 | } 44 | 45 | @Test 46 | public void testFindAll() { 47 | Flux employees = repository.findAll(); 48 | Assert.assertTrue(employees.count().block() > 0); 49 | } 50 | 51 | @Test 52 | public void testFindByOrganization() { 53 | Flux employees = repository.findByOrganizationName("TestO"); 54 | Assert.assertTrue(employees.count().block() > 0); 55 | } 56 | 57 | @Test 58 | public void testFindByName() { 59 | Flux employees = repository.findByName("John Smith"); 60 | Assert.assertTrue(employees.count().block() > 0); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /employee-service/src/test/java/pl/piomin/services/elasticsearch/EmployeeRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import org.junit.jupiter.api.MethodOrderer; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.TestMethodOrder; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.DynamicPropertyRegistry; 9 | import org.springframework.test.context.DynamicPropertySource; 10 | import org.testcontainers.elasticsearch.ElasticsearchContainer; 11 | import org.testcontainers.junit.jupiter.Container; 12 | import org.testcontainers.junit.jupiter.Testcontainers; 13 | import pl.piomin.services.elasticsearch.model.Department; 14 | import pl.piomin.services.elasticsearch.model.Employee; 15 | import pl.piomin.services.elasticsearch.model.Organization; 16 | import pl.piomin.services.elasticsearch.repository.EmployeeRepository; 17 | 18 | import java.util.List; 19 | 20 | import static org.junit.jupiter.api.Assertions.assertNotNull; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | @SpringBootTest 24 | @Testcontainers 25 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 26 | public class EmployeeRepositoryTest { 27 | 28 | @Autowired 29 | EmployeeRepository repository; 30 | 31 | @Container 32 | public static ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.9.2"); 33 | 34 | @DynamicPropertySource 35 | static void registerElasticsearchProperties(DynamicPropertyRegistry registry) { 36 | // String uri = container.getContainerIpAddress() + ":" + container.getMappedPort(9200); 37 | registry.add("spring.elasticsearch.rest.uris", () -> container.getHttpHostAddress()); 38 | } 39 | 40 | @Test 41 | public void testAdd() { 42 | Employee employee = new Employee(); 43 | employee.setName("John Smith"); 44 | employee.setAge(33); 45 | employee.setPosition("Developer"); 46 | employee.setDepartment(new Department(1L, "TestD")); 47 | employee.setOrganization(new Organization(1L, "TestO", "Test Street No. 1")); 48 | employee = repository.save(employee); 49 | assertNotNull(employee); 50 | assertNotNull(employee.getId()); 51 | } 52 | 53 | @Test 54 | public void testFindAll() { 55 | Iterable employees = repository.findAll(); 56 | assertTrue(employees.iterator().hasNext()); 57 | } 58 | 59 | @Test 60 | public void testFindByOrganization() { 61 | List employees = repository.findByOrganizationName("TestO"); 62 | assertTrue(employees.size() > 0); 63 | } 64 | 65 | @Test 66 | public void testFindByName() { 67 | List employees = repository.findByName("John Smith"); 68 | assertTrue(employees.size() > 0); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/controller/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch.controller; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import pl.piomin.services.elasticsearch.model.Department; 10 | import pl.piomin.services.elasticsearch.model.Employee; 11 | import pl.piomin.services.elasticsearch.model.Organization; 12 | import pl.piomin.services.elasticsearch.repository.EmployeeRepository; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RestController; 21 | import reactor.core.publisher.Flux; 22 | import reactor.core.publisher.Mono; 23 | 24 | @RestController 25 | @RequestMapping("/employees") 26 | public class EmployeeController { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeController.class); 29 | 30 | @Autowired 31 | EmployeeRepository repository; 32 | 33 | @PostMapping 34 | public Mono add(@RequestBody Employee employee) { 35 | return repository.save(employee); 36 | } 37 | 38 | @GetMapping("/{name}") 39 | public Flux findByName(@PathVariable("name") String name) { 40 | return repository.findByName(name); 41 | } 42 | 43 | @GetMapping 44 | public Flux findAll() { 45 | return repository.findAll(); 46 | } 47 | 48 | @GetMapping("/count/all") 49 | public Mono count() { 50 | return repository.count(); 51 | } 52 | 53 | @GetMapping("/organization/{organizationName}") 54 | public Flux findByOrganizationName(@PathVariable("organizationName") String organizationName) { 55 | return repository.findByOrganizationName(organizationName); 56 | } 57 | 58 | @PostMapping("/generate") 59 | public Flux generateMulti() { 60 | return repository.saveAll(employees()).doOnNext(employee -> LOGGER.info("Added: {}", employee)); 61 | } 62 | 63 | private List employees() { 64 | List employees = new ArrayList<>(); 65 | for (int i = 0; i < 200; i++) { 66 | Random r = new Random(); 67 | Employee employee = new Employee(); 68 | employee.setName("JohnSmith" + r.nextInt(1000000)); 69 | employee.setAge(r.nextInt(100)); 70 | employee.setPosition("Developer"); 71 | int departmentId = r.nextInt(5000); 72 | employee.setDepartment(new Department((long) departmentId, "TestD" + departmentId)); 73 | int organizationId = departmentId % 100; 74 | employee.setOrganization(new Organization((long) organizationId, "TestO" + organizationId, "Test Street No. " + organizationId)); 75 | employees.add(employee); 76 | } 77 | return employees; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /employee-service/src/main/java/pl/piomin/services/elasticsearch/SampleDataSet.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.core.task.TaskExecutor; 8 | import org.springframework.data.elasticsearch.core.ElasticsearchOperations; 9 | import org.springframework.data.elasticsearch.core.IndexOperations; 10 | import org.springframework.data.elasticsearch.core.query.IndexQuery; 11 | import pl.piomin.services.elasticsearch.model.Department; 12 | import pl.piomin.services.elasticsearch.model.Employee; 13 | import pl.piomin.services.elasticsearch.model.Organization; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Random; 19 | 20 | public class SampleDataSet { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(SampleDataSet.class); 23 | private static final String INDEX_NAME = "sample"; 24 | private static final String INDEX_TYPE = "employee"; 25 | private static int COUNTER = 0; 26 | 27 | @Autowired 28 | IndexOperations indexOperations; 29 | @Autowired 30 | ElasticsearchOperations template; 31 | @Autowired 32 | TaskExecutor taskExecutor; 33 | 34 | @PostConstruct 35 | public void init() { 36 | if (!indexOperations.exists()) { 37 | indexOperations.create(); 38 | LOGGER.info("New index created: {}", INDEX_NAME); 39 | } 40 | for (int i = 0; i < 10000; i++) { 41 | taskExecutor.execute(() -> bulk()); 42 | } 43 | } 44 | 45 | public void bulk() { 46 | try { 47 | ObjectMapper mapper = new ObjectMapper(); 48 | List queries = new ArrayList<>(); 49 | List employees = employees(); 50 | for (Employee employee : employees) { 51 | IndexQuery indexQuery = new IndexQuery(); 52 | indexQuery.setSource(mapper.writeValueAsString(employee)); 53 | // TODO - think about it 54 | // indexQuery.setIndexName(INDEX_NAME); 55 | queries.add(indexQuery); 56 | } 57 | if (queries.size() > 0) { 58 | template.bulkIndex(queries, Employee.class); 59 | } 60 | // TODO - replace it 61 | // template.refresh(INDEX_NAME); 62 | LOGGER.info("BulkIndex completed: {}", ++COUNTER); 63 | } catch (Exception e) { 64 | LOGGER.error("Error bulk index", e); 65 | } 66 | } 67 | 68 | private List employees() { 69 | List employees = new ArrayList<>(); 70 | for (int i = 0; i < 10000; i++) { 71 | Random r = new Random(); 72 | Employee employee = new Employee(); 73 | employee.setName("JohnSmith" + r.nextInt(1000000)); 74 | employee.setAge(r.nextInt(100)); 75 | employee.setPosition("Developer"); 76 | int departmentId = r.nextInt(500000); 77 | employee.setDepartment(new Department((long) departmentId, "TestD" + departmentId)); 78 | int organizationId = departmentId / 100; 79 | employee.setOrganization(new Organization((long) organizationId, "TestO" + organizationId, "Test Street No. " + organizationId)); 80 | employees.add(employee); 81 | } 82 | return employees; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /employee-reactive-service/src/main/java/pl/piomin/services/elasticsearch/SampleDataSet.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; 7 | import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate; 8 | import pl.piomin.services.elasticsearch.model.Department; 9 | import pl.piomin.services.elasticsearch.model.Employee; 10 | import pl.piomin.services.elasticsearch.model.Organization; 11 | import pl.piomin.services.elasticsearch.repository.EmployeeRepository; 12 | import reactor.core.publisher.Flux; 13 | import reactor.core.publisher.Mono; 14 | 15 | import javax.annotation.PostConstruct; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Random; 19 | 20 | public class SampleDataSet { 21 | 22 | private static final Logger LOGGER = LoggerFactory.getLogger(SampleDataSet.class); 23 | private static final String INDEX_NAME = "sample"; 24 | private static final String INDEX_TYPE = "employee"; 25 | 26 | @Autowired 27 | EmployeeRepository repository; 28 | @Autowired 29 | ReactiveElasticsearchTemplate template; 30 | @Autowired 31 | ReactiveElasticsearchClient client; 32 | 33 | @PostConstruct 34 | public void init() throws InterruptedException { 35 | for (int i = 0; i < 10000; i++) { 36 | bulk(i); 37 | Thread.sleep(10000); 38 | } 39 | } 40 | 41 | public void bulk(int ii) { 42 | try { 43 | client.indices().existsIndex(request -> request.indices(INDEX_NAME)) 44 | .flatMap( 45 | ex -> { 46 | if (!ex) { 47 | LOGGER.info("Creating index: {}", INDEX_NAME); 48 | return client.indices().createIndex(request -> request.index(INDEX_NAME)); 49 | } else { 50 | return Mono.empty(); 51 | } 52 | }).subscribe(); 53 | List employees = employees(); 54 | Flux s = repository.saveAll(employees); 55 | s.subscribe(empl -> LOGGER.info("ADD: {}", empl), e -> LOGGER.info("Error: {}", e.getMessage())); 56 | LOGGER.info("BulkIndex completed: {}", ii); 57 | } catch (Exception e) { 58 | LOGGER.error("Error bulk index", e); 59 | } 60 | } 61 | 62 | private List employees() { 63 | List employees = new ArrayList<>(); 64 | repository.count().doOnNext(cnt -> 65 | LOGGER.info("Starting from id: {}", cnt.intValue()) 66 | ).subscribe(); 67 | 68 | for (int i = 0; i < 100; i++) { 69 | Random r = new Random(); 70 | Employee employee = new Employee(); 71 | employee.setName("JohnSmith" + r.nextInt(1000000)); 72 | employee.setAge(r.nextInt(100)); 73 | employee.setPosition("Developer"); 74 | int departmentId = r.nextInt(5000); 75 | employee.setDepartment(new Department((long) departmentId, "TestD" + departmentId)); 76 | int organizationId = departmentId % 100; 77 | employee.setOrganization(new Organization((long) organizationId, "TestO" + organizationId, "Test Street No. " + organizationId)); 78 | employees.add(employee); 79 | } 80 | return employees; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /employee-reactive-service/src/test/java/pl/piomin/services/elasticsearch/EmployeeRepositoryPerformanceTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.services.elasticsearch; 2 | 3 | import com.carrotsearch.junitbenchmarks.BenchmarkOptions; 4 | import com.carrotsearch.junitbenchmarks.BenchmarkRule; 5 | import net.jodah.concurrentunit.Waiter; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import org.junit.rules.TestRule; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.http.HttpHeaders; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import pl.piomin.services.elasticsearch.model.Department; 14 | import pl.piomin.services.elasticsearch.model.Employee; 15 | import pl.piomin.services.elasticsearch.model.Organization; 16 | import reactor.core.publisher.Flux; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.util.Random; 20 | import java.util.concurrent.TimeoutException; 21 | 22 | public class EmployeeRepositoryPerformanceTest { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeRepositoryPerformanceTest.class); 25 | 26 | @Rule 27 | public TestRule benchmarkRun = new BenchmarkRule(); 28 | 29 | private final Random r = new Random(); 30 | private final WebClient client = WebClient.builder() 31 | .baseUrl("http://localhost:8080") 32 | .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json") 33 | .build(); 34 | 35 | @Test 36 | @BenchmarkOptions(concurrency = 30, benchmarkRounds = 500, warmupRounds = 2) 37 | public void addTest() throws TimeoutException, InterruptedException { 38 | final Waiter waiter = new Waiter(); 39 | Employee employee = new Employee(); 40 | employee.setName("John Smith"); 41 | employee.setAge(r.nextInt(100)); 42 | employee.setPosition("Developer"); 43 | employee.setDepartment(new Department((long) r.nextInt(10), "TestD")); 44 | employee.setOrganization(new Organization((long) r.nextInt(10), "TestO", "Test Street No. 1")); 45 | Mono empMono = client.post().uri("/employees").body(Mono.just(employee), Employee.class).retrieve().bodyToMono(Employee.class); 46 | empMono.subscribe(employeeLocal -> { 47 | waiter.assertNotNull(employeeLocal); 48 | waiter.assertNotNull(employeeLocal.getId()); 49 | waiter.resume(); 50 | }); 51 | waiter.await(5000); 52 | } 53 | 54 | @Test 55 | @BenchmarkOptions(concurrency = 30, benchmarkRounds = 500, warmupRounds = 2) 56 | public void findByNameTest() throws TimeoutException, InterruptedException { 57 | final Waiter waiter = new Waiter(); 58 | String name = "JohnSmith" + r.nextInt(1000000); 59 | Flux employees = client.get().uri("/employees/{name}", name).retrieve().bodyToFlux(Employee.class); 60 | employees.count().subscribe(count -> { 61 | waiter.assertTrue(count > 0); 62 | waiter.resume(); 63 | LOGGER.info("Found({}): {}", name, count); 64 | }); 65 | waiter.await(5000); 66 | } 67 | 68 | @Test 69 | @BenchmarkOptions(concurrency = 30, benchmarkRounds = 500, warmupRounds = 2) 70 | public void findByOrganizationNameTest() throws TimeoutException, InterruptedException { 71 | final Waiter waiter = new Waiter(); 72 | String organizationName = "TestO" + r.nextInt(5000); 73 | Flux employees = client.get().uri("/employees/organization/{organizationName}", organizationName).retrieve().bodyToFlux(Employee.class); 74 | employees.count().subscribe(count -> { 75 | waiter.assertTrue(count > 0); 76 | waiter.resume(); 77 | LOGGER.info("Found: {}", count); 78 | }); 79 | waiter.await(5000); 80 | } 81 | 82 | } 83 | --------------------------------------------------------------------------------