Elasticsearch實(shí)戰(zhàn)篇——Spring Boot整合ElasticSearch

當(dāng)前Spring Boot很是流行,包括我自己因妇,也是在用Spring Boot集成其他框架進(jìn)行項(xiàng)目開(kāi)發(fā)灰瞻,所以這一節(jié),我們一起來(lái)探討Spring Boot整合ElasticSearch的問(wèn)題干跛。

本文主要講以下內(nèi)容:

第一部分子姜,通讀文檔

第二部分,Spring Boot整合ElasticSearch

第三部分楼入,基本的CRUD操作

第四部分哥捕,搜索

第五部分,例子

還沒(méi)有學(xué)過(guò)Elasticsearch的朋友嘉熊,可以先學(xué)這個(gè)系列的第一節(jié)(這個(gè)系列共三節(jié))扭弧,如果你有不明白或者不正確的地方,可以給我評(píng)論记舆、留言或者私信鸽捻。

第一步,通讀文檔

Spring Data Elasticsearch 官方文檔泽腮,這是當(dāng)前最新的文檔御蒲。

關(guān)于repository

文檔一開(kāi)始就介紹 CrudRepository ,比如诊赊,繼承 Repository厚满,其他比如 JpaRepositoryMongoRepository是繼承CrudRepository碧磅。也對(duì)其中的方法做了簡(jiǎn)單說(shuō)明碘箍,我們一起來(lái)看一下:

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繼承圖

這說(shuō)明什么丰榴?

  • 用法和JPA一樣;

  • 再這他除了有CRUD的基本功能之外秆撮,還有分頁(yè)和排序四濒。

清楚了這之后,是不是應(yīng)該考慮該如何使用了呢职辨?

如何用盗蟆?

沒(méi)錯(cuò),接下來(lái)舒裤,開(kāi)始說(shuō)如何用喳资,也寫(xiě)了很多示例代碼。相對(duì)來(lái)說(shuō)腾供,還是比較簡(jiǎn)單仆邓,這里就貼一下代碼就行了吧亏栈。

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

是不是這樣,就可以正常使用了呢宏赘?

問(wèn)題

當(dāng)然可以,但是如果錯(cuò)了問(wèn)題怎么辦呢黎侈,官網(wǎng)寫(xiě)了一個(gè)常見(jiàn)的問(wèn)題察署,比如包掃描問(wèn)題,沒(méi)有你要的方法峻汉。

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

你也可以自己寫(xiě)接口贴汪,并且去實(shí)現(xiàn)它。

說(shuō)完理論休吠,作為我扳埂,應(yīng)該在實(shí)際的代碼中如何運(yùn)用呢?

示例

官方也提供了很多示例代碼瘤礁,我們一起來(lái)看看阳懂。

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

這段代碼相對(duì)來(lái)說(shuō)還是十分經(jīng)典的,我相信很多人都看到別人的代碼柜思,可能都會(huì)問(wèn)岩调,它為什么會(huì)這么用呢,答案或許就在這里吧赡盘。

當(dāng)然号枕,這是以前的代碼,或許現(xiàn)在用不一定合適陨享。

高級(jí)搜索

終于到高潮了葱淳!

學(xué)完我的第一節(jié),你應(yīng)該已經(jīng)發(fā)現(xiàn)了抛姑,Elasticsearch搜索是一件十分復(fù)雜的事赞厕,為了用好它,我們不得不學(xué)好它定硝。一起加油坑傅。

到這里,官方文檔我們算是過(guò)了一遍了喷斋,大致明白了唁毒,他要告訴我們什么。其實(shí)星爪,文檔還有很多內(nèi)容浆西,可能你遇到的問(wèn)題都能在里面找到答案。

最后顽腾,我們繼續(xù)看一下官網(wǎng)寫(xiě)的一段處理得十分優(yōu)秀的一段代碼吧:

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

這樣就完成了整合诺核,接下來(lái)我們用兩種方式操作。

Model

我們先寫(xiě)一個(gè)的實(shí)體類久信,借助這個(gè)實(shí)體類呢來(lái)完成基礎(chǔ)的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字段是必須的艇棕,可以不寫(xiě)注解@Id。

BlogRepository

