org.projectlombok
@@ -161,6 +164,7 @@ spring boot demo 是一个用来学习 spring boot 的项目,已经集成 actu
| [spring-boot-demo-swagger](./spring-boot-demo-swagger) | spring-boot 集成 [spring-boot-starter-swagger](https://github.com/SpringForAll/spring-boot-starter-swagger) (由大佬[翟永超](http://blog.didispace.com/)开源)用于统一管理、测试 API 接口 |
| [spring-boot-demo-ureport2](./spring-boot-demo-ureport2) | spring-boot 集成 [ureport2](https://github.com/youseries/ureport) 实现自定义报表(ureport2可以轻松实现复杂的中国式报表,功能十分强大) |
| [spring-boot-demo-war](./spring-boot-demo-war) | spring-boot 打成 war 包的配置 |
+| [spring-boot-demo-elasticsearch](./spring-boot-demo-elasticsearch) | spring-boot 集成 ElasticSearch(采用原生操作 ES 的方式) |
# 官方提供的 starter 介绍
diff --git a/TODO.md b/TODO.md
index e946b36..f3e307a 100644
--- a/TODO.md
+++ b/TODO.md
@@ -39,6 +39,7 @@
- [ ] spring-boot-demo-async(Spring boot 实现异步调用)
- [ ] spring-boot-demo-dubbo(集成 dubbo)
- [x] ~~spring-boot-demo-war(打包成war包)~~
+- [x] ~~spring-boot-demo-elasticsearch(集成 ElasticSearch,使用原生操作 ES 的方式)~~
### 备注
diff --git a/spring-boot-demo-elasticsearch/.gitignore b/spring-boot-demo-elasticsearch/.gitignore
new file mode 100644
index 0000000..2af7cef
--- /dev/null
+++ b/spring-boot-demo-elasticsearch/.gitignore
@@ -0,0 +1,24 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
\ No newline at end of file
diff --git a/spring-boot-demo-elasticsearch/README.md b/spring-boot-demo-elasticsearch/README.md
new file mode 100644
index 0000000..dac0f2d
--- /dev/null
+++ b/spring-boot-demo-elasticsearch/README.md
@@ -0,0 +1,280 @@
+# spring-boot-demo-elasticsearch
+
+依赖 [spring-boot-demo-parent](../spring-boot-demo-parent)
+
+ElasticSearch 的 demo 我这里没有使用 spring-data-elasticsearch,我使用的是原生的方式
+
+操作 ElasticSearch 由很多种方式:
+
+1. ES 官方提供的原生方式,**本例子使用这种方式**,这种方式的好处是高度自定义,并且可以使用最新的 ES 版本,缺点就是所有操作都得自己写。
+2. 使用 Spring 官方提供的 spring-data-elasticsearch,这里给出地址 https://projects.spring.io/spring-data-elasticsearch/ ,采用的方式类似 JPA,并且为 SpringBoot 提供了一个 `spring-boot-starter-data-elasticsearch`,优点是操作 ES 的方式采用了 JPA 的方式,都已经封装好了,缺点是版本得随着官方慢慢迭代,不能使用 ES 的最新特性。
+
+### pom.xml
+
+```xml
+
+
+ 4.0.0
+
+ spring-boot-demo-elasticsearch
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-demo-elasticsearch
+ Demo project for Spring Boot
+
+
+ com.xkcoding
+ spring-boot-demo-parent
+ 0.0.1-SNAPSHOT
+ ../spring-boot-demo-parent/pom.xml
+
+
+
+
+ 6.1.1
+
+
+
+
+
+ org.elasticsearch.client
+ transport
+ ${elasticsearch.version}
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.7
+
+
+
+
+ spring-boot-demo-elasticsearch
+
+
+
+```
+
+### application.yml
+
+```yaml
+server:
+ port: 8080
+ context-path: /demo
+elasticsearch:
+ host: 127.0.0.1
+ port: 9300
+ cluster:
+ name: xkcoding
+```
+
+ElasticSearchConfig.java
+
+```java
+/**
+ *
+ * ES 的配置类
+ *
+ *
+ * @package: com.xkcoding.springbootdemoelasticsearch.config
+ * @description: ES 的配置类
+ * @author: yangkai.shen
+ * @date: Created in 2018/1/18 下午4:41
+ * @copyright: Copyright (c) 2018
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Configuration
+public class ElasticSearchConfig {
+ @Value("${elasticsearch.host}")
+ private String host;
+
+ @Value("${elasticsearch.port}")
+ private int port;
+
+ @Value("${elasticsearch.cluster.name}")
+ private String clusterName;
+
+ @Bean
+ public TransportClient esClient() throws UnknownHostException {
+ Settings settings = Settings.builder().put("cluster.name", this.clusterName).put("client.transport.sniff", true).build();
+
+ TransportAddress master = new TransportAddress(InetAddress.getByName(host), port);
+ TransportClient client = new PreBuiltTransportClient(settings).addTransportAddress(master);
+ return client;
+ }
+}
+```
+
+PersonController.java
+
+```java
+/**
+ *
+ * Person Controller
+ *
+ *
+ * @package: com.xkcoding.springbootdemoelasticsearch.web.controller
+ * @description: Person Controller
+ * @author: yangkai.shen
+ * @date: Created in 2018/1/18 下午5:06
+ * @copyright: Copyright (c) 2018
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@RestController
+@Slf4j
+public class PersonController {
+ public static final String INDEX = "people";
+ public static final String TYPE = "person";
+
+ @Autowired
+ private TransportClient esClient;
+
+ /**
+ * 插入一条数据到 ES 中,id 由 ES 生成
+ *
+ * @param name 名称
+ * @param country 国籍
+ * @param age 年龄
+ * @param birthday 生日
+ * @return 插入数据的主键
+ */
+ @PostMapping("/person")
+ public ApiResponse add(@RequestParam String name, @RequestParam String country, @RequestParam Integer age, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday) {
+ try {
+ XContentBuilder content = XContentFactory.jsonBuilder().startObject().field("name", name).field("country", country).field("age", age).field("birthday", birthday.getTime()).endObject();
+
+ IndexResponse response = esClient.prepareIndex(INDEX, TYPE).setSource(content).get();
+ return ApiResponse.ofSuccess(response.getId());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return ApiResponse.ofStatus(Status.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * 根据 id 删除 ES 的一条记录
+ *
+ * @param id ES 中的 id
+ * @return DELETED 代表删除
+ */
+ @DeleteMapping("/person/{id}")
+ public ApiResponse delete(@PathVariable String id) {
+ DeleteResponse response = esClient.prepareDelete(INDEX, TYPE, id).get();
+ return ApiResponse.ofSuccess(response.getResult());
+ }
+
+ /**
+ * 根据主键,修改传递字段对应的值
+ *
+ * @param id ES 中的 id
+ * @param name 姓名
+ * @param country 国籍
+ * @param age 年龄
+ * @param birthday 生日
+ * @return UPDATED 代表文档修改成功
+ */
+ @PutMapping("/person/{id}")
+ public ApiResponse update(@PathVariable String id, @RequestParam(value = "name", required = false) String name, @RequestParam(value = "country", required = false) String country, @RequestParam(value = "age", required = false) Integer age, @RequestParam(value = "birthday", required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birthday) {
+ UpdateRequest request = new UpdateRequest(INDEX, TYPE, id);
+ try {
+ XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
+ if (!Strings.isNullOrEmpty(name)) {
+ builder.field("name", name);
+ }
+ if (!Strings.isNullOrEmpty(country)) {
+ builder.field("country", country);
+ }
+ if (age != null && age > 0) {
+ builder.field("age", age);
+ }
+ if (birthday != null) {
+ builder.field("birthday", birthday.getTime());
+ }
+ builder.endObject();
+ request.doc(builder);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return ApiResponse.ofStatus(Status.INTERNAL_SERVER_ERROR);
+ }
+ try {
+ UpdateResponse response = esClient.update(request).get();
+ return ApiResponse.ofSuccess(response);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return ApiResponse.ofStatus(Status.INTERNAL_SERVER_ERROR);
+ }
+ }
+
+ /**
+ * 简单查询 根据 id 查 ES 中的文档内容
+ *
+ * @param id ES 中存储的 id
+ * @return 对应 id 的文档内容
+ */
+ @GetMapping("/person/{id}")
+ public ApiResponse get(@PathVariable String id) {
+ GetResponse response = esClient.prepareGet(INDEX, TYPE, id).get();
+ if (!response.isExists() || response.isSourceEmpty()) {
+ return ApiResponse.ofStatus(Status.NOT_FOUND);
+ }
+ return ApiResponse.ofSuccess(response.getSource());
+ }
+
+ /**
+ * 复合查询,根据传进来的条件,查询具体内容
+ *
+ * @param name 根据姓名匹配
+ * @param country 根据国籍匹配
+ * @param gtAge 大于年龄
+ * @param ltAge 小于年龄
+ * @return 满足条件的文档内容
+ */
+ @PostMapping("/person/query")
+ public ApiResponse query(@RequestParam(value = "name", required = false) String name,
+ @RequestParam(value = "country", required = false) String country,
+ @RequestParam(value = "gt_age", defaultValue = "0") int gtAge,
+ @RequestParam(value = "lt_age", required = false) Integer ltAge) {
+ BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
+
+ if (!Strings.isNullOrEmpty(name)) {
+ boolQueryBuilder.must(QueryBuilders.matchQuery("name", name));
+ }
+
+ if (!Strings.isNullOrEmpty(country)) {
+ boolQueryBuilder.must(QueryBuilders.matchQuery("country", country));
+ }
+
+ RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").from(gtAge);
+
+ if (ltAge != null && ltAge > 0) {
+ rangeQueryBuilder.to(ltAge);
+ }
+
+ boolQueryBuilder.filter(rangeQueryBuilder);
+
+ SearchRequestBuilder searchRequestBuilder = esClient.prepareSearch(INDEX)
+ .setTypes(TYPE)
+ .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
+ .setQuery(boolQueryBuilder)
+ .setFrom(0)
+ .setSize(20);
+
+ log.info("【query】:{}", searchRequestBuilder);
+
+ SearchResponse searchResponse = searchRequestBuilder.get();
+ List