diff --git a/README.md b/README.md
index 394031e..5038ff4 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,9 @@ Dubbo 服务提供者工程和 Dubbo 服务消费者工程
#### 『 Spring Data ES 篇 』
- spring-data-elasticsearch-crud
-Spring Data Elasticsearch - 基本案例
+ [《Spring Data Elasticsearch - 基本案例》](http://spring4all.com/article/70 "Spring Data Elasticsearch - 基本案例")
+- spring-data-elasticsearch-query
+spring-data-elasticsearch - 实战案例详解
## 二、项目 Quick Start 快速开发指南
#### a. 基本环境配置
diff --git a/pom.xml b/pom.xml
index 9c9becf..cce40de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@
spring-data-elasticsearch-crud
+ spring-data-elasticsearch-query
diff --git a/spring-data-elasticsearch-query/pom.xml b/spring-data-elasticsearch-query/pom.xml
new file mode 100755
index 0000000..60235d9
--- /dev/null
+++ b/spring-data-elasticsearch-query/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ springboot
+ spring-data-elasticsearch-query
+ 0.0.1-SNAPSHOT
+ spring-data-elasticsearch-query :: spring-data-elasticsearch - 实战案例详解
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.1.RELEASE
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-elasticsearch
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ junit
+ junit
+ 4.12
+
+
+
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/Application.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/Application.java
new file mode 100644
index 0000000..3fe97d3
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/Application.java
@@ -0,0 +1,20 @@
+package org.spring.springboot;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * Spring Boot 应用启动类
+ *
+ * Created by bysocket on 20/06/2017.
+ */
+// Spring Boot 应用的标识
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ // 程序启动入口
+ // 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
+ SpringApplication.run(Application.class,args);
+ }
+}
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/controller/CityRestController.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/controller/CityRestController.java
new file mode 100644
index 0000000..2df1a96
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/controller/CityRestController.java
@@ -0,0 +1,47 @@
+package org.spring.springboot.controller;
+
+import org.spring.springboot.domain.City;
+import org.spring.springboot.service.CityService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 城市 Controller 实现 Restful HTTP 服务
+ *
+ * Created by bysocket on 20/06/2017.
+ */
+@RestController
+public class CityRestController {
+
+ @Autowired
+ private CityService cityService;
+
+ /**
+ * 插入 ES 新城市
+ *
+ * @param city
+ * @return
+ */
+ @RequestMapping(value = "/api/city", method = RequestMethod.POST)
+ public Long createCity(@RequestBody City city) {
+ return cityService.saveCity(city);
+ }
+
+ /**
+ * 搜索返回分页结果
+ *
+ * @param pageNumber 当前页码
+ * @param pageSize 每页大小
+ * @param searchContent 搜索内容
+ * @return
+ */
+ @RequestMapping(value = "/api/city/search", method = RequestMethod.GET)
+ public List searchCity(@RequestParam(value = "pageNumber") Integer pageNumber,
+ @RequestParam(value = "pageSize", required = false) Integer pageSize,
+ @RequestParam(value = "searchContent") String searchContent) {
+ return cityService.searchCity(pageNumber, pageSize,searchContent);
+ }
+}
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/domain/City.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/domain/City.java
new file mode 100644
index 0000000..5905f24
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/domain/City.java
@@ -0,0 +1,68 @@
+package org.spring.springboot.domain;
+
+import org.springframework.data.elasticsearch.annotations.Document;
+
+import java.io.Serializable;
+
+/**
+ * 城市实体类
+ *
+ * Created by bysocket on 20/06/2017.
+ */
+@Document(indexName = "province", type = "city")
+public class City implements Serializable {
+
+ private static final long serialVersionUID = -1L;
+
+ /**
+ * 城市编号
+ */
+ private Long id;
+
+ /**
+ * 城市名称
+ */
+ private String name;
+
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 城市评分
+ */
+ private Integer score;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Integer getScore() {
+ return score;
+ }
+
+ public void setScore(Integer score) {
+ this.score = score;
+ }
+}
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/repository/CityRepository.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/repository/CityRepository.java
new file mode 100644
index 0000000..c51c729
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/repository/CityRepository.java
@@ -0,0 +1,66 @@
+package org.spring.springboot.repository;
+
+import org.spring.springboot.domain.City;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.elasticsearch.annotations.Query;
+import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
+
+import java.util.List;
+
+/**
+ * ES 操作类
+ *
+ * Created by bysocket on 20/06/2017.
+ */
+public interface CityRepository extends ElasticsearchRepository {
+ /**
+ * AND 语句查询
+ *
+ * @param description
+ * @param score
+ * @return
+ */
+ List findByDescriptionAndScore(String description, Integer score);
+
+ /**
+ * OR 语句查询
+ *
+ * @param description
+ * @param score
+ * @return
+ */
+ List findByDescriptionOrScore(String description, Integer score);
+
+ /**
+ * 查询城市描述
+ *
+ * 等同于下面代码
+ * @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
+ * Page findByDescription(String description, Pageable pageable);
+ *
+ * @param description
+ * @param page
+ * @return
+ */
+ Page findByDescription(String description, Pageable page);
+
+ /**
+ * NOT 语句查询
+ *
+ * @param description
+ * @param page
+ * @return
+ */
+ Page findByDescriptionNot(String description, Pageable page);
+
+ /**
+ * LIKE 语句查询
+ *
+ * @param description
+ * @param page
+ * @return
+ */
+ Page findByDescriptionLike(String description, Pageable page);
+
+}
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/CityService.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/CityService.java
new file mode 100644
index 0000000..c6cd894
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/CityService.java
@@ -0,0 +1,31 @@
+
+package org.spring.springboot.service;
+
+import org.spring.springboot.domain.City;
+
+import java.util.List;
+
+/**
+ * 城市 ES 业务接口类
+ *
+ */
+public interface CityService {
+
+ /**
+ * 新增 ES 城市信息
+ *
+ * @param city
+ * @return
+ */
+ Long saveCity(City city);
+
+ /**
+ * 搜索词搜索,分页返回城市信息
+ *
+ * @param pageNumber 当前页码
+ * @param pageSize 每页大小
+ * @param searchContent 搜索内容
+ * @return
+ */
+ List searchCity(Integer pageNumber, Integer pageSize, String searchContent);
+}
\ No newline at end of file
diff --git a/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/impl/CityESServiceImpl.java b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/impl/CityESServiceImpl.java
new file mode 100644
index 0000000..4c5b22b
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/java/org/spring/springboot/service/impl/CityESServiceImpl.java
@@ -0,0 +1,105 @@
+package org.spring.springboot.service.impl;
+
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
+import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.spring.springboot.domain.City;
+import org.spring.springboot.repository.CityRepository;
+import org.spring.springboot.service.CityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
+import org.springframework.data.elasticsearch.core.query.SearchQuery;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 城市 ES 业务逻辑实现类
+ *
+ * Created by bysocket on 20/06/2017.
+ */
+@Service
+public class CityESServiceImpl implements CityService {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
+
+ /* 分页参数 */
+ Integer PAGE_SIZE = 12; // 每页数量
+ Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
+
+ /* 搜索模式 */
+ String SCORE_MODE_SUM = "sum"; // 权重分求和模式
+ Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
+
+ @Autowired
+ CityRepository cityRepository;// ES 操作类
+
+ public Long saveCity(City city) {
+ City cityResult = cityRepository.save(city);
+ return cityResult.getId();
+ }
+
+ @Override
+ public List searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
+
+ // 校验分页参数
+ if (pageSize == null || pageSize <= 0) {
+ pageSize = PAGE_SIZE;
+ }
+
+ if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
+ pageNumber = DEFAULT_PAGE_NUMBER;
+ }
+
+ LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
+
+ // 构建搜索查询
+ SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
+
+ LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
+
+ Page cityPage = cityRepository.search(searchQuery);
+ return cityPage.getContent();
+ }
+
+ /**
+ * 根据搜索词构造搜索查询语句
+ *
+ * 代码流程:
+ * - 权重分查询
+ * - 短语匹配
+ * - 设置权重分最小值
+ * - 设置分页参数
+ *
+ * @param pageNumber 当前页码
+ * @param pageSize 每页大小
+ * @param searchContent 搜索内容
+ * @return
+ */
+ private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
+ // 短语匹配到的搜索词,求和模式累加权重分
+ // 权重分查询 https://www.elastic.co/guide/cn/elasticsearch/guide/current/function-score-query.html
+ // - 短语匹配 https://www.elastic.co/guide/cn/elasticsearch/guide/current/phrase-matching.html
+ // - 字段对应权重分设置,可以优化成 enum
+ // - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
+ FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
+ .add(QueryBuilders.matchPhraseQuery("name", searchContent),
+ ScoreFunctionBuilders.weightFactorFunction(1000))
+ .add(QueryBuilders.matchPhraseQuery("description", searchContent),
+ ScoreFunctionBuilders.weightFactorFunction(500))
+ .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
+
+ // 分页参数
+ Pageable pageable = new PageRequest(pageNumber, pageSize);
+ return new NativeSearchQueryBuilder()
+ .withPageable(pageable)
+ .withQuery(functionScoreQueryBuilder).build();
+ }
+
+
+}
diff --git a/spring-data-elasticsearch-query/src/main/resources/application.properties b/spring-data-elasticsearch-query/src/main/resources/application.properties
new file mode 100644
index 0000000..4f1ed6d
--- /dev/null
+++ b/spring-data-elasticsearch-query/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+# ES
+spring.data.elasticsearch.repositories.enabled = true
+spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
\ No newline at end of file