public interface BlogRepository extends ElasticsearchRepository<BlogModel, String> {
}

第三部分,CRUD

基礎(chǔ)操作的代碼悬槽,都是在 BlogController 里面寫(xiě)滨巴。

@RestController
@RequestMapping("/blog")
public class BlogController {
    @Autowired
    private BlogRepository blogRepository;
}

添加

@PostMapping("/add")
public Result add(@RequestBody BlogModel blogModel) {
    blogRepository.save(blogModel);
    return Result.success();
}

我們添加一條數(shù)據(jù)优烧,標(biāo)題是:Elasticsearch實(shí)戰(zhàn)篇:Spring Boot整合ElasticSearch沛豌,時(shí)間是:2019-03-06。我們來(lái)測(cè)試啃炸,看一下成不成功铆隘。

POST http://localhost:8080/blog/add

{
    "title":"Elasticsearch實(shí)戰(zhàn)篇:Spring Boot整合ElasticSearch",
    "time":"2019-05-06"
}

得到響應(yīng):

{
    "code": 0,
    "msg": "Success"
}

嘿,成功了南用。那接下來(lái)膀钠,我們一下查詢方法測(cè)試一下。

查詢

  • 根據(jù)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();
}

測(cè)試一下:

測(cè)試根據(jù)ID查詢

ok裹虫,沒(méi)問(wèn)題托修。

  • 查詢所有
@GetMapping("/get")
public Result getAll() {
    Iterable<BlogModel> iterable = blogRepository.findAll();
    List<BlogModel> list = new ArrayList<>();
    iterable.forEach(list::add);
    return Result.success(list);
}

測(cè)試一下:

GET http://localhost:8080/blog/get

結(jié)果:

{
    "code": 0,
    "msg": "Success",
    "data": [
        {
            "id": "fFXTTmkBTzBv3AXCweFS",
            "title": "Elasticsearch實(shí)戰(zhàn)篇:Spring Boot整合ElasticSearch",
            "time": "2019-05-06"
        }
    ]
}

根據(jù)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();
}

測(cè)試:

POST http://localhost:8080/blog/update

{
    "id":"fFXTTmkBTzBv3AXCweFS",
    "title":"Elasticsearch入門篇",
    "time":"2019-05-01"
}

響應(yīng):

{
    "code": 0,
    "msg": "Success"
}

查詢一下:

修改數(shù)據(jù)成功

ok,成功恒界!

刪除

  • 根據(jù)ID刪除
@DeleteMapping("/delete/{id}")
public Result deleteById(@PathVariable String id) {
    if (StringUtils.isEmpty(id))
        return Result.error();
    blogRepository.deleteById(id);
    return Result.success();
}

測(cè)試:

DELETE http://localhost:8080/blog/delete/fFXTTmkBTzBv3AXCweFS

響應(yīng):

{
    "code": 0,
    "msg": "Success"
}

我們?cè)俨橐幌拢?/p>

刪除數(shù)據(jù)成功
  • 刪除所有數(shù)據(jù)
@DeleteMapping("/delete")
public Result deleteById() {
    blogRepository.deleteAll();
    return Result.success();
}

第四部分睦刃,搜索

構(gòu)造數(shù)據(jù)

為了方便測(cè)試,我們先構(gòu)造數(shù)據(jù)

構(gòu)造查詢數(shù)據(jù)

Repository查詢操作

搜索標(biāo)題中的關(guān)鍵字

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

我們來(lái)測(cè)試一下十酣。

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

結(jié)果:

{
    "code": 0,
    "msg": "Success",
    "data": [
        {
            "id": "f1XrTmkBTzBv3AXCeeFA",
            "title": "java實(shí)戰(zhàn)",
            "time": "2018-03-01"
        },
        {
            "id": "fVXrTmkBTzBv3AXCHuGH",
            "title": "java入門",
            "time": "2018-01-01"
        },
        {
            "id": "flXrTmkBTzBv3AXCUOHj",
            "title": "java基礎(chǔ)",
            "time": "2018-02-01"
        },
        {
            "id": "gFXrTmkBTzBv3AXCn-Eb",
            "title": "java web",
            "time": "2018-04-01"
        },
        {
            "id": "gVXrTmkBTzBv3AXCzuGh",
            "title": "java ee",
            "time": "2018-04-10"
        }
    ]
}

