Elasticsearch实战篇——Spring Boot整合ElasticSearch

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

当前Spring Boot很是流行,包括我自己,也是在用Spring Boot集成其他框架进行项目开发,所以这一节,我们一起来探讨Spring Boot整合ElasticSearch的问题。

本文主要讲以下内容:

第一部分,通读文档

第二部分,Spring Boot整合ElasticSearch

第三部分,基本的CRUD操作

第四部分,搜索

第五部分,例子

还没有学过Elasticsearch的朋友,可以先学这个系列的第一节(这个系列共三节),如果你有不明白或者不正确的地方,可以给我评论、留言或者私信。

第一步,通读文档

Spring Data Elasticsearch 官方文档,这是当前最新的文档。

关于repository

文档一开始就介绍 CrudRepository ,比如,继承 Repository,其他比如 JpaRepositoryMongoRepository是继承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 他是怎样的吧。

ElasticsearchRepository继承图

这说明什么?

  • 用法和JPA一样;

  • 再这他除了有CRUD的基本功能之外,还有分页和排序。

清楚了这之后,是不是应该考虑该如何使用了呢?

如何用?

没错,接下来,开始说如何用,也写了很多示例代码。相对来说,还是比较简单,这里就贴一下代码就行了吧。

interface PersonRepository extends Repository<User, Long> {List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);// Enables the distinct flag for the queryList<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);// Enabling ignoring case for an individual propertyList<Person> findByLastnameIgnoreCase(String lastname);// Enabling ignoring case for all suitable propertiesList<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);// Enabling static ORDER BY for a queryList<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:9300cluster-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;@Idprivate 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 {@Autowiredprivate 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();
}

测试一下:

测试根据ID查询

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();
}

第四部分,搜索

构造数据

为了方便测试,我们先构造数据

构造查询数据

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"},{"id": "hFXsTmkBTzBv3AXCtOE6","title": "Elasticsearch入门","time": "2019-01-20"},{"id": "glXsTmkBTzBv3AXCBeH_","title": "php入门","time": "2018-05-10"},{"id": "gFXrTmkBTzBv3AXCn-Eb","title": "java web","time": "2018-04-01"},{"id": "gVXrTmkBTzBv3AXCzuGh","title": "java ee","time": "2018-04-10"},{"id": "f1XrTmkBTzBv3AXCeeFA","title": "java实战","time": "2018-03-01"},{"id": "flXrTmkBTzBv3AXCUOHj","title": "java基础","time": "2018-02-01"}]
}

哈哈,有没有觉得很眼熟。

那根据上次的经验,我们正好换一种方式解决这个问题。

@Query("{\"match_phrase\":{\"title\":\"?0\"}}")
List<BlogModel> findByTitleCustom(String keyword);

值得一提的是,官方文档示例代码可能是为了好看,出现问题。

官网文档给的错误示例:

官网文档错误

官网示例代码:

官方示例代码

官方示例代码

另外,?0 代指变量的意思。

@GetMapping("/rep/search/title/custom")
public Result repSearchTitleCustom(String keyword) {if (StringUtils.isEmpty(keyword))return Result.error();return Result.success(blogRepository.findByTitleCustom(keyword));
}

测试一下:

测试成功示例

ok,没有问题。

ElasticsearchTemplate

@Autowired
private ElasticsearchTemplate elasticsearchTemplate;@GetMapping("/search/title")
public Result searchTitle(String keyword) {if (StringUtils.isEmpty(keyword))return Result.error();SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryStringQuery(keyword)).build();List<BlogModel> list = elasticsearchTemplate.queryForList(searchQuery, BlogModel.class);return Result.success(list);
}

测试:

POST http://localhost:8080/blog/search/title?keyword=java入门

结果:

{"code": 0,"msg": "Success","data": [{"id": "fVXrTmkBTzBv3AXCHuGH","title": "java入门","time": "2018-01-01"},{"id": "hFXsTmkBTzBv3AXCtOE6","title": "Elasticsearch入门","time": "2019-01-20"},{"id": "glXsTmkBTzBv3AXCBeH_","title": "php入门","time": "2018-05-10"},{"id": "gFXrTmkBTzBv3AXCn-Eb","title": "java web","time": "2018-04-01"},{"id": "gVXrTmkBTzBv3AXCzuGh","title": "java ee","time": "2018-04-10"},{"id": "f1XrTmkBTzBv3AXCeeFA","title": "java实战","time": "2018-03-01"},{"id": "flXrTmkBTzBv3AXCUOHj","title": "java基础","time": "2018-02-01"}]
}

