Elasticsearch入门

一、索引操作

1、Mapping映射属性

常见的Mapping属性包括:

  • <font style="background-color:rgb(187,191,196);">type</font>:字段数据类型,常见的简单类型有:
    • 字符串:<font style="background-color:rgb(187,191,196);">text</font>(可分词的文本)
    • <font style="background-color:rgb(187,191,196);">keyword</font>(精确值,例如:品牌、国家、ip地址),keyword 类型主要用于存储不需要分词处理的字符串,例如电子邮件地址、标签、ID 等,这些字符串通常用于精确匹配搜索。
    • 数值:<font style="background-color:rgb(187,191,196);">long</font><font style="background-color:rgb(187,191,196);">integer</font><font style="background-color:rgb(187,191,196);">short</font><font style="background-color:rgb(187,191,196);">byte</font><font style="background-color:rgb(187,191,196);">double</font><font style="background-color:rgb(187,191,196);">float</font>
    • 布尔:<font style="background-color:rgb(187,191,196);">boolean</font>
    • 日期:<font style="background-color:rgb(187,191,196);">date</font>
    • 对象:<font style="background-color:rgb(187,191,196);">object</font>
  • <font style="background-color:rgb(187,191,196);">index</font>:是否索引

index为true时可对此字段搜索,并且如果type为text则会对文本内容进行分词

index为false表示不分词也不能搜索。

  • <font style="background-color:rgb(187,191,196);">analyzer</font>:添加索引时使用哪种分词器分词
  • <font style="background-color:rgb(187,191,196);">properties</font>:该字段的子字段
  • search_analyzer: 搜索时使用哪种分词器分词

通常情况下,我们在搜索和创建索引时使用的是同一分析,默认情况下搜索将会使用字段映射时定义的分析器,也能通过search_analyzer 设置不同的分词器。

2、创建索引

基本语法

  • 请求方式:<font style="background-color:rgb(187,191,196);">PUT</font>
  • 请求路径:<font style="background-color:rgb(187,191,196);">/索引名</font>,可以自定义
  • 请求参数:<font style="background-color:rgb(187,191,196);">mapping</font>映射

格式

PUT /索引名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

创建成功返回下边的结果:

{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "nihao"
}

举例:

PUT /索引名
{
  "mappings":{
  "properties": {
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "你好",
    "email": "zc@itcast.cn",
    "score": [99.1, 99.5, 98.9],
    "name": {
      "firstName": "云",`
      "lastName": "赵"
    }
  }
}
}

3、查询索引

基本语法

  • 请求方式:GET
  • 请求路径:/索引名
  • 请求参数:无 基本语法
  • 请求方式:GET
  • 请求路径:/索引名
  • 请求参数:无

格式

GET /索引名

4、修改索引

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引一旦创建,无法修改mapping

虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。因此修改索引能做的就是向索引中添加新字段,或者更新索引的基础属性。

语法说明

PUT /索引名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

5、删除索引

语法:

  • 请求方式:DELETE
  • 请求路径:/索引名
  • 请求参数:无

格式:

DELETE /索引名

二、文档操作

1、新增文档

语法:

POST /索引名/_doc/文档id
{
  "字段1": "值1",
  "字段2": "值2",
  "字段3": {
    "子属性1": "值3",
    "子属性2": "值4"
  },
}

2、查询文档

根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。

语法:

GET /{索引名称}/_doc/{id}

3、删除文档

删除使用DELETE请求,同样,需要根据id进行删除:

语法:

DELETE /{索引名}/_doc/id值

4、修改文档

修改有两种方式:

  • 全量修改:直接覆盖原来的文档
  • 局部修改:修改文档中的部分字段
4.1 全量修改

全量修改是覆盖原来的文档,其本质是两步操作:

  • 根据指定的id删除文档
  • 新增一个相同id的文档

注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。

语法:

PUT /{索引名}/_doc/文档id
{
  "字段1": "值1",
  "字段2": "值2",
  // ... 略
}
4.2 局部修改

局部修改是只修改指定id匹配的文档中的部分字段。

语法:

POST /{索引名}/_update/文档id
{
  "doc": {
    "字段名": "新的值",
  }
}

三、Java Client

1、创建连接

public class IndexTest {

    private ElasticsearchClient esClient;
    private ElasticsearchClient elasticsearchClient;
    private ElasticsearchTransport transport;
    private RestClient restClient;

    public void setUp(){
        //创建低级客户端
        restClient = RestClient.builder(new HttpHost("你的服务器ip", 9200)).build();

        //支持LocalDateTime序列化
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());

        // 使用 Jackson 映射器创建传输
        transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper(objectMapper));
        // 并创建 API 客户端
        esClient = new ElasticsearchClient(transport);
    }
}

2、引入依赖

在parent工程中添加依赖管理:

<properties>
  <es.version>7.17.7</es.version>
  <jackson.version>2.13.0</jackson.version>
  <jakarta.json-ai.version>2.0.1</jakarta.json-ai.version>
</properties>

<!-- 对依赖包进行管理 -->
<dependencyManagement>
  <dependencies>
    <!--es-->
    <dependency>
      <groupId>co.elastic.clients</groupId>
      <artifactId>elasticsearch-java</artifactId>
      <version>${es.version}</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.version}</version>
    </dependency>
    <dependency>
      <groupId>jakarta.json</groupId>
      <artifactId>jakarta.json-api</artifactId>
      <version>${jakarta.json-ai.version}</version>
    </dependency>
  </dependencies>
</dependencyManagement>

在children子工程添加如下依赖:

<dependency>
  <groupId>co.elastic.clients</groupId>
  <artifactId>elasticsearch-java</artifactId>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
  <groupId>jakarta.json</groupId>
  <artifactId>jakarta.json-api</artifactId>
</dependency>

3、Java Client操作增、删、改、查

首先创建索引

PUT /items
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "price":{
        "type": "integer"
      },
      "stock":{
        "type": "integer"
      },
      "image":{
        "type": "keyword",
        "index": false
      },
      "category":{
        "type": "keyword"
      },
      "brand":{
        "type": "keyword"
      },
      "sold":{
        "type": "integer"
      },
      "commentCount":{
        "type": "integer",
        "index": false
      },
      "isAD":{
        "type": "boolean"
      },
      "updateTime":{
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}
  1. 新增文档
/**
 * 新增文档
 * @throws IOException
 */
@Test
void testAddDocument() throws IOException {
    // 1.根据id查询商品数据
    Item item = itemService.getById(317578);
    // 2.转换为文档类型
    ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
    IndexResponse response = esClient.index(i -> i
            .index("items")//指定索引名称
            .id(itemDoc.getId())//指定主键
            .document(itemDoc)//指定文档对象
    );
    //结果
    String s = response.result().jsonValue();
    log.info("result:"+s);
}
  1. 查询文档
/**
 * 查询文档
 * @throws IOException
 */
@Test
void testGetDocument() throws IOException {
    GetResponse<ItemDoc> response = esClient.get(g -> g
            .index("items")
            .id("317578"),ItemDoc.class);
    log.warn(response.source().toString());
}
  1. 更新文档
/**
 * 更新文档
 * @throws IOException
 */
@Test
void testUpdateDocument1() throws IOException {
    ItemDoc itemDoc = new ItemDoc();
    itemDoc.setName("更新名称");
    UpdateResponse<ItemDoc> response = esClient.update(u -> u
            .index("items")
            .id("317578").
            doc(itemDoc), ItemDoc.class
    );
    log.warn(response.result().toString());
}

/**
 * 更新文档,有则更新,没有则新增文档
 * @throws IOException
 */
@Test
void testUpdateDocument2() throws IOException {
    ItemDoc itemDoc = new ItemDoc();
    itemDoc.setName("更新名称");
    UpdateResponse<ItemDoc> response = esClient.update(u -> u
            .index("items")
            .id("1111")
            .doc(itemDoc)
            .docAsUpsert(true), ItemDoc.class
    );
    log.warn(response.result().toString());
}

通过docAsUpsert(true)控制,如果没有该文档则添加新文档。

  1. 删除文档
/**
 * 删除文档
 * @throws IOException
 */
@Test
void testDelDocument() throws IOException {
    DeleteResponse response = esClient.delete(d -> d
            .index("items")
            .id("1111")
    );
    log.warn(response.result().toString());
}

4、批量导入

/**
 * 批量导入
 *
 * @throws IOException
 */
@Test
void tesBulkDocument() throws IOException {
    int pageNum = 1;
    int pageSize = 1000;
    while (true) {
        //1、查询数据库中的数据,每次查询5000条,循环导入
        Page<Item> page = Page.of(pageNum, pageSize);
        List<Item> items = itemService.page(page).getRecords();
        //如果为空则结束循环
        if (CollUtil.isEmpty(items)) {
            break;
        }
        //2、把获取到的数据转成List<ItemDoc>
        List<ItemDoc> itemDocs = BeanUtil.copyToList(items, ItemDoc.class);
    
        //3、创建BulkRequest对象
        BulkRequest.Builder builder = new BulkRequest.Builder();
    
        //4、遍历数据,想BulkRequest对象中插入,增加文档的对象
        itemDocs.forEach(itemDoc -> {
            //operations操作在这里便是增加操作
            builder.operations(b ->
                               b.index(i -> i
                                       .index("items")
                                       .id(itemDoc.getId())
                                       .document(itemDoc))
                              );
        });
        //这一行代码的作用是将通过 BulkRequest.Builder 构建的批量操作请求最终构建为一个 BulkRequest 对象。
        BulkRequest bulkRequest = builder.build();
    
        //5、让esClient去执行bulk操作【批量操作】
        esClient.bulk(bulkRequest);
        log.warn("导入了第" + pageNum + "页的数据");
        //页码自增
        pageNum ++;
    }
}

四、搜索

1、精确查询

精确查询,英文是Term-level query,顾名思义,词条级别的查询。也就是说不会对用户输入的搜索条件再分词,而是作为一个词条,与搜索的字段内容精确值匹配。因此推荐查找keyword、数值、日期、boolean类型的字段。

1.1 term精确查询
GET /items/_search
{
  "query": {
    "term": {
      "category": {
        "value": "拉杆箱"
      }
    }
  }
}
1.2 range精确查询
GET /items/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 500
      }
    }
  }
}
1.3 JavaClient精确查询
/**
 * 精确查找---term
 * @throws IOException
 */
@Test
void tesTermDocument() throws IOException {
    SearchResponse<ItemDoc> response = esClient.search(
        s -> s.index("items")
        .query(
            q -> q.term(
                t -> t.field("category").value("拉杆箱")
            )
        )
    
        , ItemDoc.class);
    List<Hit<ItemDoc>> hits = response.hits().hits();
    hits.forEach(h -> {
        ItemDoc source = h.source();
        log.info("查询到数据:{}", source);
    });
}

/**
 * 精确查找---range
 * @throws IOException
 */
@Test
void tesRangDocument() throws IOException {
    SearchResponse<ItemDoc> response = esClient.search(
        s -> s.index("items")
        .query(
            q -> q.range(
                r -> r.field("price")
                .gte(JsonData.of(100))
                .lte(JsonData.of(500))
            )
        )
        , ItemDoc.class);
    List<Hit<ItemDoc>> hits = response.hits().hits();
    hits.forEach(h -> {
            ItemDoc source = h.source();
            log.info("查询到数据:{}", source);
        }
    );
}

2、全文检索

2.1 match全文检索
#match
GET /items/_search
{
  "query": {
    "match": {
      "name": "早餐奶"
    }
  }
}
2.2 multi_match全文检索
#multi_match
GET /items/_search
{
  "query": {
    "multi_match": {
      "query": "德国进口牛奶",
      "fields": ["name","category"]
    }
  }
}
2.3 JavaClient全文检索
/**
 * 全文检索---match
 * @throws IOException
 */
@Test
void testMatchQuery() throws IOException {
    SearchResponse<ItemDoc> response = esClient.search(
            s -> s.index("items").query(
                    q -> q.match(
                            m -> m.field("name").query("早餐奶")
                    )
            )
            , ItemDoc.class
    );
    List<Hit<ItemDoc>> hits = response.hits().hits();
    long value = response.hits().total().value();
    log.info("查询到总数据:{}条", value);
    hits.forEach(h -> {
                ItemDoc source = h.source();
                log.info("查询到数据:{}", source);
            }
    );
}

/**
 * 全文检索---multi_match
 * @throws IOException
 */
@Test
void testMultiMatchQuery() throws IOException {
    SearchResponse<ItemDoc> response = esClient.search(
            s -> s.index("items").query(
                    q -> q.multiMatch(MultiMatchQuery.of(
                            mm -> mm.fields("name", "category")
                                    .query("德国进口牛奶")
                            )
                    )
            )
            , ItemDoc.class
    );
    List<Hit<ItemDoc>> hits = response.hits().hits();
    long value = response.hits().total().value();
    log.info("查询到总数据:{}条", value);
    hits.forEach(h -> {
                ItemDoc source = h.source();
                log.info("查询到数据:{}", source);
            }
    );
}

3、排序和分页

3.1 Elasticsearch语法:
GET /items/_search
{
  "query": {
    "multi_match": {
      "query": "德国进口牛奶",
      "fields": ["name","category"]
    }
  },
  "sort": [
    {"price": {"order": "desc"}},
    {"stock": {"order": "asc"}}
  ],
  "from": 0,
  "size": 20
}
3.2 JavaClient排序和分页:
/**
 * 排序和分页
 * @throws IOException
 */
@Test
void testSortPage() throws IOException {
    SearchResponse<ItemDoc> response = esClient.search(
            s -> s
                    .index("items")
                    .query(q ->
                            q.multiMatch(
                                MultiMatchQuery.of(
                                        mm -> mm.fields("name", "category").query("德国进口牛奶")
                                )
                            )
                    )
                    .sort(sort->sort.field(f->f.field("price").order(SortOrder.Desc)))
                    .sort(sort->sort.field(f->f.field("stock").order(SortOrder.Asc)))
                    .from(0)
                    .size(20)
            , ItemDoc.class
    );
    List<Hit<ItemDoc>> hits = response.hits().hits();
    long value = response.hits().total().value();
    log.info("查询到总数据:{}条", value);
    hits.forEach(h -> {
                ItemDoc source = h.source();
                log.info("查询到数据:{}", source);
            }
    );
}

4、复合查询

4.1 布尔查询

bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:

  • must:必须匹配每个子查询,类似“与”
  • should:选择性匹配子查询,类似“或”
  • must_not:必须不匹配,不参与算分,类似“非”
  • filter:必须匹配,不参与算分,尽量使用filter
#复合查询
GET /items/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"name": "手机"}}
      ],
      "should": [
        {"term": {"brand": { "value": "vivo" }}},
        {"term": {"brand": { "value": "小米" }}}
      ],
      "must_not": [
        {"range": {"price": {"gte": 250000}}}
      ],
      "filter": [
        {
          "range": {
            "price": {
              "lte": 200000
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "brand": {
        "order": "desc"
      }
    }
  ]
}
4.2 JavaClient布尔查询
/**
 * 复合查询
 * @throws IOException
 */
@Test
public void testBollQuery() throws Exception{
    SearchResponse<ItemDoc> response = esClient.search(s ->
                    s.index("items")
                            .query(q -> q.bool(b -> b
                                    .must(m -> m.match(mm -> mm.field("name").query("手机")))
                                    .should(s1 -> s1.term(t -> t.field("brand").value("vivo")))
                                    .should(s1 -> s1.term(t -> t.field("brand").value("小米")))
                                    .mustNot(mn -> mn.range(t -> t.field("price").gte(JsonData.of(250000))))
                                    .filter(f -> f.range(t -> t.field("price").lte(JsonData.of(200000))))
                            ))
            , ItemDoc.class);
    List<Hit<ItemDoc>> hits = response.hits().hits();
    long value = response.hits().total().value();
    log.info("查询到总数据:{}条", value);
    hits.forEach(h -> {
                ItemDoc source = h.source();
                log.info("查询到数据:{}", source);
            }
    );
}

5、高亮显示

5.1 Elasticsearch语法:
GET /{索引库名}/_search
{
  "query": {
    "match": {
      "搜索字段": "搜索关键字"
    }
  },
  "highlight": {
    "fields": {
      "高亮字段名称": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}
5.2 JavaClient高亮显示:
/**
 * 高亮显示
 * @throws Exception
 */
@Test
public void testHighLight() throws Exception {
    SearchResponse<ItemDoc> response = esClient.search(s ->
                    s.index("items")
                            .query(q -> q.match(m -> m.field("brand").query("小米")))
                            .highlight(h -> h.fields("name", f -> f)
                                    .preTags("<b style='color:red;'>")
                                    .postTags("</b>")
                                    .requireFieldMatch(false))
            , ItemDoc.class);
    long total = response.hits().total().value();
    System.out.println("总条数" + total);
    List<Hit<ItemDoc>> hits = response.hits().hits();
    //解析出高亮结果
    for (Hit<ItemDoc> hit : hits) {
        ItemDoc itemDoc = hit.source();
        Map<String, List<String>> highlightFields = hit.highlight();
        if (highlightFields != null && highlightFields.containsKey("name")) {
            //只关心name字段的高亮
            List<String> name = highlightFields.get("name");
            if (name != null && !name.isEmpty()) {
                String highlightName = name.get(0);
                itemDoc.setName(highlightName);
                System.out.println(itemDoc);
            }
        }
    }
}
posted @ 2025-03-23 18:25  厂长是我表哥  阅读(79)  评论(0)    收藏  举报