繼續(xù)搜索:

GET http://localhost:8080/blog/rep/search/title?keyword=入門

結(jié)果:

{
    "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"
        }
    ]
}

為了驗(yàn)證涩拙,我們?cè)贀Q一個(gè)關(guān)鍵字搜索:

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實(shí)戰(zhàn)",
            "time": "2018-03-01"
        },
        {
            "id": "flXrTmkBTzBv3AXCUOHj",
            "title": "java基礎(chǔ)",
            "time": "2018-02-01"
        }
    ]
}

哈哈,有沒(méi)有覺(jué)得很眼熟耸采。

那根據(jù)上次的經(jīng)驗(yàn)兴泥,我們正好換一種方式解決這個(gè)問(wèn)題。

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

值得一提的是虾宇,官方文檔示例代碼可能是為了好看搓彻,出現(xiàn)問(wèn)題。

官網(wǎng)文檔給的錯(cuò)誤示例:

官網(wǎng)文檔錯(cuò)誤

官網(wǎng)示例代碼:

官方示例代碼

官方示例代碼

另外嘱朽,?0 代指變量的意思旭贬。

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

測(cè)試一下:

測(cè)試成功示例

ok,沒(méi)有問(wèn)題搪泳。

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

測(cè)試:

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

結(jié)果:

{
    "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實(shí)戰(zhàn)",
            "time": "2018-03-01"
        },
        {
            "id": "flXrTmkBTzBv3AXCUOHj",
            "title": "java基礎(chǔ)",
            "time": "2018-02-01"
        }
    ]
}

OK稀轨,暫時(shí)先到這里,關(guān)于搜索岸军,我們后面會(huì)專門開(kāi)一個(gè)專題奋刽,學(xué)習(xí)搜索瓦侮。

第五部分,例子

我們寫(xiě)個(gè)什么例子佣谐,想了很久肚吏,那就寫(xiě)一個(gè)搜索手機(jī)的例子吧!

界面截圖

我們先看下最后實(shí)現(xiàn)的效果吧

主頁(yè)效果:

主頁(yè)效果

分頁(yè)效果:

分頁(yè)效果

我們搜索 “小米”:

全文搜索 - “小米”

我們搜索 “1999”:

全文搜索 - “1999”

我們搜索 “黑色”:

全文搜索 - “黑色”

高級(jí)搜索頁(yè)面:

高級(jí)搜索 - "主頁(yè)面"

我們使用高級(jí)搜索狭魂,搜索:“小米”罚攀、“1999”:

高級(jí)搜索 - “小米 1999”

高級(jí)搜索 “小米”、“1999” 結(jié)果:

高級(jí)搜索 - “小米 1999” - 結(jié)果

上面的并且關(guān)系生效了嗎趁蕊?我們?cè)囈幌滤阉?“華為”,“1999”:

高級(jí)搜索 - “華為 1999” - 結(jié)果

最后仔役,我們嘗試搜索時(shí)間段:

高級(jí)搜索 - “2019-03-19 01:44:53 ~ 2019-03-19 01:44:55”

看一下掷伙,搜索結(jié)果吧:

高級(jí)搜索 - “2019-03-19 01:44:53 ~ 2019-03-19 01:44:55” - 結(jié)果

說(shuō)實(shí)話,這個(gè)時(shí)間搜索結(jié)果又兵,我不是很滿意任柜,ES 的時(shí)間問(wèn)題,我打算在后面花一些時(shí)間去研究下沛厨。

搭建項(xiàng)目

基于Gradle搭建Spring Boot項(xiàng)目宙地,把我折騰的受不了(如果哪位這方面有經(jīng)驗(yàn),可以給我指點(diǎn)指點(diǎn))逆皮,這個(gè)demo寫(xiě)了很久宅粥,那天都跑的好好的,今早上起來(lái)电谣,就跑步起來(lái)了秽梅,一氣之下,就改成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 支持
    用于編寫(xiě)非阻塞接口
     -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!--
    添加 fastjson 的支持
    用于處理JSON格式數(shù)據(jù)
     -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.56</version>
    </dependency>

    <!--
    添加 Httpclient 的支持
    用于網(wǎng)絡(luò)請(qǐng)求
     -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.7</version>
    </dependency>

    <!--
    添加 jsoup 的支持
    用于解析網(wǎng)頁(yè)內(nèi)容
     -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.10.2</version>
    </dependency>