OK,暂时先到这里,关于搜索,我们后面会专门开一个专题,学习搜索。

第五部分,例子

我们写个什么例子,想了很久,那就写一个搜索手机的例子吧!

界面截图

我们先看下最后实现的效果吧

主页效果:

主页效果

分页效果:

分页效果

我们搜索 “小米”:

全文搜索 - “小米”

我们搜索 “1999”:

全文搜索 - “1999”

我们搜索 “黑色”:

全文搜索 - “黑色”

高级搜索页面:

高级搜索 - "主页面"

我们使用高级搜索,搜索:“小米”、“1999”:

高级搜索 - “小米 1999”

高级搜索 “小米”、“1999” 结果:

高级搜索 - “小米 1999” - 结果

上面的并且关系生效了吗?我们试一下搜索 “华为”,“1999”:

高级搜索 - “华为 1999” - 结果

最后,我们尝试搜索时间段:

高级搜索 - “2019-03-19 01:44:53 ~ 2019-03-19 01:44:55”

看一下,搜索结果吧:

高级搜索 - “2019-03-19 01:44:53 ~ 2019-03-19 01:44:55” - 结果

说实话,这个时间搜索结果,我不是很满意,ES 的时间问题,我打算在后面花一些时间去研究下。

搭建项目

基于Gradle搭建Spring Boot项目,把我折腾的受不了(如果哪位这方面有经验,可以给我指点指点),这个demo写了很久,那天都跑的好好的,今早上起来,就跑步起来了,一气之下,就改成Maven了。

下面看一下我的依赖和配置

pom.xml 片段

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/> <!-- lookup parent from repository -->
</parent><repositories><repository><id>jitpack.io</id><url>https://jitpack.io</url></repository>
</repositories><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--添加 JavaLib 支持用于接口返回--><dependency><groupId>com.github.fengwenyi</groupId><artifactId>JavaLib</artifactId><version>1.0.7.RELEASE</version></dependency><!--添加 webflux 支持用于编写非阻塞接口--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><!--添加 fastjson 的支持用于处理JSON格式数据--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.56</version></dependency><!--添加 Httpclient 的支持用于网络请求--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.7</version></dependency><!--添加 jsoup 的支持用于解析网页内容--><dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.10.2</version></dependency>
</dependencies>

application.yml

server:port: 9090spring:data:elasticsearch:cluster-nodes: localhost:9300cluster-name: es-wyfrepositories:enabled: true

PhoneModel

