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; +}