</dependencies>

application.yml

server:
  port: 9090

spring:
  data:
    elasticsearch:
      cluster-nodes: localhost:9300
      cluster-name: es-wyf
      repositories:
        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 */
    @Id
    private String id;

    /* 名稱 */
    private String name;

    /* 顏色企垦,用英文分號(hào)(;)分隔 */
    private String colors;

    /* 賣點(diǎn),用英文分號(hào)(;)分隔 */
    private String sellingPoints;

    /* 價(jià)格 */
    private String price;

    /* 產(chǎn)量 */
    private Long yield;

    /* 銷售量 */
    private Long sale;

    /* 上市時(shí)間 */
    //@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date marketTime;

    /* 數(shù)據(jù)抓取時(shí)間 */
    //@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 {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

}

后面接口晒来,都會(huì)在這里寫(xiě)钞诡。

構(gòu)造數(shù)據(jù)

我的數(shù)據(jù)是抓的 “華為” 和 “小米” 官網(wǎng)

首先使用 httpclient 下載html,然后使用 jsoup 進(jìn)行解析湃崩。

華為 為例:

private void huawei() throws IOException {
    CloseableHttpClient httpclient = HttpClients.createDefault(); // 創(chuàng)建httpclient實(shí)例
    HttpGet httpget = new HttpGet("https://consumer.huawei.com/cn/phones/?ic_medium=hwdc&ic_source=corp_header_consumer"); // 創(chuàng)建httpget實(shí)例

    CloseableHttpResponse response = httpclient.execute(httpget); // 執(zhí)行g(shù)et請(qǐng)求
    HttpEntity entity=response.getEntity(); // 獲取返回實(shí)體
    //System.out.println("網(wǎng)頁(yè)內(nèi)容:"+ EntityUtils.toString(entity, "utf-8")); // 指定編碼打印網(wǎng)頁(yè)內(nèi)容
    String content = EntityUtils.toString(entity, "utf-8");
    response.close(); // 關(guān)閉流和釋放系統(tǒng)資源

//        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("產(chǎn)品名:" + productName);
//                System.out.println("顏  色:" + color);
//                System.out.println("買  點(diǎn):" + 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);
        }
    }
}

全文搜索

全文搜索來(lái)說(shuō)荧降,還是相對(duì)來(lái)說(shuō),比較簡(jiǎn)單攒读,直接貼代碼吧:

/**
 * 全文搜索
 * @param keyword 關(guān)鍵字
 * @param page 當(dāng)前頁(yè)誊抛,從0開(kāi)始
 * @param size 每頁(yè)大小
 * @return {@link Result} 接收到的數(shù)據(jù)格式為json
 */
@GetMapping("/full")
public Mono<Result> full(String keyword, int page, int size) {
    // System.out.println(new Date() + " => " + keyword);

    // 校驗(yàn)參數(shù)
    if (StringUtils.isEmpty(page))
        page = 0; // if page is null, page = 0
    
    if (StringUtils.isEmpty(size))
        size = 10; // if size is null, size default 10
    
    // 構(gòu)造分頁(yè)類
    Pageable pageable = PageRequest.of(page, size);
    
    // 構(gòu)造查詢 NativeSearchQueryBuilder
    NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder()
            .withPageable(pageable)
            ;
    if (!StringUtils.isEmpty(keyword)) {
        // keyword must not null
        searchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keyword));
    }
    
    /*
    SearchQuery
    這個(gè)很關(guān)鍵,這是搜索條件的入口整陌,
    elasticsearchTemplate 會(huì) 使用它 進(jìn)行搜索
     */
    SearchQuery searchQuery = searchQueryBuilder.build();

    // page search
    Page<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);
    
    // return
    return Mono.just(Result.success(phoneModelPage));
}

官網(wǎng)文檔也是這么用的拗窃,所以相對(duì)來(lái)說(shuō)瞎领,這還是很簡(jiǎn)單的,不過(guò)拆詞 和 搜索策略 搜索速度 可能在實(shí)際使用中要考慮随夸。