@Data
@Accessors(chain = true)
@Document(indexName = "springboot_elasticsearch_example_phone", type = "com.fengwenyi.springbootelasticsearchexamplephone.model.PhoneModel")
public class PhoneModel implements Serializable {private static final long serialVersionUID = -5087658155687251393L;/* ID */@Idprivate String id;/* 名称 */private String name;/* 颜色,用英文分号(;)分隔 */private String colors;/* 卖点,用英文分号(;)分隔 */private String sellingPoints;/* 价格 */private String price;/* 产量 */private Long yield;/* 销售量 */private Long sale;/* 上市时间 *///@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date marketTime;/* 数据抓取时间 *///@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private Date createTime;}

PhoneRepository

public interface PhoneRepository extends ElasticsearchRepository<PhoneModel, String> {
}

PhoneController

@RestController
@RequestMapping(value = "/phone")
@CrossOrigin
public class PhoneController {@Autowiredprivate ElasticsearchTemplate elasticsearchTemplate;}

后面接口,都会在这里写。

构造数据

我的数据是抓的 “华为” 和 “小米” 官网

首先使用 httpclient 下载html,然后使用 jsoup 进行解析。

华为 为例:

private void huawei() throws IOException {CloseableHttpClient httpclient = HttpClients.createDefault(); // 创建httpclient实例HttpGet httpget = new HttpGet("https://consumer.huawei.com/cn/phones/?ic_medium=hwdc&ic_source=corp_header_consumer"); // 创建httpget实例CloseableHttpResponse response = httpclient.execute(httpget); // 执行get请求HttpEntity entity=response.getEntity(); // 获取返回实体//System.out.println("网页内容:"+ EntityUtils.toString(entity, "utf-8")); // 指定编码打印网页内容String content = EntityUtils.toString(entity, "utf-8");response.close(); // 关闭流和释放系统资源//        System.out.println(content);Document document = Jsoup.parse(content);Elements elements = document.select("#content-v3-plp #pagehidedata .plphidedata");for (Element element : elements) {
//            System.out.println(element.text());String jsonStr = element.text();List<HuaWeiPhoneBean> list = JSON.parseArray(jsonStr, HuaWeiPhoneBean.class);for (HuaWeiPhoneBean bean : list) {String productName = bean.getProductName();List<ColorModeBean> colorsItemModeList = bean.getColorsItemMode();StringBuilder colors = new StringBuilder();for (ColorModeBean colorModeBean : colorsItemModeList) {String colorName = colorModeBean.getColorName();colors.append(colorName).append(";");}List<String> sellingPointList = bean.getSellingPoints();StringBuilder sellingPoints = new StringBuilder();for (String sellingPoint : sellingPointList) {sellingPoints.append(sellingPoint).append(";");}//                System.out.println("产品名:" + productName);
//                System.out.println("颜  色:" + color);
//                System.out.println("买  点:" + sellingPoint);
//                System.out.println("-----------------------------------");PhoneModel phoneModel = new PhoneModel().setName(productName).setColors(colors.substring(0, colors.length() - 1)).setSellingPoints(sellingPoints.substring(0, sellingPoints.length() - 1)).setCreateTime(new Date());phoneRepository.save(phoneModel);}}
}

全文搜索

全文搜索来说,还是相对来说,比较简单,直接贴代码吧:

/*** 全文搜索* @param keyword 关键字* @param page 当前页,从0开始* @param size 每页大小* @return {@link Result} 接收到的数据格式为json*/
@GetMapping("/full")
public Mono<Result> full(String keyword, int page, int size) {// System.out.println(new Date() + " => " + keyword);// 校验参数if (StringUtils.isEmpty(page))page = 0; // if page is null, page = 0if (StringUtils.isEmpty(size))size = 10; // if size is null, size default 10// 构造分页类Pageable pageable = PageRequest.of(page, size);// 构造查询 NativeSearchQueryBuilderNativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder().withPageable(pageable);if (!StringUtils.isEmpty(keyword)) {// keyword must not nullsearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keyword));}/*SearchQuery这个很关键,这是搜索条件的入口,elasticsearchTemplate 会 使用它 进行搜索*/SearchQuery searchQuery = searchQueryBuilder.build();// page searchPage<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);// returnreturn Mono.just(Result.success(phoneModelPage));
}

官网文档也是这么用的,所以相对来说,这还是很简单的,不过拆词 和 搜索策略 搜索速度 可能在实际使用中要考虑。

高级搜索

先看代码,后面我们再来分析:

/*** 高级搜索,根据字段进行搜索* @param name 名称* @param color 颜色* @param sellingPoint 卖点* @param price 价格* @param start 开始时间(格式:yyyy-MM-dd HH:mm:ss)* @param end 结束时间(格式:yyyy-MM-dd HH:mm:ss)* @param page 当前页,从0开始* @param size 每页大小* @return {@link Result}*/
@GetMapping("/_search")
public Mono<Result> search(String name, String color, String sellingPoint, String price, String start, String end, int page, int size) {// 校验参数if (StringUtils.isEmpty(page) || page < 0)page = 0; // if page is null, page = 0if (StringUtils.isEmpty(size) || size < 0)size = 10; // if size is null, size default 10// 构造分页对象Pageable pageable = PageRequest.of(page, size);// BoolQueryBuilder (Elasticsearch Query)BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();if (!StringUtils.isEmpty(name)) {boolQueryBuilder.must(QueryBuilders.matchQuery("name", name));}if (!StringUtils.isEmpty(color)) {boolQueryBuilder.must(QueryBuilders.matchQuery("colors", color));}if (!StringUtils.isEmpty(color)) {boolQueryBuilder.must(QueryBuilders.matchQuery("sellingPoints", sellingPoint));}if (!StringUtils.isEmpty(price)) {boolQueryBuilder.must(QueryBuilders.matchQuery("price", price));}if (!StringUtils.isEmpty(start)) {Date startTime = null;try {startTime = DateTimeUtil.stringToDate(start, DateTimeFormat.yyyy_MM_dd_HH_mm_ss);} catch (ParseException e) {e.printStackTrace();}boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime").gt(startTime.getTime()));}if (!StringUtils.isEmpty(end)) {Date endTime = null;try {endTime = DateTimeUtil.stringToDate(end, DateTimeFormat.yyyy_MM_dd_HH_mm_ss);} catch (ParseException e) {e.printStackTrace();}boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime").lt(endTime.getTime()));}// BoolQueryBuilder (Spring Query)SearchQuery searchQuery = new NativeSearchQueryBuilder().withPageable(pageable).withQuery(boolQueryBuilder).build();// page searchPage<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);// returnreturn Mono.just(Result.success(phoneModelPage));
}

不管spring如何封装,查询方式都一样,如下图:

es 搜索 语句

好吧,我们怀着这样的心态去看下源码。

org.springframework.data.elasticsearch.core.query.SearchQuery

这个是我们搜索需要用到对象

