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..c1b9623
--- /dev/null
+++ b/src/main/java/com/vincent/es/entity/Student.java
@@ -0,0 +1,106 @@
+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; // 英文檢定通過日
+ private String bloodType; // 血型
+ private List phoneNumbers; // 電話號碼
+
+ 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;
+ }
+
+ 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;
+ }
+}
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..ddba9e9
--- /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) {
+ // 範例為求方便,只簡單做例外處理
+ 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;
+}