高級(jí)搜索

先看代碼九默,后面我們?cè)賮?lái)分析:

/**
 * 高級(jí)搜索,根據(jù)字段進(jìn)行搜索
 * @param name 名稱
 * @param color 顏色
 * @param sellingPoint 賣點(diǎn)
 * @param price 價(jià)格
 * @param start 開(kāi)始時(shí)間(格式:yyyy-MM-dd HH:mm:ss)
 * @param end 結(jié)束時(shí)間(格式:yyyy-MM-dd HH:mm:ss)
 * @param page 當(dāng)前頁(yè)宾毒,從0開(kāi)始
 * @param size 每頁(yè)大小
 * @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) {

    // 校驗(yàn)參數(shù)
    if (StringUtils.isEmpty(page) || page < 0)
        page = 0; // if page is null, page = 0

    if (StringUtils.isEmpty(size) || size < 0)
        size = 10; // if size is null, size default 10
    
    // 構(gòu)造分頁(yè)對(duì)象
    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 search
    Page<PhoneModel> phoneModelPage = elasticsearchTemplate.queryForPage(searchQuery, PhoneModel.class);
    
    // return
    return Mono.just(Result.success(phoneModelPage));
}

不管spring如何封裝驼修,查詢方式都一樣,如下圖:

es 搜索 語(yǔ)句

好吧诈铛,我們懷著這樣的心態(tài)去看下源碼乙各。

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

這個(gè)是我們搜索需要用到對(duì)象

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

OK,根據(jù)源碼幢竹,我們需要構(gòu)造這個(gè) QueryBuilder耳峦,那么問(wèn)題來(lái)了,這個(gè)是個(gè)什么東西焕毫,我們要如何構(gòu)造蹲坷,繼續(xù)看:

org.elasticsearch.index.query.QueryBuilder

注意包名。

啥邑飒,怎么又跑到 elasticsearch循签。

你想啊,你寫(xiě)的東西疙咸,會(huì)讓別人直接操作嗎县匠?

答案是不會(huì)的,我們只會(huì)提供API撒轮,所有聚唐,不管Spring如何封裝,也只會(huì)通過(guò)API去調(diào)用腔召。

query 包下得類

好吧杆查,今天先到這里,下一個(gè)專題臀蛛,我們?cè)儆懻撽P(guān)于搜索問(wèn)題亲桦。

鏈接

ElasticSearch 學(xué)習(xí)系列

代碼

Spring Boot整合Elasticsearch

演示視頻

SpringBoot集合Elasticsearch,實(shí)現(xiàn)手機(jī)搜索小例子

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浊仆,一起剝皮案震驚了整個(gè)濱河市客峭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抡柿,老刑警劉巖舔琅,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洲劣,居然都是意外死亡备蚓,警方通過(guò)查閱死者的電腦和手機(jī)课蔬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)郊尝,“玉大人二跋,你說(shuō)我怎么就攤上這事×骰瑁” “怎么了扎即?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)况凉。 經(jīng)常有香客問(wèn)我谚鄙,道長(zhǎng),這世上最難降的妖魔是什么刁绒? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任闷营,我火速辦了婚禮,結(jié)果婚禮上膛锭,老公的妹妹穿的比我還像新娘粮坞。我一直安慰自己蚊荣,他們只是感情好初狰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著互例,像睡著了一般奢入。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媳叨,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天腥光,我揣著相機(jī)與錄音,去河邊找鬼糊秆。 笑死武福,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痘番。 我是一名探鬼主播捉片,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼汞舱!你這毒婦竟也來(lái)了伍纫?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昂芜,失蹤者是張志新(化名)和其女友劉穎莹规,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體泌神,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡良漱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年舞虱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片债热。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砾嫉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窒篱,到底是詐尸還是另有隱情焕刮,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布墙杯,位于F島的核電站配并,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏高镐。R本人自食惡果不足惜溉旋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫉髓。 院中可真熱鬧观腊,春花似錦、人聲如沸算行。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)州邢。三九已至儡陨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間量淌,已是汗流浹背骗村。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呀枢,地道東北人胚股。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像裙秋,于是被迫代替她去往敵國(guó)和親琅拌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355