    public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) {this.queryBuilder = queryBuilder;return this;}

OK,根据源码,我们需要构造这个 QueryBuilder,那么问题来了,这个是个什么东西,我们要如何构造,继续看:

org.elasticsearch.index.query.QueryBuilder

注意包名。

啥,怎么又跑到 elasticsearch。

你想啊,你写的东西,会让别人直接操作吗?

答案是不会的,我们只会提供API,所有,不管Spring如何封装,也只会通过API去调用。

query 包下得类

好吧,今天先到这里,下一个专题,我们再讨论关于搜索问题。

链接

  • ElasticSearch入门

  • Elastic官网

  • ElasticSearch

  • ElasticSearch Docs

  • ElasticSearch Head

  • 搜索软件Elastic上市:市值近50亿美元 是开源项目商业化范本

ElasticSearch 学习系列

  • Elasticsearch入门篇——基础知识

  • Elasticsearch实战篇——Spring Boot整合ElasticSearch

  • Elasticsearch专题篇——搜索

代码

Spring Boot整合Elasticsearch

Spring Boot结合Elasticsearch,实现手机信息搜索小例子

演示视频

<iframe height=498 width=510 src='http://player.youku.com/embed/XNDEwNzg4NzMwOA==' frameborder=0 'allowfullscreen'></iframe>

如果无法播放,请点击这里

转载于:https://my.oschina.net/fengwenyi/blog/3026668

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/252079.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python: pip升级报错了:You are using pip version 10.0.1, however version 20.3.3 is available.

1,Python使用命令&#xff1a;python -m pip install --upgrade pip升级pip的时候报了下面这个错 2,换了个命令&#xff1a; python -m pip install --upgrade pip -i https://pypi.douban.com/simple 更新成功了&#xff0c;但又报了一个新的错误&#xff1a; AttributeError:…

新手上路之Hibernate:第一个Hibernate例子

一、Hibernate概述 &#xff08;一&#xff09;什么是Hibernate&#xff1f; Hibernate核心内容是ORM&#xff08;关系对象模型&#xff09;。可以将对象自动的生成数据库中的信息&#xff0c;使得开发更加的面向对象。这样作为程序员就可以使用面向对象的思想来操作数据库&…

模板标签及模板的继承与引用

1.常用的模板标签 - 作用是什么:提供各种逻辑 view.py: def index(request):#模板标签 --常用标签 总结&#xff1a;语法 {% tag %} {% endtag %} {% tag 参数 参数 %} 示例 展示页index.html&#xff0c;包含for标签&#xff0c;if标签&#xff0c;url标签 {% extends teacher…

Golang实现一个密码生成器

小地鼠防止有人偷他的果实&#xff0c;在家里上了一把锁。这个锁怎么来的呢&#xff1f;请往下看。。 package mainimport ("flag""fmt""math/rand""time" )var (length intcharset string )const (NUmStr "0123456789"C…

C# WPF:初识布局容器

StackPanel堆叠布局 StackPanel是简单布局方式之一&#xff0c;可以很方便的进行纵向布局和横向布局 StackPanel默认是纵向布局的 <Window x:Class"WpfApplication1.MainWindow" xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation" …

Kibana源码分析--Hapijs路由设置理解笔记

【ES6解构赋值】&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 【Joi APi】&#xff1a;https://github.com/hapijs/joi/blob/v13.1.2/API.md 转载于:https://www.cnblogs.com/lishidefengchen/p/866874…

Python打包EXE神器 pyinstaller

最近由于项目需要&#xff0c;以前的python文件需要编辑为EXE供前端客户使用。 由于最早接触的是distutils&#xff0c;所以一开始准备使用distutils和py2exe搭配来进行python的exe化&#xff0c;也就是传统的使用setup.py的方式来进行exe安装。但是结果都不是很好&#xff0c;…

20种PLC元件编号和Modbus编号地址对应表

