├── 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 [](https://twitter.com/piotr_minkowski)
2 |
3 | [](https://circleci.com/gh/piomin/sample-spring-elasticsearch)
4 |
5 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch)
6 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch)
7 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-elasticsearch)
8 | [](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 |
--------------------------------------------------------------------------------