elasticsearch系列(1):springboot整合elasticsearch(含实战)

版本匹配

image.png

我安装的elasticsearch是7.6.2的,所以我的springboot版本得使用2.3.x的

可视化工具ElasticHD

1、下载

方式一:公众号获取
关注I am Walker,回复elasticHD

方式二:github下载
https://github.com/qax-os/ElasticHD/releases
image.png
我这里使用的是64位的windows,所以直接点击下载即可

2、解压并启动

解压后,将文件夹打开,里面又ElasticHD.exe
image.png

双击执行即可,执行会出现命令行
image.png

然后访问localhost:9800,有时候0.0.0.0:9800是不生效的
页面如下:
image.png

之后启动elasticsearch,并且连接自己的elasticsearch即可

springboot使用

参考文档:
https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.query-methods.finders

前提要求

版本匹配

本案例中使用的es时7.6.2的,所以spring-boot的版本应为2.3.x

使用

1、导入依赖


          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
          </dependency>

<!--  lombok也引入,方便实体类getter和setter的使用 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>

2、配置

方式一:application.yaml中配置
spring:
  elasticsearch:
    rest:
    # es的访问路径
      uris: localhost:9200

# 端口随意定义即可
server:
  port: 8079

方式二:编写RestClientConfig配置(当配置比较复杂时,可采用该方法)

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
        final ClientConfiguration clientConfiguration= ClientConfiguration.builder()
                .connectedTo("localhost:9200") //连接自己的elasticsearch的url
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}

具体的客户端配置,可以查看文档
image.png

3、实体类创建

例如,以userEntity作为案例对象

package com.walker.es.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
//文档
@Document(indexName = "user")
public class UserEntity {
    //文档的id
    @Id
    private String id;
    @Field(type = FieldType.Text)
    private String name;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Text)
    private String hobby;
}

在实体类中,可以使用的注解主要如下:
@Document:文档,用于类

  • indexName代表索引的名称定义好之后,可以根据operations方法创建对应的索引,indexName也可以使用表达式,例如:“log-#{T(java.time.LocalDate).now().toString()}”
  • createIndex 默认为true,代表着当查询数据的时候,如果所以不存在,则会自动进行创建

**@Id: **文档id
@Field: 属性,一般用于对象属性上

  • name :属性名称,这可以用于重命名,如果没有设置的话,属性名和类的属性名称一致
  • type:属性类型,类型主要有以下:Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float, Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range, Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type,常用的已经加粗,基本跟java常用的类型差不多,其他的大家可以自己进行研究
  • pattern:例如时间格式,@Field(type=FieldType.Date, pattern=“dd.MM.uuuu”)
  • store:标记原始字段是否存储在es中

其他注解:
@Transient: 不会被写入映射到es文档中
@ReadOnlyProperty:只读属性,无法修改值,但是会从es中返回对应的数据回来
@WriteOnlyProperty:只写属性,数据存在于es中,可以进行修改,但是不会从es中返回来
@GeoPoint:地理位置

4、操作实体 Elasticsearch Operations

主要分为以下几种,分别是

  • IndexOperations :一般用来操作索引
  • DocumentOperations :操作文档
  • SearchOperations :操作查询
  • ElasticsearchOperations : 包含DocumentOperations 和SearchOperations ,关系如下:

image.png
一般来说使用ElasticSearchOperations比较多,下面以ElasticSearchOperations为案例进行使用

5、ElasticSearchOperations 基础操作

在DocumentOperations中主要有以下的方法,主要针对于document进行操作,也就是对文档的增删改查
image.png

而对于SearchOperations的方法主要如下:
image.png

在编写测试代码前,先引入ElasticsearchOperations

@Autowired
private ElasticsearchOperations elasticsearchOperations;
新增数据 save
 @Test
    void save() {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("walker");
        userEntity.setAge(18);
        userEntity.setHobby("篮球");
        userEntity.setCreateTime(new Date());
        elasticsearchOperations.save(userEntity);
    }

一般来说如果id不进行赋值的话,会自动生成id,但是类型为String
image.png