1、三菱&#xff1a; X元件支持Modbus之02功能码&#xff1b; Y元件支持Modbus之01、05、15功能码&#xff1b; D元件支持Modbus之03、06、16功能码。 2、西门子&#xff1a; I元件支持Modbus之02功能码&#xff1b; Q元件支持Modbus之01、05、15功能码&#xff1b; V元件…

暑期学习

由于最后大作业的呈现情况与短学期所完成的还相差甚远&#xff0c;所以在暑期的时候开始进一步的细化。 在这个过程之中产生了如下的问题&#xff1a; 已解决的有&#xff1a; 1.用a标签在同一页面实现跳转。 要点&#xff1a;标记<a href"../home#pre">的时候…

五、RabbitMQ的消息属性(读书笔记)

2019独角兽企业重金招聘Python工程师标准>>> 简介 当使用RabbitMQ发布消息时&#xff0c;消息又AMQP规范中的三个低层帧类型组成&#xff1a; Basic.publish方法帧&#xff1b;内容头帧&#xff1b;消息体帧&#xff1b;这三种帧类型按顺序一起工作&#xff0c;以便…

异步和单线程

转载于:https://www.cnblogs.com/sunmarvell/p/8674748.html

C#:把dll封入exe中方法

在这个事件中,可以重新为加载失败的程序集手动加载 如果你将dll作为资源文件打包的你的应用程序中(或者类库中) 就可以在硬盘加载失败的时候 从资源文件中加载对应的dll 就像这样: class Program {static Program(){ //这个绑定事件必须要在引用到TestLibrary1这个程序…

C#结构类型图

转载于:https://www.cnblogs.com/kangao/p/8674838.html

使用gradle多渠道打包

以友盟的多渠道打包为例&#xff0c;如果我们须要打包出例如以下渠道&#xff1a;UMENG, WANDOUJIA, YINGYONGBAO。 第一种方法。是须要创建文件的。我们在写完我们的代码之后&#xff0c;在app/src以下。分别创建和main同级目录的目录umeng, wandoujia, yingyongbao,这三个目录…

四大步骤,彻底关闭Win10自动更新

尽管Win11已经发布了一段时间&#xff0c;但目前互联网上大部分电脑用户所使用的的操作系统仍是Win10&#xff0c;对于Win10&#xff0c;笔者相信大部分人应该都不陌生&#xff0c;作为目前市面上占比最高的电脑系统&#xff0c;Win10的许多功能和操作逻辑都十分优秀&#xff0…

虚拟机windows7安装启动MYSQL5.7

一.环境 环境&#xff1a;虚拟机VMVare 系统&#xff1a;windows7旗舰版 MYSQL版本&#xff1a;mysql5.7.25 二.具体步骤 1.首先下载安装mysql5.7.25&#xff0c;这里用的是安装版的mysql&#xff0c;网上大多数都是推荐去官网下载&#xff0c;这里推荐的是清华大学开源镜像站…

故障转移架构的本质:数据中心的基础设施过剩

数据中心构成了全球互联基础设施的核心&#xff0c;我们称之为“云”。从根本上讲&#xff0c;云计算指的是基础设施从桌面计算&#xff08;文件和应用程序存储在计算机的本地硬盘上&#xff09;到在线计算&#xff08;文件和应用程序存储在可通过互联网远程访问的数据中心中&a…

常用模块之hashlib,configparser,logging模块

常用模块二 hashlib模块 hashlib提供了常见的摘要算法&#xff0c;如md5和sha1等等。 那么什么是摘要算法呢?摘要算法又称为哈希算法、散列算法。它通过一个函数&#xff0c;把任意长度的数据转换为一个长度固定的数据串&#xff08;通常用16进制的字符串表示&#xff09;。 注…

浙江嘉兴徒步游

最近参加了一个徒步团&#xff0c;趁着周末时光&#xff0c;来了一场徒步旅游&#xff0c;不一样的体验图片发自简书App一开始进山探秘外蒲岛的路程&#xff0c;荒草丛生图片发自简书App树木郁郁葱葱&#xff0c;蓝天白云&#xff0c;一切都很没好图片发自简书App漫山遍野都开满…

ASP.NET Web API 2 过滤器

前言 我们知道 ASP.NET Web API 过滤器&#xff0c;也是属于消息处理机制中的一部分。正因如此&#xff0c;我们经常使用它来完成对请求的授权验证、参数验证&#xff0c;以及请求的 Log 记录&#xff0c;程序异常捕获等。 1. 常用的四大过滤器 ASP.NET Web API 2 中的所有…