当前Spring Boot很是流行,包括我自己,也是在用Spring Boot集成其他框架进行项目开发,所以这一节,我们一起来探讨Spring Boot整合ElasticSearch的问题。
本文主要讲以下内容:
-
第一部分,通读文档
-
第二部分,Spring Boot整合ElasticSearch
-
第三部分,基本的CRUD操作
-
第四部分,搜索
-
第五部分,例子
还没有学过Elasticsearch的朋友,可以先学这个系列的第一节(这个系列共三节),如果你有不明白或者不正确的地方,可以给我评论、留言或者私信。
第一步,通读文档
关于repository
文档一开始就介绍 CrudRepository
,比如,继承 Repository
,其他比如 JpaRepository
、 MongoRepository
是继承 CrudRepository
。也对其中的方法做了简单说明,我们一起来看一下:
`public interface CrudRepository<T, ID extends Serializable>`
`extends Repository<T, ID> {`
`// Saves the given entity.`
`<S extends T> S save(S entity);`
`// Returns the entity identified by the given ID.`
`Optional<T> findById(ID primaryKey);`
`// Returns all entities.`
`Iterable<T> findAll();`
`// Returns the number of entities.`
`long count();`
`// Deletes the given entity.`
`void delete(T entity);`
`// Indicates whether an entity with the given ID exists.`
`boolean existsById(ID primaryKey);`
`// ... more functionality omitted.`
`}`
好了,下面我们看一下今天的主角 ElasticsearchRepository
他是怎样的吧。
这说明什么?
-
用法和JPA一样;
-
再这他除了有CRUD的基本功能之外,还有分页和排序。
清楚了这之后,是不是应该考虑该如何使用了呢?
如何用?
没错,接下来,开始说如何用,也写了很多示例代码。相对来说,还是比较简单,这里就贴一下代码就行了吧。
`interface PersonRepository extends Repository<User, Long> {`
`List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);`
`// Enables the distinct flag for the query`
`List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);`
`List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);`
`// Enabling ignoring case for an individual property`
`List<Person> findByLastnameIgnoreCase(String lastname);`
`// Enabling ignoring case for all suitable properties`
`List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);`
`// Enabling static ORDER BY for a query`
`List<Person> findByLastnameOrderByFirstnameAsc(String lastname);`
`List<Person> findByLastnameOrderByFirstnameDesc(String lastname);`
`}`
是不是这样,就可以正常使用了呢?
问题
当然可以,但是如果错了问题怎么办呢,官网写了一个常见的问题,比如包扫描问题,没有你要的方法。
`interface HumanRepository {`
`void someHumanMethod(User user);`
`}`
`class HumanRepositoryImpl implements HumanRepository {`
`public void someHumanMethod(User user) {`
`// Your custom implementation`
`}`
`}`
`interface ContactRepository {`
`void someContactMethod(User user);`
`User anotherContactMethod(User user);`
`}`
`class ContactRepositoryImpl implements ContactRepository {`
`public void someContactMethod(User user) {`
`// Your custom implementation`
`}`
`public User anotherContactMethod(User user) {`
`// Your custom implementation`
`}`
`}`
你也可以自己写接口,并且去实现它。
说完理论,作为我,应该在实际的代码中如何运用呢?
示例
官方也提供了很多示例代码,我们一起来看看。
`@Controller`
`class PersonController {`
`@Autowired PersonRepository repository;`
`@RequestMapping(value = "/persons", method = RequestMethod.GET)`
`HttpEntity<PagedResources<Person>> persons(Pageable pageable,`
`PagedResourcesAssembler assembler) {`
`Page<Person> persons = repository.findAll(pageable);`
`return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);`
`}`
`}`
这段代码相对来说还是十分经典的,我相信很多人都看到别人的代码,可能都会问,它为什么会这么用呢,答案或许就在这里吧。
当然,这是以前的代码,或许现在用不一定合适。
高级搜索
终于到高潮了!
学完我的第一节,你应该已经发现了,Elasticsearch搜索是一件十分复杂的事,为了用好它,我们不得不学好它。一起加油。
到这里,官方文档我们算是过了一遍了,大致明白了,他要告诉我们什么。其实,文档还有很多内容,可能你遇到的问题都能在里面找到答案。
最后,我们继续看一下官网写的一段处理得十分优秀的一段代码吧:
`SearchQuery searchQuery = new NativeSearchQueryBuilder()`
`.withQuery(matchAllQuery())`
`.withIndices(INDEX_NAME)`
`.withTypes(TYPE_NAME)`
`.withFields("message")`
`.withPageable(PageRequest.of(0, 10))`
`.build();`
`CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);`
`List<SampleEntity> sampleEntities = new ArrayList<>();`
`while (stream.hasNext()) {`
`sampleEntities.add(stream.next());`
`}`
第二部分,Spring Boot整合ElasticSearch
添加依赖
`implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'`
添加配置
`spring:`
`data:`
`elasticsearch:`
`cluster-nodes: localhost:9300`
`cluster-name: es-wyf`
这样就完成了整合,接下来我们用两种方式操作。
Model
我们先写一个的实体类,借助这个实体类呢来完成基础的CRUD功能。
`@Data`
`@Accessors(chain = true)`
`@Document(indexName = "blog", type = "java")`
`public class BlogModel implements Serializable {`
`private static final long serialVersionUID = 6320548148250372657L;`
`@Id`
`private String id;`
`private String title;`
`//@Field(type = FieldType.Date, format = DateFormat.basic_date)`
`@DateTimeFormat(pattern = "yyyy-MM-dd")`
`@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")`
`private Date time;`
`}`
注意id字段是必须的,可以不写注解@Id。
BlogRepository
`public interface BlogRepository extends ElasticsearchRepository<BlogModel, String> {`
`}`
第三部分,CRUD
基础操作的代码,都是在 BlogController
里面写。
`@RestController`
`@RequestMapping("/blog")`
`public class BlogController {`
`@Autowired`
`private BlogRepository blogRepository;`
`}`
添加
`@PostMapping("/add")`
`public Result add(@RequestBody BlogModel blogModel) {`
`blogRepository.save(blogModel);`
`return Result.success();`
`}`
我们添加一条数据,标题是:Elasticsearch实战篇:Spring Boot整合ElasticSearch,时间是:2019-03-06。我们来测试,看一下成不成功。
POST http://localhost:8080/blog/add
`{`
`"title":"Elasticsearch实战篇:Spring Boot整合ElasticSearch",`
`"time":"2019-05-06"`
`}`
得到响应:
`{`
`"code": 0,`
`"msg": "Success"`
`}`
嘿,成功了。那接下来,我们一下查询方法测试一下。
查询
- 根据ID查询
`@GetMapping("/get/{id}")`
`public Result getById(@PathVariable String id) {`
`if (StringUtils.isEmpty(id))`
`return Result.error();`
`Optional<BlogModel> blogModelOptional = blogRepository.findById(id);`
`if (blogModelOptional.isPresent()) {`
`BlogModel blogModel = blogModelOptional.get();`
`return Result.success(blogModel);`
`}`
`return Result.error();`
`}`
测试一下:
ok,没问题。
- 查询所有
`@GetMapping("/get")`
`public Result getAll() {`
`Iterable<BlogModel> iterable = blogRepository.findAll();`
`List<BlogModel> list = new ArrayList<>();`
`iterable.forEach(list::add);`
`return Result.success(list);`
`}`
测试一下:
GET http://localhost:8080/blog/get
结果:
`{`
`"code": 0,`
`"msg": "Success",`
`"data": [`
`{`
`"id": "fFXTTmkBTzBv3AXCweFS",`
`"title": "Elasticsearch实战篇:Spring Boot整合ElasticSearch",`
`"time": "2019-05-06"`
`}`
`]`
`}`
根据ID修改
`@PostMapping("/update")`
`public Result updateById(@RequestBody BlogModel blogModel) {`
`String id = blogModel.getId();`
`if (StringUtils.isEmpty(id))`
`return Result.error();`
`blogRepository.save(blogModel);`
`return Result.success();`
`}`
测试:
POST http://localhost:8080/blog/update
`{`
`"id":"fFXTTmkBTzBv3AXCweFS",`
`"title":"Elasticsearch入门篇",`
`"time":"2019-05-01"`
`}`
响应:
`{`
`"code": 0,`
`"msg": "Success"`
`}`
查询一下:
ok,成功!
删除
- 根据ID删除
`@DeleteMapping("/delete/{id}")`
`public Result deleteById(@PathVariable String id) {`
`if (StringUtils.isEmpty(id))`
`return Result.error();`
`blogRepository.deleteById(id);`
`return Result.success();`
`}`
测试:
DELETE http://localhost:8080/blog/delete/fFXTTmkBTzBv3AXCweFS
响应:
`{`
`"code": 0,`
`"msg": "Success"`
`}`
我们再查一下:
- 删除所有数据
`@DeleteMapping("/delete")`
`public Result deleteById() {`
`blogRepository.deleteAll();`
`return Result.success();`
`}`
分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存!
第四部分,搜索
构造数据
为了方便测试,我们先构造数据
Repository查询操作
搜索标题中的关键字
BlogRepository
`List<BlogModel> findByTitleLike(String keyword);`
BlogController
`@GetMapping("/rep/search/title")`
`public Result repSearchTitle(String keyword) {`
`if (StringUtils.isEmpty(keyword))`
`return Result.error();`
`return Result.success(blogRepository.findByTitleLike(keyword));`
`}`
我们来测试一下。
POST http://localhost:8080/blog/rep/search/title?keyword=java
结果:
`{`
`"code": 0,`
`"msg": "Success",`
`"data": [`
`{`
`"id": "f1XrTmkBTzBv3AXCeeFA",`
`"title": "java实战",`
`"time": "2018-03-01"`
`},`
`{`
`"id": "fVXrTmkBTzBv3AXCHuGH",`
`"title": "java入门",`
`"time": "2018-01-01"`
`},`
`{`
`"id": "flXrTmkBTzBv3AXCUOHj",`
`"title": "java基础",`
`"time": "2018-02-01"`
`},`
`{`
`"id": "gFXrTmkBTzBv3AXCn-Eb",`
`"title": "java web",`
`"time": "2018-04-01"`
`},`
`{`
`"id": "gVXrTmkBTzBv3AXCzuGh",`
`"title": "java ee",`
`"time": "2018-04-10"`
`}`
`]`
`}`
继续搜索:
GET http://localhost:8080/blog/rep/search/title?keyword=入门
结果:
`{`
`"code": 0,`
`"msg": "Success",`
`"data": [`
`{`
`"id": "hFXsTmkBTzBv3AXCtOE6",`
`"title": "Elasticsearch入门",`
`"time": "2019-01-20"`
`},`
`{`
`"id": "fVXrTmkBTzBv3AXCHuGH",`
`"title": "java入门",`
`"time": "2018-01-01"`
`},`
`{`
`"id": "glXsTmkBTzBv3AXCBeH_",`
`"title": "php入门",`
`"time": "2018-05-10"`
`}`
`]`
`}`
为了验证,我们再换一个关键字搜索:
GET http://localhost:8080/blog/rep/search/title?keyword=java入门
`{`
`"code": 0,`
`"msg": "Success",`
`"data": [`
`{`
`"id": "fVXrTmkBTzBv3AXCHuGH",`
`"title": "java入门",`
`"time": "2018-01-01"`
`},`
10. `{`
11. `"id": "hFXsTmkBTzBv3AXCtOE6",`
12. `"title": "Elasticsearch入门",`
13. `"time": "2019-01-20"`
14. `},`
15. `{`
16. `"id": "glXsTmkBTzBv3AXCBeH_",`
17. `"title": "php入门",`
18. `"time": "2018-05-10"`
19. `},`
20. `{`
21. `"id": "gFXrTmkBTzBv3AXCn-Eb",`
22. `"title": "java web",`
23. `"time": "2018-04-01"`
24. `},`
25. `{`
26. `"id": "gVXrTmkBTzBv3AXCzuGh",`
27. `"title": "java ee",`
28. `"time": "2018-04-10"`
29. `},`
30. `{`
31. `"id": "f1XrTmkBTzBv3AXCeeFA",`
32. `"title": "java实战",`
33. `"time": "2018-03-01"`
34. `},`
35. `{`
36. `"id": "flXrTmkBTzBv3AXCUOHj",`
37. `"title": "java基础",`
38. `"time": "2018-02-01"`
39. `}`
40. `]`
41. `}`
哈哈,有没有觉得很眼熟。
那根据上次的经验,我们正好换一种方式解决这个问题。
1. `@Query("{\"match_phrase\":{\"title\":\"?0\"}}")`
2. `List<BlogModel> findByTitleCustom(String keyword);`
值得一提的是,官方文档示例代码可能是为了好看,出现问题。
官网文档给的错误示例:
官网示例代码:
官方示例代码
另外, ?0
代指变量的意思。
1. `@GetMapping("/rep/search/title/custom")`
2. `public Result repSearchTitleCustom(String keyword) {`
3. `if (StringUtils.isEmpty(keyword))`
4. `return Result.error();`
5. `return Result.success(blogRepository.findByTitleCustom(keyword));`
6. `}`
测试一下:
ok,没有问题。
ElasticsearchTemplate
1. `@Autowired`
2. `private ElasticsearchTemplate elasticsearchTemplate;`
4. `@GetMapping("/search/title")`
5. `public Result searchTitle(String keyword) {`
6. `if (StringUtils.isEmpty(keyword))`
7. `return Result.error();`
8. `SearchQuery searchQuery = new NativeSearchQueryBuilder()`
9. `.withQuery(queryStringQuery(keyword))`
10. `.build();`
11. `List<BlogModel> list = elasticsearchTemplate.queryForList(searchQuery, BlogModel.class);`
12. `return Result.success(list);`
13. `}`
测试:
POST http://localhost:8080/blog/search/title?keyword=java入门
结果:
1. `{`
2. `"code": 0,`
3. `"msg": "Success",`
4. `"data": [`
5. `{`
6. `"id": "fVXrTmkBTzBv3AXCHuGH",`
7. `"title": "java入门",`
8. `"time": "2018-01-01"`
9. `},`
10. `{`
11. `"id": "hFXsTmkBTzBv3AXCtOE6",`
12. `"title": "Elasticsearch入门",`
13. `"time": "2019-01-20"`
14. `},`
15. `{`
16. `"id": "glXsTmkBTzBv3AXCBeH_",`
17. `"title": "php入门",`
18. `"time": "2018-05-10"`
19. `},`
20. `{`
21. `"id": "gFXrTmkBTzBv3AXCn-Eb",`
22. `"title": "java web",`
23. `"time": "2018-04-01"`
24. `},`
25. `{`
26. `"id": "gVXrTmkBTzBv3AXCzuGh",`
27. `"title": "java ee",`
28. `"time": "2018-04-10"`
29. `},`
30. `{`
31. `"id": "f1XrTmkBTzBv3AXCeeFA",`
32. `"title": "java实战",`
33. `"time": "2018-03-01"`
34. `},`
35. `{`
36. `"id": "flXrTmkBTzBv3AXCUOHj",`
37. `"title": "java基础",`
38. `"time": "2018-02-01"`
39. `}`
40. `]`
41. `}`
OK,暂时先到这里,关于搜索,我们后面会专门开一个专题,学习搜索。
分享一套SpringBoot开发博客系统源码,以及完整开发文档!速度保存!
第五部分,例子
我们写个什么例子,想了很久,那就写一个搜索手机的例子吧!
界面截图
我们先看下最后实现的效果吧
主页效果:
分页效果:
我们搜索 “小米”:
我们使用高级搜索,搜索:“小米”、“1999”:
高级搜索 “小米”、“1999” 结果:
上面的并且关系生效了吗?我们试一下搜索 “华为”,“1999”:
最后,我们尝试搜索时间段:
看一下,搜索结果吧:
说实话,这个时间搜索结果,我不是很满意,ES 的时间问题,我打算在后面花一些时间去研究下。
搭建项目
基于Gradle搭建Spring Boot项目,把我折腾的受不了(如果哪位这方面有经验,可以给我指点指点),这个demo写了很久,那天都跑的好好的,今早上起来,就跑步起来了,一气之下,就改成Maven了。
下面看一下我的依赖和配置
pom.xml 片段
1. `<parent>`
2. `<groupId>org.springframework.boot</groupId>`
3. `<artifactId>spring-boot-starter-parent</artifactId>`
4. `<version>2.1.3.RELEASE</version>`
5. `<relativePath/> <!-- lookup parent from repository -->`
6. `</parent>`
8. `<repositories>`
9. `<repository>`
10. `<id>jitpack.io</id>`
11. `<url>https://jitpack.io</url>`
12. `</repository>`
13. `</repositories>`
15. `<dependencies>`
16. `<dependency>`
17. `<groupId>org.springframework.boot</groupId>`
18. `<artifactId>spring-boot-starter-data-elasticsearch</artifactId>`
19. `</dependency>`
20. `<dependency>`
21. `<groupId>org.springframework.boot</groupId>`
22. `<artifactId>spring-boot-starter-web</artifactId>`
23. `</dependency>`
25. `<dependency>`
26. `<groupId>org.projectlombok</groupId>`
27. `<artifactId>lombok</artifactId>`
28. `<optional>true</optional>`
29. `</dependency>`
30. `<dependency>`
31. `<groupId>org.springframework.boot</groupId>`
32. `<artifactId>spring-boot-starter-test</artifactId>`
33. `<scope>test</scope>`
34. `</dependency>`
36. `<!--`
37. `添加 JavaLib 支持`
38. `用于接口返回`
39. `-->`
40. `<dependency>`
41. `<groupId>com.github.fengwenyi</groupId>`
42. `<artifactId>JavaLib</artifactId>`
43. `<version>1.0.7.RELEASE</version>`
44. `</dependency>`
46. `<!--`
47. `添加 webflux 支持`
48. `用于编写非阻塞接口`
49. `-->`
50. `<dependency>`
51. `<groupId>org.springframework.boot</groupId>`
52. `<artifactId>spring-boot-starter-webflux</artifactId>`
53. `</dependency>`
55. `<!--`
56. `添加 fastjson 的支持`
57. `用于处理JSON格式数据`
58. `-->`
59. `<dependency>`
60. `<groupId>com.alibaba</groupId>`
61. `<artifactId>fastjson</artifactId>`
62. `<version>1.2.56</version>`
63. `</dependency>`
65. `<!--`
66. `添加 Httpclient 的支持`
67. `用于网络请求`
68. `-->`
69. `<dependency>`
70. `<groupId>org.apache.httpcomponents</groupId>`
71. `<artifactId>httpclient</artifactId>`
72. `<version>4.5.7</version>`
73. `</dependency>`
75. `<!--`
76. `添加 jsoup 的支持`
77. `用于解析网页内容`
78. `-->`
79. `<dependency>`
80. `<groupId>org.jsoup</groupId>`
81. `<artifactId>jsoup</artifactId>`
82. `<version>1.10.2</version>`
83. `</dependency>`
84. `</dependencies>`
application.yml
1. `server:`
2. `port: 9090`
4. `spring:`
5. `data:`
6. `elasticsearch:`
7. `cluster-nodes: localhost:9300`
8. `cluster-name: es-wyf`
9. `repositories:`
10. `enabled: true`
PhoneModel
1. `@Data`
2. `@Accessors(chain = true)`
3. `@Document(indexName = "springboot_elasticsearch_example_phone", type = "com.fengwenyi.springbootelasticsearchexamplephone.model.PhoneModel")`
4. `public class PhoneModel implements Serializable {`
5. `private static final long serialVersionUID = -5087658155687251393L;`
7. `/* ID */`
8. `@Id`
9. `private String id;`
11. `/* 名称 */`
12. `private String name;`
14. `/* 颜色,用英文分号(;)分隔 */`
15. `private String colors;`
17. `/* 卖点,用英文分号(;)分隔 */`
18. `private String sellingPoints;`
20. `/* 价格 */`
21. `private String price;`
23. `/* 产量 */`
24. `private Long yield;`
26. `/* 销售量 */`
27. `private Long sale;`
29. `/* 上市时间 */`
30. `//@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
31. `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")`
32. `private Date marketTime;`
34. `/* 数据抓取时间 */`
35. `//@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
36. `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")`
37. `private Date createTime;`
39. `}`
PhoneRepository
1. `public interface PhoneRepository extends ElasticsearchRepository<PhoneModel, String> {`
2. `}`
PhoneController
1. `@RestController`
2. `@RequestMapping(value = "/phone")`
3. `@CrossOrigin`
4. `public class PhoneController {`
6. `@Autowired`
7. `private ElasticsearchTemplate elasticsearchTemplate;`
9. `}`
后面接口,都会在这里写。
构造数据
我的数据是抓的 “华为” 和 “小米” 官网
首先使用 httpclient
下载html,然后使用 jsoup
进行解析。
以 华为 为例:
1. `private void huawei() throws IOException {`
2. `CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建httpclient实例`
3. `HttpGet httpget = new HttpGet("https://consumer.huawei.com/cn/phones/?ic_medium=hwdc&ic_source=corp_header_consumer"); // 创建httpget实例`
5. `CloseableHttpResponse response = httpclient.execute(httpget); // 执行get请求`
6. `HttpEntity entity=response.getEntity(); // 获取返回实体`
7. `//System.out.println("网页内容:"+ EntityUtils.toString(entity, "utf-8")); // 指定编码打印网页内容`
8. `String content = EntityUtils.toString(entity, "utf-8");`
9. `response.close(); // 关闭流和释放系统资源`
11. `// System.out.println(content);`
13. `Document document = Jsoup.parse(content);`
14. `Elements elements = document.select("#content-v3-plp #pagehidedata .plphidedata");`
15. `for (Element element : elements) {`
16. `// System.out.println(element.text());`
17. `String jsonStr = element.text();`
18. `List<HuaWeiPhoneBean> list = JSON.parseArray(jsonStr, HuaWeiPhoneBean.class);`
19. `for (HuaWeiPhoneBean bean : list) {`
20. `String productName = bean.getProductName();`
21. `List<ColorModeBean> colorsItemModeList = bean.getColorsItemMode();`
23. `StringBuilder colors = new StringBuilder();`
24. `for (ColorModeBean colorModeBean : colorsItemModeList) {`
25. `String colorName = colorModeBean.getColorName();`
26. `colors.append(colorName).append(";");`
27. `}`
29. `List<String> sellingPointList = bean.getSellingPoints();`
30. `StringBuilder sellingPoints = new StringBuilder();`
31. `for (String sellingPoint : sellingPointList) {`
32. `sellingPoints.append(sellingPoint).append(";");`
33. `}`
35. `// System.out.println("产品名:" + productName);`
36. `// System.out.println("颜 色:" + color);`
37. `// System.out.println("买 点:" + sellingPoint);`
38. `// System.out.println("-----------------------------------");`
39. `PhoneModel phoneModel = new PhoneModel()`
40. `.setName(productName)`
41. `.setColors(colors.substring(0, colors.length() - 1))`
42. `.setSellingPoints(sellingPoints.substring(0, sellingPoints.length() - 1))`
43. `.setCreateTime(new Date());`
44. `phoneRepository.save(phoneModel);`
45. `}`
46. `}`
47. `}`
全文搜索
全文搜索来说,还是相对来说,比较简单,直接贴代码吧:
1. `/**`
2. `* 全文搜索`
3. `* @param keyword 关键字`
4. `* @param page 当前页,从0开始`
5. `* @param size 每页大小`
6. `* @return {@link Result} 接收到的数据格式为json`
7. `*/`
8. `@GetMapping("/full")`
9. `public Mono<Result> full(String keyword, int page, int size) {`
10. `// System.out.println(new Date() + " => " + keyword);`
12. `// 校验参数`
13. `if (StringUtils.isEmpty(page))`
14. `page = 0; // if page is null, page = 0`
16. `if (StringUtils.isEmpty(size))`
17. `size = 10; // if size is null, size default 10`
19. `// 构造分页类`
20. `Pageable pageable = PageRequest.of(page, size);`
22. `// 构造查询 NativeSearchQueryBuilder`
23. `NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()`
24. `.withPageable(pageable)`
25. `;`
26. `if (!StringUtils.isEmpty(keyword)) {`
27. `// keyword must not null`
28. `searchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keyword));`
29. `}`
31. `/*`
32. `SearchQuery`
33. `这个很关键,这是搜索条件的入口,`
34. `elasticsearchTemplate 会 使用它 进行搜索`
35. `*/`
36. `SearchQuery searchQuery = searchQueryBuilder.build();`
38. `// page search`
39. `Page<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);`
41. `// return`
42. `return Mono.just(Result.success(phoneModelPage));`
43. `}`
官网文档也是这么用的,所以相对来说,这还是很简单的,不过拆词 和 搜索策略 搜索速度 可能在实际使用中要考虑。
高级搜索
先看代码,后面我们再来分析:
1. `/**`
2. `* 高级搜索,根据字段进行搜索`
3. `* @param name 名称`
4. `* @param color 颜色`
5. `* @param sellingPoint 卖点`
6. `* @param price 价格`
7. `* @param start 开始时间(格式:yyyy-MM-dd HH:mm:ss)`
8. `* @param end 结束时间(格式:yyyy-MM-dd HH:mm:ss)`
9. `* @param page 当前页,从0开始`
10. `* @param size 每页大小`
11. `* @return {@link Result}`
12. `*/`
13. `@GetMapping("/_search")`
14. `public Mono<Result> search(String name, String color, String sellingPoint, String price, String start, String end, int page, int size) {`
16. `// 校验参数`
17. `if (StringUtils.isEmpty(page) || page < 0)`
18. `page = 0; // if page is null, page = 0`
20. `if (StringUtils.isEmpty(size) || size < 0)`
21. `size = 10; // if size is null, size default 10`
23. `// 构造分页对象`
24. `Pageable pageable = PageRequest.of(page, size);`
26. `// BoolQueryBuilder (Elasticsearch Query)`
27. `BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();`
28. `if (!StringUtils.isEmpty(name)) {`
29. `boolQueryBuilder.must(QueryBuilders.matchQuery("name", name));`
30. `}`
32. `if (!StringUtils.isEmpty(color)) {`
33. `boolQueryBuilder.must(QueryBuilders.matchQuery("colors", color));`
34. `}`
36. `if (!StringUtils.isEmpty(color)) {`
37. `boolQueryBuilder.must(QueryBuilders.matchQuery("sellingPoints", sellingPoint));`
38. `}`
40. `if (!StringUtils.isEmpty(price)) {`
41. `boolQueryBuilder.must(QueryBuilders.matchQuery("price", price));`
42. `}`
44. `if (!StringUtils.isEmpty(start)) {`
45. `Date startTime = null;`
46. `try {`
47. `startTime = DateTimeUtil.stringToDate(start, DateTimeFormat.yyyy_MM_dd_HH_mm_ss);`
48. `} catch (ParseException e) {`
49. `e.printStackTrace();`
50. `}`
51. `boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime").gt(startTime.getTime()));`
52. `}`
54. `if (!StringUtils.isEmpty(end)) {`
55. `Date endTime = null;`
56. `try {`
57. `endTime = DateTimeUtil.stringToDate(end, DateTimeFormat.yyyy_MM_dd_HH_mm_ss);`
58. `} catch (ParseException e) {`
59. `e.printStackTrace();`
60. `}`
61. `boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime").lt(endTime.getTime()));`
62. `}`
64. `// BoolQueryBuilder (Spring Query)`
65. `SearchQuery searchQuery = new NativeSearchQueryBuilder()`
66. `.withPageable(pageable)`
67. `.withQuery(boolQueryBuilder)`
68. `.build()`
69. `;`
71. `// page search`
72. `Page<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);`
74. `// return`
75. `return Mono.just(Result.success(phoneModelPage));`
76. `}`
不管spring如何封装,查询方式都一样,如下图:
好吧,我们怀着这样的心态去看下源码。
1. `org.springframework.data.elasticsearch.core.query.SearchQuery`
这个是我们搜索需要用到对象
1. `public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {`
2. `this.queryBuilder = queryBuilder;`
3. `return this;`
4. `}`
OK,根据源码,我们需要构造这个 QueryBuilder,那么问题来了,这个是个什么东西,我们要如何构造,继续看:
1. `org.elasticsearch.index.query.QueryBuilder`
注意包名。
啥,怎么又跑到 elasticsearch。
你想啊,你写的东西,会让别人直接操作吗?
答案是不会的,我们只会提供API,所有,不管Spring如何封装,也只会通过API去调用。
好吧,今天先到这里,下一个专题,我们再讨论关于搜索问题。