注意

  • 当自己填写id,且id的值存在的时候,会直接覆盖数据,而不是生成新的数据,因为id是唯一的
批量添加 save
@Test
    void saveBatch() {

        ArrayList<UserEntity> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            UserEntity userEntity = new UserEntity();
            userEntity.setName("测试"+new Random().nextInt(10000));
            userEntity.setAge(new Random().nextInt(50));
            userEntity.setHobby("爱好"+i);
            userEntity.setCreateTime(new Date());
            list.add(userEntity);
        }

        //批量添加
        elasticsearchOperations.save(list);
    }
根据id查询单个数据 get
<T> T get(String id, Class<T> clazz);

传入实体类和id即可查询,如果查询到则会直接返回结果,否则则会返回null

@Test
    void get() {
        //先新增一条数据
        UserEntity userEntity = new UserEntity();
        userEntity.setId("abcd1");
        userEntity.setName("test");
        userEntity.setAge(11);
        userEntity.setHobby("羽毛球");
        userEntity.setCreateTime(new Date());
        elasticsearchOperations.save(userEntity);

        //获取数据
        System.out.println(elasticsearchOperations.get(userEntity.getId(), UserEntity.class));
    }
根据id删除数据 delete
String delete(String id, Class<?> entityType);
  • id
  • entityType: 类
 @Test
    void delete() {
        //删除数据
        String res = elasticsearchOperations.delete("abcd1", UserEntity.class);
        //返回的是id
        System.out.println(res);
    }
根据id查询是否存在 exists

boolean exists(String id, Class<?> clazz);

@Test
    void exists(){
        boolean res = elasticsearchOperations.exists("P9I8mIcBk_im7nqSwlT5", UserEntity.class);
        System.out.println(res);
    }
批量新增修改 bulkIndex
default List<String> bulkIndex(List<IndexQuery> queries, IndexCoordinates index) {
   return bulkIndex(queries, BulkOptions.defaultOptions(), index);
}
    @Test
    void bulkIndex(){
        ArrayList<IndexQuery> indexQueries = new ArrayList<>();
        IndexQuery indexQuery = new IndexQuery();
        indexQuery.setId("aaa");
        UserEntity u = new UserEntity();
        u.setName("hello");
        u.setHobby("pingpong");
        indexQuery.setSource(JSON.toJSONString(u));
        indexQueries.add(indexQuery);
    	
        IndexQuery iq2 = new IndexQuery();
        iq2.setId("bbb");
        UserEntity u2 = new UserEntity();
        u2.setName("a2");
        u2.setHobby("aaa");
        iq2.setSource(JSON.toJSONString(u2));
        indexQueries.add(iq2);

        //批量新增/修改文档 如果id相同则进行覆盖
        List<String> res = elasticsearchOperations.bulkIndex(indexQueries, IndexCoordinates.of("user"));
        System.out.println(res);
    }
查询 Query

查询结果类型分类:

  • SearchHit 命中单个
  • SearchHits 命中集合
  • SearchPage 命中分页
  • SearchScrollHits
  • SearchHitsIterator

查询方式主要有三种

  • CriteriaQuery 标准查询
  • StringQuery 字符串查询
  • NativeQuery 原生查询
CriteriaQuery

标准查询,可以先构建Criteria,然后放入CriteriaQuery。

 @Test
    void criteriaQuery(){
        //is:精确匹配
        Criteria c = new Criteria("name").is("walker");

        CriteriaQuery criteriaQuery = new CriteriaQuery(c)
                //分页
                .setPageable(PageRequest.of(1,1));
        SearchHits<UserEntity> search = elasticsearchOperations.search(criteriaQuery, UserEntity.class);
        log.info(JSON.toJSONString(search));
    }

返回结果如下:

{
	"empty": false, // 是否为空
	"maxScore": 1.9365597, //最大分数
	"searchHits": [{ //命中数据
		"content": {
			"age": 18,
			"createTime": 1681862400000,
			"hobby": "篮球",
			"id": "PNI4mIcBk_im7nqS7VSg",
			"name": "walker"
		},
		"highlightFields": {},
		"id": "PNI4mIcBk_im7nqS7VSg",  //id
		"score": 1.9365597, //分数
		"sortValues": []
	}],
	"totalHits": 1,  //总数
	"totalHitsRelation": "EQUAL_TO"
}

这里就简单的教大家怎么使用,具体的可以自己尝试了

Criteria的主要方法有以下:
image.png

and 且,可以添加新的Criteria
or 或  有些类似于should
exists 存在
is 是
contains  包含
startsWith  以什么为开头
endsWith 以什么结尾
between  介于 
expression
lessThanEqual 小于等于 
not 非
fuzzy 模糊
greaterThan 大于
greaterThanEqual  大于等于
lessThan 小于
in 存在于
notIn 不存在于
...
StringQuery

该查询方法主要是使用JSON表达式去编写的,比较类似于我们直接使用ES api的感觉

    @Test
    void stringQuery(){
        StringQuery query = new StringQuery("{ \"match\": { \"name\": { \"query\": \"walker\" } } } ");
        SearchHits<UserEntity> search = elasticsearchOperations.search(query, UserEntity.class);
        log.info(JSON.toJSONString(search));
    }
NativeQuery (包含高亮)

当CriteriaQuery没法支持的时候,可以使用该query,例如同时进行查询和聚合的时候,或者分页高亮等等,原生查询,可以进行的操作相对较多,案例如下:

    @Test
    void nativeQuery(){

        NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();
        PageRequest page = PageRequest.of(1, 1);
        NativeSearchQuery build = nb.withQuery(QueryBuilders
                .matchQuery("name", "walker"))
                //排序
                .withSort(SortBuilders.fieldSort("age").sortMode(SortMode.MAX))
                //高亮
                .withHighlightBuilder(new HighlightBuilder().field("name"))
                //分页
                .withPageable(page)
                .build();
        
        SearchHits<UserEntity> search = elasticsearchOperations.search(build, UserEntity.class);
        log.info(JSON.toJSONString(search));
        
    }

NativeSearchQueryBuilder中可以使用的方法

withQuery 查询
withFilter 过滤
withSort 排序
withScriptField  脚本属性 
withCollapseField 
addAggregation //聚合函数
withHighlightBuilder //高亮
withHighlightFields //高亮
withPageable //分页
withMinScore //最小分数
withIds //id
withSearchType //查询类型

在spring-boot-elasticsearch中,还提供了另外一种方式进行查询的,叫做
Elasticsearch Repositories,有些类似于JPA的使用,只需要定义对应的接口方法,就可以使用了,而且在使用过程中有一定的规则,接下来便听我讲讲

6、Elasticsearch Repositories

在Repository中按照格式定义方法,就会自动转换成对应的表达式,极大地方便我们使用,例如模糊查找属性name的时候,可以使用findByName,就会被映射为:

{
    "query": {
        "bool" : {
            "must" : [
                { "query_string" : { "query" : "?", "fields" : [ "name" ] } },
            ]
        }
    }
}

如果需要使用且,就使用And,或就是用Or,例如根据name和price同时查询,可以使用
findByNameAndPrice(String name, Integer price),会被映射为:

{
    "query": {
        "bool" : {
            "must" : [
                { "query_string" : { "query" : "?", "fields" : [ "name" ] } },
                { "query_string" : { "query" : "?", "fields" : [ "price" ] } }
            ]
        }
    }
}
关键词

关键词主要有如下这些:大家可以查看以下文档

KeywordSampleElasticsearch Query String
AndfindByNameAndPrice{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } },
{ “query_string” : { “query” : “?”, “fields” : [ “price” ] } }
]
}
}}
OrfindByNameOrPrice{ “query” : {
“bool” : {
“should” : [
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } },
{ “query_string” : { “query” : “?”, “fields” : [ “price” ] } }
]
}
}}
IsfindByName{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } }
]
}
}}
NotfindByNameNot{ “query” : {
“bool” : {
“must_not” : [
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] } }
]
}
}}
BetweenfindByPriceBetween{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } }
]
}
}}
LessThanfindByPriceLessThan{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } }
]
}
}}
LessThanEqualfindByPriceLessThanEqual{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } }
]
}
}}
GreaterThanfindByPriceGreaterThan{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } }
]
}
}}
GreaterThanEqualfindByPriceGreaterThan{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } }
]
}
}}
BeforefindByPriceBefore{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } }
]
}
}}
AfterfindByPriceAfter{ “query” : {
“bool” : {
“must” : [
{“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } }
]
}
}}
LikefindByNameLike{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true }
]
}
}}
StartingWithfindByNameStartingWith{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true }
]
}
}}
EndingWithfindByNameEndingWith{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true }
]
}
}}
Contains/ContainingfindByNameContaining{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true }
]
}
}}
In (when annotated as FieldType.Keyword)findByNameIn(Collectionnames){ “query” : {
“bool” : {
“must” : [
{“bool” : {“must” : [
{“terms” : {“name” : [“?”,“?”]}}
]
}
}
]
}
}}
InfindByNameIn(Collectionnames){ “query”: {“bool”: {“must”: [{“query_string”:{“query”: “\”?\" \“?\”", “fields”: [“name”]}}]}}}
NotIn (when annotated as FieldType.Keyword)findByNameNotIn(Collectionnames){ “query” : {
“bool” : {
“must” : [
{“bool” : {“must_not” : [
{“terms” : {“name” : [“?”,“?”]}}
]
}
}
]
}
}}
NotInfindByNameNotIn(Collectionnames){“query”: {“bool”: {“must”: [{“query_string”: {“query”: “NOT(\”?\" \“?\”)", “fields”: [“name”]}}]}}}
TruefindByAvailableTrue{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “true”, “fields” : [ “available” ] } }
]
}
}}
FalsefindByAvailableFalse{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “false”, “fields” : [ “available” ] } }
]
}
}}
OrderByfindByAvailableTrueOrderByNameDesc{ “query” : {
“bool” : {
“must” : [
{ “query_string” : { “query” : “true”, “fields” : [ “available” ] } }
]
}
}, “sort”:[{“name”:{“order”:“desc”}}]
}
ExistsfindByNameExists{“query”:{“bool”:{“must”:[{“exists”:{“field”:“name”}}]}}}
IsNullfindByNameIsNull{“query”:{“bool”:{“must_not”:[{“exists”:{“field”:“name”}}]}}}
IsNotNullfindByNameIsNotNull{“query”:{“bool”:{“must”:[{“exists”:{“field”:“name”}}]}}}
IsEmptyfindByNameIsEmpty{“query”:{“bool”:{“must”:[{“bool”:{“must”:[{“exists”:{“field”:“name”}}],“must_not”:[{“wildcard”:{“name”:{“wildcard”:“*”}}}]}}]}}}
IsNotEmptyfindByNameIsNotEmpty{“query”:{“bool”:{“must”:[{“wildcard”:{“name”:{“wildcard”:“*”}}}]}}}
返回结果类型
  • List
  • Stream
  • SearchHits
  • List<SearchHit>
  • Stream<SearchHit>
  • SearchPage
注解
@Query 原生query查询

当然,也可以使用@Query进行json字符串原生查询的编写,在遇到不大好处理的情况时,这个时候名称就可以不需要按照findByXX的格式进行编写了

    @Query(value = "{\"match\": {\"age\": {\"query\": \"?0\"}}}")
    SearchHits<UserEntity> age(Integer age);
@Highlight 高亮

可以repository方法中可以使用@Highlight进行配置
例如下边的案例,是对hobby字段进行高亮处理

    @Highlight(fields = {
            @HighlightField(name = "hobby")
    })
    SearchHits<UserEntity> findByHobbyLike(String hobby);
使用案例
1、首先先定义实体类

这里在上边已经接收过了,就不详细介绍了

package com.walker.es.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;

@Data
@Document(indexName = "user",createIndex = true)
public class UserEntity {
    @Id
    private String id;
    @Field(type = FieldType.Text)
    private String name;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Text)
    private String hobby;
    @Field(type = FieldType.Date,format = DateFormat.basic_date)
    private Date createTime;
}

2、编写Repository接口类
package com.walker.es.repository;

import com.walker.es.model.UserEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Highlight;
import org.springframework.data.elasticsearch.annotations.HighlightField;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Service;

@Service
public interface UserRepository extends ElasticsearchRepository<UserEntity, String> {


    /**
     * 查询单个:默认会获取第一个
     */
    UserEntity findByName(String name);


    /**
     * 分页查询
     */
    Page<UserEntity> findByNameLike(String name, Pageable pageable);


    /**
     * 使用@Query注解
     */
    @Query(value = "{\"match\": {\"age\": {\"query\": \"?0\"}}}")
    SearchHits<UserEntity> age(Integer age);


    /**
     * 高亮查询
     */
    @Highlight(fields = {
            @HighlightField(name = "hobby")
    })
    SearchHits<UserEntity> findByHobbyLike(String hobby);
}

3、测试使用
package com.walker.es.repository;

import com.alibaba.fastjson.JSON;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.walker.es.model.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.AbstractPageRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;

import java.util.Iterator;
import java.util.List;

@Slf4j
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;


    @Test
    void query(){
        //查询单个
        System.out.println(userRepository.findByName("walker"));

        //模糊查询
        System.out.println(userRepository.findByNameLike("walker"));

        //分页
        PageRequest of = PageRequest.of(1, 2);
        Page<UserEntity> page = userRepository.findByNameLike("walker", of);
        List<UserEntity> content = page.getContent();
        System.out.println(content);

        //高亮
        System.out.println("高亮");
        SearchHits<UserEntity> hits = userRepository.findByHobbyLike("篮球");
        log.info(JSON.toJSONString(hits));


        //原生查询
        SearchHits<UserEntity> age = userRepository.age(18);
        log.info(JSON.toJSONString(age));
    }


}

具体实践

商品搜索【模仿京东】

参考:https://blog.csdn.net/qq_41432730/article/details/121723456

拉取数据

主要是从京东商品中爬取数据,使用的是jsoup进行拉取
爬取的网址如下:
[https://search.jd.com/Search?keyword=](https://search.jd.com/Search?keyword=)关键词

1、导入依赖
<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.10.2</version>
</dependency>
2、编写商品实体类和Jsoup工具类,从京东中拉取数据
  • 实体类
package com.walker.es.model;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;

/**
* 京东商品实体类
*/
@Data
//es 文档注解类
@Document(indexName = "jdgood") 
public class JdGoodEntity {
    @Id
    private String id;
    private String price;
    private String title;
    private String img;
}

  • Jsoup工具类
package com.walker.es.utils;

import com.alibaba.fastjson.JSON;
import com.walker.es.model.JdGoodEntity;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class JsoupUtils {


    //这里自己可以调用main方法测试一下,能否爬取到对应的数据
    public static void main(String[] args) throws IOException {
        List<JdGoodEntity> res = parseJD("java");
        log.info(JSON.toJSONString(res));
    }



    //从京东中爬取商品数据
    public static List<JdGoodEntity> parseJD(String keywords) throws IOException {
        // 中文需要转义
        Document document = parse("https://search.jd.com/Search?keyword=",keywords,30000);
        //所有js中的操作都可以使用。
        Element element = document.getElementById("J_goodsList");
        //获取所有Li标签
        Elements elements = element.getElementsByTag("li");

        List<JdGoodEntity> goodsList = new ArrayList<>();
        //获取元素中的内容 这个的el就是每一个li标签
        for (Element el : elements) {
            //关于获取不到图片,由于是懒加载的,所以获取src是获取不到的
            String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String name = el.getElementsByClass("p-name").eq(0).text();

            JdGoodEntity content = new JdGoodEntity();
            content.setPrice(price);
            content.setImg(img);
            content.setTitle(name);
            goodsList.add(content);
        }

        return goodsList;
    }

    /**
    *  解析网站
    */
    public static Document parse(String url,String keywords,int timeoutMillis) throws IOException {
        url=url + keywords;
        //解析网页(document就是浏览器页面对象)
        Document document = Jsoup.parse(new URL(url), timeoutMillis);
        return document;
    }


}

3、controller编写
  • controller
package com.walker.es.controller;

import com.walker.es.model.JdGoodEntity;
import com.walker.es.service.JdGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;

@RequestMapping("/jd")
@RestController
public class JDController {


    @Autowired
    private JdGoodService jdGoodService;

    /**
     * 爬取京东数据并添加到es中
     */
    @GetMapping("/parseData/{keyword}")
    public String parseData(@PathVariable(value = "keyword") String keyword) {
        jdGoodService.parseData(keyword);
        return "ok";
    }

    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public ArrayList<JdGoodEntity> search(@PathVariable("keyword") String keyword,
                                          @PathVariable("pageNo") int pageNo,
                                          @PathVariable("pageSize") int pageSize){
        return jdGoodService.search(keyword,pageNo,pageSize);
    }
}

  • service
package com.walker.es.service;

import com.walker.es.model.JdGoodEntity;

import java.util.ArrayList;

public interface JdGoodService {
    void parseData(String keyword);

    ArrayList<JdGoodEntity> search(String keyword, int pageNo, int pageSize);
}

  • service实现类
package com.walker.es.service.impl;

import com.walker.es.model.JdGoodEntity;
import com.walker.es.repository.JdGoodRepository;
import com.walker.es.service.JdGoodService;
import com.walker.es.utils.JsoupUtils;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class JdGoodServiceImpl implements JdGoodService {

    @Autowired
    private ElasticsearchOperations elasticsearchOperations;
    @Autowired
    private JdGoodRepository jdGoodRepository;

    @Override
    public void parseData(String keyword) {
        try {
            List<JdGoodEntity> res = JsoupUtils.parseJD("java");
            elasticsearchOperations.save(res);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

    @Override
    public ArrayList<JdGoodEntity> search(String keyword, int pageNo, int pageSize) {
        
        //这里是可以使用多种方式进行查询的
        //可以使用NativeSearchQuery 

        //也可以使用repository方式进行
        
        NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();
        PageRequest page = PageRequest.of(pageNo, pageSize);

        NativeSearchQuery query = nb.withQuery(QueryBuilders.matchQuery("title", keyword))
                .withHighlightBuilder(new HighlightBuilder().field("title"))
                .withPageable(page)
                .build();
        SearchHits<JdGoodEntity> search = elasticsearchOperations.search(query, JdGoodEntity.class);


        ArrayList<JdGoodEntity> entities = new ArrayList<>();

        for (SearchHit<JdGoodEntity> hit : search) {
            JdGoodEntity content = hit.getContent();
            content.setTitle(hit.getHighlightField("title").get(0));
            entities.add(content);
        }
        return entities;
    }
}

如果是使用repository方式的话 repository如此编写
这里需要注意,返回结果使用SearchPage,而不是Page,如果是Page的话,没法获取到高亮的返回结果

    @Highlight(fields = {
            @HighlightField(name = "title")
    })
    SearchPage<JdGoodEntity> findByTitle(String title, Pageable pageable);
4、调用测试
  • 爬取解析上传数据

image.png
返回数据为ok即可,之后再使用查询进行测试一下,看一下是否能够查询到数据

  • 查询

image.png
返回结果如下:
结果已经对高亮的部分获取,并进行了替换
image.png

[
  {
    "id": "Q9hLn4cB2uWypaEqQ9Dr",
    "price": "¥59.90",
    "title": "【动力节点著作】漫画<em>Java</em>编程 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img14.360buyimg.com/n7/jfs/t1/169886/36/37126/66675/6440e096F40c55ebc/3341b7ca43cf1b10.jpg"
  },
  {
    "id": "L9hLn4cB2uWypaEqQ9Dr",
    "price": "¥35.60",
    "title": "O'Reilly:Head First <em>Java</em>(中文版 第2版 涵盖Java5.0) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img10.360buyimg.com/n7/jfs/t1/203043/24/32689/53051/6440da57Fec2ec08c/ad5ed54beaf84a09.jpg"
  },
  {
    "id": "ONhLn4cB2uWypaEqQ9Dr",
    "price": "¥64.50",
    "title": "大话设计模式 <em>Java</em>溢彩加强版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img12.360buyimg.com/n7/jfs/t1/170340/1/36868/66744/6440de7bFa9b0310e/c8f3eb616cba42d9.jpg"
  },
  {
    "id": "RNhLn4cB2uWypaEqQ9Dr",
    "price": "¥149.00",
    "title": "<em>Java</em>核心技术 第11版 套装共2册 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img10.360buyimg.com/n7/jfs/t1/220363/2/28321/59463/6440d7cdF62467337/de186c21b70c8b33.jpg"
  },
  {
    "id": "NNhLn4cB2uWypaEqQ9Dr",
    "price": "¥100.00",
    "title": "计算机二级C语言办公软件office硬件运维网络工程师<em>JAVA</em>程序员平面设计考试IT证书考证培训 Pro/E设计师",
    "img": "//img12.360buyimg.com/n7/jfs/t1/58711/30/19751/395110/62a807a5E54f52128/9429184ff08da8a0.jpg"
  },
  {
    "id": "PdhLn4cB2uWypaEqQ9Dr",
    "price": "¥83.40",
    "title": "<em>Java</em>语言程序设计 基础篇 原书第12版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img12.360buyimg.com/n7/jfs/t1/158063/34/21117/158566/6080deecEe8a120f5/133314834a5093d1.jpg"
  },
  {
    "id": "KdhLn4cB2uWypaEqQ9Dr",
    "price": "¥74.50",
    "title": "<em>Java</em>核心技术 卷I:开发基础(原书第12版) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img10.360buyimg.com/n7/jfs/t1/36950/34/19440/55191/6440d87eF269de47e/5c7dbb3889501aa1.jpg"
  },
  {
    "id": "MthLn4cB2uWypaEqQ9Dr",
    "price": "¥74.50",
    "title": "<em>Java</em>核心技术 卷II:高级特性 原书第12版 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img13.360buyimg.com/n7/jfs/t1/100230/24/32336/84778/641d0582Fe1b45780/f28cc0c3d824fc7b.jpg"
  },
  {
    "id": "PNhLn4cB2uWypaEqQ9Dr",
    "price": "¥3600.00",
    "title": "佳沃(<em>JAVA</em>) 公路自行车鱼雷3代碟刹22变速弯把男女碳纤维前叉男女成人赛车 3代黑色DECA版 22速 50#建议173-178CM 700C",
    "img": "//img12.360buyimg.com/n7/jfs/t1/198893/6/761/440240/6104f84dE8b85f555/0bb465d9a3b8295c.jpg"
  },
  {
    "id": "KNhLn4cB2uWypaEqQ9Dr",
    "price": "¥39.90",
    "title": "<em>Java</em>从入门到精通(第6版)(软件开发视频大讲堂) 423阅读狂欢节,图书5折封顶部分商品领券再享300减100,团购电话4006186622抢购",
    "img": "//img13.360buyimg.com/n7/jfs/t1/159462/12/37290/61622/6440dcdcFa88a8131/f99130288178cf39.jpg"
  }
]

在我们的实际场景中,也是一样,一般是将跑个定时任务,将数据定时上传到es中,然后进行分页查询即可

问题

Method has to have one of the following return types!

原因:
image.png
参数使用了Pageable,但是返回结果没有使用Page

总结

对于springboot整合elasticsearch的使用,首先需要注意版本的匹配,然后阅读对应版本的文档去对接,在使用过程中,elasticsearch可以选择多种方式,大家可以选择自己觉得比较习惯便捷的方式去执行。
希望对你有所帮助,我是Walker

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WalkerShen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值