From bc1f922d1cb9ea1796434437623c5e4a852118b0 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sun, 9 Oct 2022 17:59:00 +0800 Subject: [PATCH 1/3] Finish part 1. Introduce ES library and implement simple CRUD. --- pom.xml | 12 ++ .../es/config/ElasticSearchConfig.java | 30 ++++ .../es/controller/StudentController.java | 52 +++++++ .../java/com/vincent/es/entity/Course.java | 22 +++ src/main/java/com/vincent/es/entity/Job.java | 22 +++ .../java/com/vincent/es/entity/Student.java | 88 +++++++++++ .../es/repository/StudentEsRepository.java | 138 ++++++++++++++++++ .../java/com/vincent/es/util/IOSupplier.java | 8 + 8 files changed, 372 insertions(+) create mode 100644 src/main/java/com/vincent/es/config/ElasticSearchConfig.java create mode 100644 src/main/java/com/vincent/es/controller/StudentController.java create mode 100644 src/main/java/com/vincent/es/entity/Course.java create mode 100644 src/main/java/com/vincent/es/entity/Job.java create mode 100644 src/main/java/com/vincent/es/entity/Student.java create mode 100644 src/main/java/com/vincent/es/repository/StudentEsRepository.java create mode 100644 src/main/java/com/vincent/es/util/IOSupplier.java diff --git a/pom.xml b/pom.xml index 592c399..5dba260 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,18 @@ junit junit + + + co.elastic.clients + elasticsearch-java + 8.3.1 + + + + jakarta.json + jakarta.json-api + 2.1.0 + diff --git a/src/main/java/com/vincent/es/config/ElasticSearchConfig.java b/src/main/java/com/vincent/es/config/ElasticSearchConfig.java new file mode 100644 index 0000000..9bd7697 --- /dev/null +++ b/src/main/java/com/vincent/es/config/ElasticSearchConfig.java @@ -0,0 +1,30 @@ +package com.vincent.es.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import com.vincent.es.repository.StudentEsRepository; +import org.apache.http.HttpHost; +import org.elasticsearch.client.RestClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ElasticSearchConfig { + + @Bean + public ElasticsearchClient elasticsearchClient() { + var httpHost = new HttpHost("localhost", 9200); + var restClient = RestClient.builder(httpHost).build(); + var transport = new RestClientTransport(restClient, new JacksonJsonpMapper()); + + return new ElasticsearchClient(transport); + } + + @Bean + public StudentEsRepository studentEsRepository(ElasticsearchClient client) { + var repo = new StudentEsRepository(client, "student"); + repo.init(); + return repo; + } +} \ No newline at end of file diff --git a/src/main/java/com/vincent/es/controller/StudentController.java b/src/main/java/com/vincent/es/controller/StudentController.java new file mode 100644 index 0000000..f466a26 --- /dev/null +++ b/src/main/java/com/vincent/es/controller/StudentController.java @@ -0,0 +1,52 @@ +package com.vincent.es.controller; + +import com.vincent.es.entity.Student; +import com.vincent.es.repository.StudentEsRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping(value = "/students", produces = MediaType.APPLICATION_JSON_VALUE) +public class StudentController { + + @Autowired + private StudentEsRepository studentEsRepository; + + @PostMapping + public ResponseEntity create(@RequestBody Student request) { + var student = studentEsRepository.insert(request); + return ResponseEntity.status(HttpStatus.CREATED).body(student); + } + + @PostMapping("/bulk") + public ResponseEntity create(@RequestBody List requests) { + studentEsRepository.insert(requests); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable("id") String id, @RequestBody Student request) { + request.setId(id); + studentEsRepository.save(request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable("id") String id) { + studentEsRepository.deleteById(id); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/{id}") + public ResponseEntity get(@PathVariable("id") String id) { + var student = studentEsRepository.findById(id).orElse(null); + return student == null + ? ResponseEntity.notFound().build() + : ResponseEntity.ok(student); + } +} diff --git a/src/main/java/com/vincent/es/entity/Course.java b/src/main/java/com/vincent/es/entity/Course.java new file mode 100644 index 0000000..0beee7b --- /dev/null +++ b/src/main/java/com/vincent/es/entity/Course.java @@ -0,0 +1,22 @@ +package com.vincent.es.entity; + +public class Course { + private String name; // 課程名稱 + private int point; // 學分數 + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getPoint() { + return point; + } + + public void setPoint(int point) { + this.point = point; + } +} diff --git a/src/main/java/com/vincent/es/entity/Job.java b/src/main/java/com/vincent/es/entity/Job.java new file mode 100644 index 0000000..6468604 --- /dev/null +++ b/src/main/java/com/vincent/es/entity/Job.java @@ -0,0 +1,22 @@ +package com.vincent.es.entity; + +public class Job { + private String name; // 職務名稱 + private Boolean primary; // 是否主要 (正, 副) + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Boolean getPrimary() { + return primary; + } + + public void setPrimary(Boolean primary) { + this.primary = primary; + } +} diff --git a/src/main/java/com/vincent/es/entity/Student.java b/src/main/java/com/vincent/es/entity/Student.java new file mode 100644 index 0000000..439fe2a --- /dev/null +++ b/src/main/java/com/vincent/es/entity/Student.java @@ -0,0 +1,88 @@ +package com.vincent.es.entity; + +import java.util.Date; +import java.util.List; + +public class Student { + private String id; // 學生編號,可當作 document id + private String name; // 姓名 + private List departments; // 科系 + private List courses; // 修習課程 + private int grade; // 年級 + private int conductScore; // 操行成績 + private Job job; // 職務 + private String introduction; // 自我介紹 + private Date englishIssuedDate; // 英文檢定通過日 + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getDepartments() { + return departments; + } + + public void setDepartments(List departments) { + this.departments = departments; + } + + public List getCourses() { + return courses; + } + + public void setCourses(List courses) { + this.courses = courses; + } + + public int getGrade() { + return grade; + } + + public void setGrade(int grade) { + this.grade = grade; + } + + public int getConductScore() { + return conductScore; + } + + public void setConductScore(int conductScore) { + this.conductScore = conductScore; + } + + public Job getJob() { + return job; + } + + public void setJob(Job job) { + this.job = job; + } + + public String getIntroduction() { + return introduction; + } + + public void setIntroduction(String introduction) { + this.introduction = introduction; + } + + public Date getEnglishIssuedDate() { + return englishIssuedDate; + } + + public void setEnglishIssuedDate(Date englishIssuedDate) { + this.englishIssuedDate = englishIssuedDate; + } +} diff --git a/src/main/java/com/vincent/es/repository/StudentEsRepository.java b/src/main/java/com/vincent/es/repository/StudentEsRepository.java new file mode 100644 index 0000000..40af134 --- /dev/null +++ b/src/main/java/com/vincent/es/repository/StudentEsRepository.java @@ -0,0 +1,138 @@ +package com.vincent.es.repository; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.mapping.DateProperty; +import co.elastic.clients.elasticsearch._types.mapping.Property; +import co.elastic.clients.elasticsearch._types.mapping.TypeMapping; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.bulk.BulkOperation; +import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem; +import co.elastic.clients.elasticsearch.core.bulk.CreateOperation; +import co.elastic.clients.elasticsearch.indices.CreateIndexRequest; +import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest; +import com.vincent.es.entity.Student; +import com.vincent.es.util.IOSupplier; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class StudentEsRepository { + private final ElasticsearchClient client; + private final String indexName; + + public StudentEsRepository(ElasticsearchClient client, String indexName) { + this.client = client; + this.indexName = indexName; + } + + public void init() { + deleteIndex(); + createIndex(); + } + + public void createIndex() { + var propertyMap = getPropertyMappings(); + var request = new CreateIndexRequest.Builder() + .index(indexName) + .mappings(TypeMapping.of(b -> b.properties(propertyMap))) + .build(); + + execute(() -> client.indices().create(request)); + } + + public void deleteIndex() { + var request = DeleteIndexRequest.of(b -> b.index(indexName)); + execute(() -> client.indices().delete(request)); + } + + public Student insert(Student doc) { + var request = new CreateRequest.Builder() + .index(indexName) + .id(doc.getId()) + .document(doc) + .build(); + + return execute(() -> { + var createResponse = client.create(request); + doc.setId(createResponse.id()); + return doc; + }); + } + + public List insert(List docs) { + var builder = new BulkRequest.Builder().index(indexName); + + docs.forEach(doc -> { + var createOp = new CreateOperation.Builder() + .id(doc.getId()) + .document(doc) + .build(); + var bulkOp = BulkOperation.of(b -> b.create(createOp)); + builder.operations(bulkOp); + }); + + var bulkRequest = builder.build(); + + return execute(() -> { + var bulkResponse = client.bulk(bulkRequest); + List items = bulkResponse.items(); + for (var i = 0; i < items.size(); i++) { + var id = items.get(i).id(); + docs.get(i).setId(id); + } + + return docs; + }); + } + + public Optional findById(String id) { + var request = new GetRequest.Builder() + .index(indexName) + .id(id) + .build(); + + var getResponse = execute(() -> client.get(request, Student.class)); + return Optional.ofNullable(getResponse.source()); + } + + public Student save(Student doc) { + var request = new IndexRequest.Builder() + .index(indexName) + .id(doc.getId()) + .document(doc) + .build(); + + return execute(() -> { + var indexResponse = client.index(request); + doc.setId(indexResponse.id()); + return doc; + }); + } + + public void deleteById(String id) { + var request = new DeleteRequest.Builder() + .index(indexName) + .id(id) + .build(); + + execute(() -> client.delete(request)); + } + + private Map getPropertyMappings() { + var englishIssuedDateProperty = DateProperty.of(b -> b)._toProperty(); + return Map.of("englishIssuedDate", englishIssuedDateProperty); + } + + @SuppressWarnings({"squid:S112"}) + private V execute(IOSupplier supplier) { + try { + return supplier.get(); + } catch (IOException e) { + // 範例為求簡便,只包裝成 RuntimeException + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/vincent/es/util/IOSupplier.java b/src/main/java/com/vincent/es/util/IOSupplier.java new file mode 100644 index 0000000..71381b2 --- /dev/null +++ b/src/main/java/com/vincent/es/util/IOSupplier.java @@ -0,0 +1,8 @@ +package com.vincent.es.util; + +import java.io.IOException; + +@FunctionalInterface +public interface IOSupplier { + V get() throws IOException; +} From bc5b14ee563b887820f8794a3e8f7c339d4a0534 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sat, 22 Oct 2022 00:19:14 +0800 Subject: [PATCH 2/3] Update comment --- .../java/com/vincent/es/repository/StudentEsRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/vincent/es/repository/StudentEsRepository.java b/src/main/java/com/vincent/es/repository/StudentEsRepository.java index 40af134..ddba9e9 100644 --- a/src/main/java/com/vincent/es/repository/StudentEsRepository.java +++ b/src/main/java/com/vincent/es/repository/StudentEsRepository.java @@ -130,7 +130,7 @@ private V execute(IOSupplier supplier) { try { return supplier.get(); } catch (IOException e) { - // 範例為求簡便,只包裝成 RuntimeException + // 範例為求方便,只簡單做例外處理 throw new RuntimeException(e); } } From 9134c37aed33ff4246e2f62cbcef1fb7d89181e3 Mon Sep 17 00:00:00 2001 From: Vincent Date: Sun, 25 Dec 2022 15:46:42 +0800 Subject: [PATCH 3/3] Add student field. --- .../java/com/vincent/es/entity/Student.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/vincent/es/entity/Student.java b/src/main/java/com/vincent/es/entity/Student.java index 439fe2a..c1b9623 100644 --- a/src/main/java/com/vincent/es/entity/Student.java +++ b/src/main/java/com/vincent/es/entity/Student.java @@ -4,15 +4,17 @@ import java.util.List; public class Student { - private String id; // 學生編號,可當作 document id - private String name; // 姓名 - private List departments; // 科系 - private List courses; // 修習課程 - private int grade; // 年級 - private int conductScore; // 操行成績 - private Job job; // 職務 - private String introduction; // 自我介紹 - private Date englishIssuedDate; // 英文檢定通過日 + private String id; // 學生編號,可當作 document id + private String name; // 姓名 + private List departments; // 科系 + private List courses; // 修習課程 + private int grade; // 年級 + private int conductScore; // 操行成績 + private Job job; // 職務 + private String introduction; // 自我介紹 + private Date englishIssuedDate; // 英文檢定通過日 + private String bloodType; // 血型 + private List phoneNumbers; // 電話號碼 public String getId() { return id; @@ -85,4 +87,20 @@ public Date getEnglishIssuedDate() { public void setEnglishIssuedDate(Date englishIssuedDate) { this.englishIssuedDate = englishIssuedDate; } + + public String getBloodType() { + return bloodType; + } + + public void setBloodType(String bloodType) { + this.bloodType = bloodType; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } }