微服务开发与实战Day08 - Elasticsearch

news2024/11/28 7:23:46

一、初始Elasticsearch

高性能分布式搜索引擎

1. 认识和安装

1.1 认识

Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:Apache Lucene - Welcome to Apache Lucene

Lucene的优势:

  • 易扩展
  • 高性能(基于倒排索引)

2004年Shay Banon基于Lucene开发了Compass

2010年Shay Banon重写了Compas,取名为Elasticsearch

官网地址:Elastic — The Search AI Company | Elastic ,目前最新的版本是8.x.x

elasticsearch具备下列优势:

  • 支持分布式,可水平扩展
  • 提高Restful接口,可被任何语言调用

elasticsearch结合kibana、Logstash、Beats,是一整套技术栈,被叫作ELK。被广泛应用在日志数据分析、实时监控等领域。整套技术栈的核心就是用来存储搜索计算的Elasticsearch。

Kibana是elastic公司提供的用于操作Elasticsearch的可视化控制台。它的功能非常强大,包括:

  • 对Elasticsearch数据的搜索、展示

  • 对Elasticsearch数据的统计、聚合,并形成图形化报表、图形

  • 对Elasticsearch的集群状态监控

  • 它还提供了一个开发控制台(DevTools),在其中对Elasticsearch的Restful的API接口提供了语法提示

1.2 安装

步骤:

①上传课前资料提供的两个镜像文件到虚拟机root/根目录

②加载镜像

docker load -i es.tar
docker load -i kibana.tar

加载完成后把tar包删除,防止占用太多空间

rm -rf *.tar

③安装并运行单机版本的elasticsearch

docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1

④查看elasticsearch的运行日志

docker logs -f es

Ctrl + C退出

⑤访问测试

http://192.168.126.151:9200/

⑥安装部署Kibana

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1

⑦查看kibana的运行日志

docker logs -f kibana

⑧访问测试

http://192.168.126.151:5601/app/home#/

使用Dev tools

2. 倒排索引

传统数据库(如MySQL)采用正向索引,例如给下表(tb-goods)中的id创建索引:

综上,根据id精确匹配时,可以走索引,查询效率较高。而当搜索条件为模糊匹配时,由于索引无法生效,导致从索引查询退化为全表扫描,效率很差。

因此,正向索引适合于根据索引字段的精确搜索,不适合基于部分词条的模糊匹配。

而倒排索引恰好解决的就是根据部分词条模糊匹配的问题。

2.1 elasticsearch采用倒排索引

  • 文档(document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
  • 词条(term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:小米手机,就可以分为:小米、手机这样的词条。

创建倒排索引就是对正向索引的一种特殊处理和应用,流程如下:

  • 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息。
  • 因为词条唯一性,可以给词条创建正向索引

此时形成的这张以词条为索引的表,就是倒排索引表,两者对比如下:

倒排索引的搜索流程如下(以搜索“华为手机”为例),如下图:

流程描述:

  • 用户输入条件“华为手机”进行搜索
  • 对用户输入条件分词,得到词条:华为、手机
  • 拿着词条在倒排索引中查找(由于词条有索引,查询效率很高),即可得到包含词条的文档id:1、2、3
  • 拿着文档id正向索引中查找具体文档即可(由于id也有索引,查询效率也很高)

虽然要先查询倒排索引,再查询正向索引,但是无论是词条、还是文档id都建立了索引,查询速度非常快,无需全表扫描

2.2 正向和倒排索引

正向索引

正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程。

优点:

  • 可以给多个字段创建索引
  • 根据索引字段搜索、排序速度非常快

缺点:

  • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。

倒排索引

倒排索引,是先找到用户要搜索的词条,根据词条得到包含词条的文档的id,然后根据id获取文档,是根据词条找文档的过程。

优点:

  • 根据词条搜索、模糊搜索时,速度非常快

缺点:

  • 只能给词条创建索引,而不是字段
  • 无法根据字段做排序

3. IK分词器

中文分词往往需要根据语义分析,比较复杂,这就需要用到中文分词器,例如IK分词器。IK分词器是林良益在2006年开源发布的,其采用的正向迭代最细粒度切分算法一直沿用至今。

https://github.com/infinilabs/analysis-ik

3.1 按照IK分词器

方式一:在线安装

①运行命令下载

docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

②重启es容器

docker restart es

方式二:离线安装

①查看之前安装的Elasticsearch容器的plugins数据卷目录:

docker volume inspect es-plugins

②把课前资料里提供的ik分词器插件上传到 /var/lib/docker/volumes/es-plugins/_data 目录下

cd /var/lib/docker/volumes/es-plugins/_data

注意,此处是把压缩包解压并重命名为ik再上传,不能直接上传资料里提供的ik文件夹,缺少了东西

③重启es容器

docker restart es

④查看es运行日志

docker logs -f es

3.2 使用IK分词器

①在Kibana的DevTools中可以使用下面的语法来测试IK分词器:

POST /_analyze
{
  "analyzer": "standard",
  "text": "黑马程序员学习java太棒了"
}

语法说明:

  • POST:请求方式
  • /_analyze:请求路径,这里省略了http://192.168.126.151:9200,有kibana帮我们补充
  • 请求参数,json风格:
  • analyzer:分词器类型,这里是默认的standard分词器
  • text:要分词的内容

结果如下:

{
  "tokens" : [
    {
      "token" : "黑",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "马",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "程",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "序",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "学",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "习",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<IDEOGRAPHIC>",
      "position" : 6
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "太",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<IDEOGRAPHIC>",
      "position" : 8
    },
    {
      "token" : "棒",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "<IDEOGRAPHIC>",
      "position" : 9
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "<IDEOGRAPHIC>",
      "position" : 10
    }
  ]
}

可以看到,标准分词器智能1字1词条,无法正确对中文做分词。

②使用IK分词器:ik_smart,智能切分,粗粒度

POST /_analyze
{
  "analyzer": "ik_smart",
  "text": "黑马程序员学习java太棒了"
}

结果如下:

{
  "tokens" : [
    {
      "token" : "黑马",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "学习",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "太棒了",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 4
    }
  ]
}

③ik_max_word:最细切分,细粒度IK分词器

POST /_analyze
{
  "analyzer": "ik_max_word",
  "text": "黑马程序员学习java太棒了"
}

结果如下:

{
  "tokens" : [
    {
      "token" : "黑马",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "程序员",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "程序",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "员",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "学习",
      "start_offset" : 5,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "java",
      "start_offset" : 7,
      "end_offset" : 11,
      "type" : "ENGLISH",
      "position" : 5
    },
    {
      "token" : "太棒了",
      "start_offset" : 11,
      "end_offset" : 14,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "太棒",
      "start_offset" : 11,
      "end_offset" : 13,
      "type" : "CN_WORD",
      "position" : 7
    },
    {
      "token" : "了",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "CN_CHAR",
      "position" : 8
    }
  ]
}

3.3 拓展词典

随着互连网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如“泰酷辣”、“传智播客”等。

IK分词器无法对这些词汇分词,测试一下:

POST /_analyze
{
  "analyzer": "ik_max_word",
  "text": "传智播客开设大学,真的泰裤辣!"
}

结果如下:

{
  "tokens" : [
    {
      "token" : "传",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "智",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "播",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "客",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "开设",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "大学",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "真的",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "泰",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "CN_CHAR",
      "position" : 7
    },
    {
      "token" : "裤",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "CN_CHAR",
      "position" : 8
    },
    {
      "token" : "辣",
      "start_offset" : 13,
      "end_offset" : 14,
      "type" : "CN_CHAR",
      "position" : 9
    }
  ]
}

IK分词器允许我们配置拓展词典来增加自定义的词库:

步骤①:利用config目录的IKAnalyzer.cfg.xml文件添加拓展词典

打开IK分词器config目录,在IKAnalyzer.cfg.xml配置文件内容添加如下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">ext.dic</entry>
</properties>

②在词典中添加拓展词条

在IK分词器的config目录新建一个ext.dic,添加如下内容

传智播客
泰裤辣

③重启elasticsearch

docker restart es

# 查看 日志
docker logs -f es

④再次测试

{
  "tokens" : [
    {
      "token" : "传智播客",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "开发",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "全日制",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "大学",
      "start_offset" : 9,
      "end_offset" : 11,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "这简直",
      "start_offset" : 12,
      "end_offset" : 15,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "泰酷辣",
      "start_offset" : 15,
      "end_offset" : 18,
      "type" : "CN_WORD",
      "position" : 5
    }
  ]
}

4. 基础概念

4.1 文档和字段

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式存储在elasticsearch中:

因此,原本数据库中的一行数据就是ES中的一个JSON文档,而数据库中每行数据都包含很多列,这些列就转换为JSON文档中的字段(Field)

4.2 索引和映射

随着业务发展,需要在es中存储的文档也会越来越多,比如有商品的文档、用户的文档、订单的文档等等:

所有文档都散乱存放显然非常混乱,也不方便管理。因此,我们要将类型相同的文档集中在一起管理,称为索引(Index),例如:

  • 所有用户文档,就可以组织在一起,称为用户的索引;
  • 所有商品的文档,可以组织在一起,称为商品的索引;
  • 所有订单的文档,可以组织在一起,称为订单的索引;

因此,我们可以把索引当作数据库中的表。数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

4.3 msql与elasticsearch对比

MySQLElasticsearch说明
TableIndex索引(Index),就是文档的集合,类似于数据库的表(Table)
RowDocument文档(Document),就是一条条的数据,类似于数据库中的行(Row),文档都是JSON
ColumnField字段(Field),就是JSON文档中的字段,类似于数据库中的列(Column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似于数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

  • MySQL:擅长事务类型操作,可以确保数据的安全和一致性
  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此,在企业中往往是两者结合使用:

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

二、索引库操作

1. Mapping映射属性

mapping是对索引库中文档的约束,常见的mapping属性包括:

type:字段数据类型,常见的简单类型有:

  • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
  • 数值:long、integer、short、byte、double、float
  • 布尔:boolean
  • 日期:date
  • 对象:object

index:是否创建索引,默认为true

analyzer:使用哪种分词器

properties:该字段的子字段

例如下面的json文档:

{
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

对应的每个字段映射(Mapping):

字段名

字段类型

类型说明

是否

参与搜索

是否

参与分词

分词器

age

integer

整数

——

weight

float

浮点数

——

isMarried

boolean

布尔

——

info

text

字符串,但需要分词

IK

email

keyword

字符串,但是不分词

——

score

float

只看数组中元素类型

——

name

firstName

keyword

字符串,但是不分词

——

lastName

keyword

字符串,但是不分词

——

2. 索引库的CRUD

Elasticsearch提供的所有API都是Restful的接口,遵循Restful的基本规范:

接口类型请求方式请求路径请求参数
查询用户GET/users/{id}路径中的id
新增用户POST/usersjson格式user对象
修改用户PUT/users/{id}

路径中的id

json格式对象

删除用户DELETE/users/{id}路径中的id

2.1 创建索引库和映射

基本语法:

  • 请求方式:PUT
  • 请求路径:/索引库名,可以自定义
  • 请求参数:mapping映射

格式:

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

示例:

PUT /heima
{
  "mappings": {
    "properties": {
      "info": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "age": {
        "type": "byte"
      },
      "email": {
        "type": "keyword",
        "index": false
      },
      "name": {
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

2.2 查询索引库

基本语法:

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

格式:

GET /索引库名

示例:

GET /heima

2.3 修改索引库

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

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

语法:

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

示例:

PUT /heima/_mapping
{
  "properties": {
    "age":{
      "type": "integer"
    }
  }
}

2.4 删除索引库

语法:

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

格式:

DELETE /索引库名

示例:

DELETE /heima

3. 文档操作

3.1 新增操作  

语法:

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

示例:

POST /heima/_doc/1
{
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

响应:

3.2 查询操作

语法:

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

示例:

GET /heima/_doc/1

响应结果:

3.3 删除操作

语法:

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

示例:

DELETE /heima/_doc/1

响应结果:

3.4 修改文档

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

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

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

语法:

PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

示例:

PUT /heima/_doc/1
{
    "info": "黑马程序员高级Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

由于id1的文档已经被删除,所以第一次执行时,得到的反馈是created

所以如果执行第2次时,得到的反馈则是updated

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

语法:

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

示例:

POST /heima/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

执行结果:

3.5 批处理

Elasticsearch中允许通过一次请求中携带多次文档操作,也就是批量处理,语法格式如下:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

其中:

index 代表新增操作

  • _index:指定索引库名
  • _id:指定要操作的文档id
  • { "field1": "value1" }:要新增的文档内容

delete 代表删除操作

  • _index:指定索引库名
  • _id:指定要操作的文档id

update 代表更新操作

  • _index:指定索引库名
  • _id:指定要操作的文档id
  • { "doc": {"field2" : "value2"} }:要更新的文档字段

示例:批量新增

POST /_bulk
{"index": {"_index":"heima", "_id": "3"}}
{"info": "黑马程序员C++讲师", "email": "ww@itcast.cn", "name":{"firstName": "五", "lastName":"王"}}
{"index": {"_index":"heima", "_id": "4"}}
{"info": "黑马程序员前端讲师", "email": "zhangsan@itcast.cn", "name":{"firstName": "三", "lastName":"张"}}

示例:批量删除

POST /_bulk
{"delete":{"_index":"heima", "_id": "3"}}
{"delete":{"_index":"heima", "_id": "4"}}

4. JavaRestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。

Elasticsearch目前最新版本是8.14,其Java客户端有很大变化。不过大多数企业使用的还是8以下版本,所以我们选择使用早期的JavaRestClient客户端来学习。

官方文档地址:Elasticsearch Clients | Elastic

然后选择7.12版本,HighLevelRestClient版本:

Java High Level REST Client | Java REST Client [7.12] | Elastic

4.1 客户端初始化

在elasticsearch提供的API中,与elasticsearch一切交互都封装在一个名为RestHighLevelClient的类中,必须先完成这个对象的初始化,建立与elasticsearch的连接。

步骤:

①在item-service模块引入es的RestHignLevelClient依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

②因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本:

在父工程hmall的pom.xml

  <properties>
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <elasticsearch.version>7.12.1</elasticsearch.version>
  </properties>

③初始化RestHighLevelClient

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
        HttpHost.create("http://192.168.126.151:9200")
));

这里为了单元测试方便,我们创建一个测试类ElasticTest,然后将初始化的代码编写在@BeforeEach方法中:

package com.hmall.item.es;


import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class ElasticTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.126.151:9200")
        ));
    }

    @Test
    void testConnect() {
        System.out.println(client);
    }

    @AfterEach
    void tearDown() throws IOException {
        if(client != null) {
            client.close();
        }
    }
}

4.2 创建索引库

4.2.1 商品Mapping映射

由于要实现对商品搜索,所以我们需要将商品添加到Elasticsearch中,不过需要根据搜索业务的需求来设定索引库结构,而不是一股脑的把MySQL数据写入Elasticsearch.

搜索页码的效果如下图:

结合数据库表结构,对应的mapping映射属性如下:

字段名

字段类型

类型说明

是否

参与搜索

是否

参与分词

分词器

id

long

长整数

——

name

text

字符串,参与分词搜索

IK

price

integer

以分为单位,所以是整数

——

stock

integer

字符串,但需要分词

——

image

keyword

字符串,但是不分词

——

category

keyword

字符串,但是不分词

——

brand

keyword

字符串,但是不分词

——

sold

integer

销量,整数

——

commentCount

integer

评价,整数

——

isAD

boolean

布尔类型

——

updateTime

Date

更新时间

——

因此,最终的索引库文档结构应是这样的:

PUT /items
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "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"
      }
    }
  }
}

4.2.2 创建索引库

创建索引库dJavaAPI与Restful接口API对比

代码分为三步:

  • ①创建Request对象:因为是创建索引库的操作,因此Request是CreateIndexRequest
  • ②添加请求参数:其实就是Json格式的Mapping映射参数。因为json字符串很长,这里定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅
  • ③发送请求:client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。例如创建索引、删除索引、判断索引是否存在等。

在item-service中的ElasticTest测试类中,添加如下:

package com.hmall.item.es;


import org.apache.http.HttpHost;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;

import java.io.IOException;

public class ElasticTest {
    private RestHighLevelClient client;

    @Test
    void testCreateIndex() throws IOException {
        // 1.创建Request对象
        CreateIndexRequest request = new CreateIndexRequest("items");
        // 2.准备请求参数
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        // 3.发送请求
        client.indices().create(request, RequestOptions.DEFAULT);
    }

    private static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_max_word\"\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"stock\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"image\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"category\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"sold\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"commentCount\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"isAD\":{\n" +
            "        \"type\": \"boolean\"\n" +
            "      },\n" +
            "      \"updateTime\":{\n" +
            "        \"type\": \"date\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

如果之前创建了ittems索引库,记得删除

# 删除索引库
DELETE /items

4.3 删除索引库

import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;

// ... ...

@Test
void testDeleteIndex() throws IOException {
    // 1.创建Request对象
    DeleteIndexRequest request = new DeleteIndexRequest("items");
    // 2.发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

代码分为三步:

  • ①创建Request对象,是DeleteIndexRequest对象
  • ②准备参数。这里是无参,因此省略
  • ③发送请求,改用delete方法

4.4 判断索引库是否存在

判断索引库是否存在,本质就是查询。

import org.elasticsearch.client.indices.GetIndexRequest;
// ... ...
@Test
void testExistsIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("items");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

代码分为三步:

①创建Request对象,是GetIndexRequest对象

②准备参数。这里是无参,直接省略

③发送请求。用exists方法

5. RestClient操作文档

5.1 新增文档

 5.1.1 API语法

新增文档的请求语法如下:

POST /{索引库名}/_doc/1
{
    "name": "Jack",
    "age": 21
}

对应的JavaAPI如下:

与索引库操作的API非常类似,同样是三步走:

  • ①创建request对象,这里是IndexRequest,因为添加文档就是创建倒排索引的过程
  • ②准备请求参数,本例中就是Json文档
  • ③发送请求

变化的地方在于,这里直接使用client.xxx()的API,不再需要client.indices()

5.1.2 实体类

索引库结构与数据库结构存在一些差异,因此我们需要定义一个索引库结构对应的实体。

在item-service模块的com.hmall.item.domain.dto包中定义一个新的DTO:

package com.hmall.item.domain.po;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@ApiModel(description = "索引库实体")
public class ItemDoc{

    @ApiModelProperty("商品id")
    private String id;

    @ApiModelProperty("商品名称")
    private String name;

    @ApiModelProperty("价格(分)")
    private Integer price;

    @ApiModelProperty("商品图片")
    private String image;

    @ApiModelProperty("类目名称")
    private String category;

    @ApiModelProperty("品牌名称")
    private String brand;

    @ApiModelProperty("销量")
    private Integer sold;

    @ApiModelProperty("评论数")
    private Integer commentCount;

    @ApiModelProperty("是否是推广广告,true/false")
    private Boolean isAD;

    @ApiModelProperty("更新时间")
    private LocalDateTime updateTime;
}

5.1.3 完整代码

我们导入商品数据,除了参考API模板“三步走”以外,还需要做几点准备工作:

  • 商品数据来源于数据库,我们需要先查询处出来,得到Item对象;
  • Item对象需要转为ItemDoc对象;
  • ItemDTO需要序列化为json格式

在item-service里添加单元测试类ElasticDocumentTest

package com.hmall.item.es;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.service.IItemService;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;

@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {
    private RestHighLevelClient client;
    @Autowired
    private IItemService itemService;

    @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.126.151:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        if(client != null) {
            client.close();
        }
    }

    @Test
    void testIndexDoc() throws IOException {
        // 1. 准备文档数据
        // 1.1 根据id查询数据库数据
        Item item = itemService.getById(584391L);
        // 1.2 把数据库数据转为文档数据
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);
        // 2. 准备Request
        IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
        // 3. 准备请求参数
        request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
        // 4. 发送请求
        client.index(request, RequestOptions.DEFAULT);
    }
}

查询文档

GET /items/_doc/584391

结果:

{
  "_index" : "items",
  "_type" : "_doc",
  "_id" : "584391",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "id" : "584391",
    "name" : "美旅AmericanTourister拉杆箱 商务男女超轻PP行李箱时尚大容量耐磨飞机轮旅行箱 20英寸海关锁DL7灰色",
    "price" : 29900,
    "image" : "https://m.360buyimg.com/mobilecms/s720x720_jfs/t1/22734/21/2036/130399/5c18af2aEab296c01/7b148f18c6081654.jpg!q70.jpg.webp",
    "category" : "拉杆箱",
    "brand" : "美旅箱包",
    "sold" : 0,
    "commentCount" : 0,
    "isAD" : false,
    "updateTime" : 1556640000000
  }
}

5.2 查询文档

5.2.1 语法说明

查询的请求语句如下:

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

流程:

  • ①创建Request对象,这次是查询,所是GetRequest
  • ②准备请求参数,这里是无参,直接省略
  • ③发送请求,得到结果。因为是查询,这里调用client.get()方法
  • ④解析结果,就是对JSON做反序列化

5.2.2 完整代码
@Test
void testGetDocumentById() throws IOException {
    // 1.准备Request对象
    GetRequest request = new GetRequest("items").id("584391");
    // 2.发送请求
    GetResponse response = client.get(request, RequestOptions.DEFAULT);
    // 3.获取响应结果中的source
    String json = response.getSourceAsString();
    
    ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);
    System.out.println("itemDoc= " + ItemDoc);
}

5.3 修改文档

修改有两种方式:

  • 全量修改:本质就是先根据id删除,再新增
  • 局部修改:修改文档中的指定字段值

在RestClient的API中,全量修改与新增的API完全一致,判断依据是ID:

  • 如果新增时,ID已经存在,则修改
  • 如果新增时,ID不存在,则新增

5.3.1 语法说明

局部修改的请求语法如下:

POST /{索引库名}/_update/{id}
{
  "doc": {
    "字段名": "字段值",
    "字段名": "字段值"
  }
}

流程:

  • ①准备Request对象。这次是修改,所以是UpdateRequest
  • ②准备参数。也就是JSON文档,里面包含要修改的字段
  • ③更新文档。这里调用client.update()方法。
5.3.2 完整代码
@Test
void testUpdateDocument() throws IOException {
    // 1.准备Request
    UpdateRequest request = new UpdateRequest("items", "584391");
    // 2.准备请求参数
    request.doc(
            "price", 58800,
            "commentCount", 1
    );
    // 3.发送请求
    client.update(request, RequestOptions.DEFAULT);
}

5.4 删除文档

5.4.1 API语法

删除的请求语句如下:

DELETE /hotel/_doc/{id}

流程:

  • ①准备Request对象,因为是删除,是DeleteRequest对象。要指定索引库名和id
  • ②准备参数,无参,直接省略
  • ③发送请求。因为是删除,所以是Client.delete()方法

在item-service的ElasticDocumentTest测试类中,编写单元测试:

@Test
void testDeleteDocument() throws IOException {
    // 1.准备Request,两个参数,第一个是索引库名,第二个是文档id
    DeleteRequest request = new DeleteRequest("items", "584391");
    // 2.发送请求
    client.delete(request, RequestOptions.DEFAULT);
}

5.5 批量导入文档

5.5.1 语法说明

流程:

  • ①创建Request对象,这次用的是BulkRequest
  • ②准备请求参数
  • ③发送请求,这次要用到client.bulk()方法

BulkRequest本身其实并没有请求参数,其本质就是将多个普通的CRUD请求组合在一起发送。例如:

  • 批量新增文档,就是给每个文档创建一个IndexRequest请求,然后封装到BulkRequest中,一起发出。
  • 批量删除,就是创建N个DeleteRequest请求,然后封装到BulkRequest,一起发出。

因此,BulkRequest中提供了add方法,用以添加其他CRUD的请求:

因此,Bulk中添加了多个IndexRequest,就是批量新增功能了。示例:

@Test
void testBulk() throws IOException {
    // 1.创建Request
    BulkRequest request = new BulkRequest();
    // 2.准备请求参数
    request.add(new IndexRequest("items").id("1").source("json doc1", XContentType.JSON));
    request.add(new IndexRequest("items").id("2").source("json doc2", XContentType.JSON));
    // 3.发送请求
    client.bulk(request, RequestOptions.DEFAULT);
}

5.5.2 完整代码
    @Test
    void testBulkIndexDoc() throws IOException {
        int pageNo = 1, pageSize = 500;
        while (true) {
            // 1. 准备文档数据
            Page<Item> page = itemService.lambdaQuery()
                    .eq(Item::getStatus, 1)
                    .page(Page.of(pageNo, pageSize));
            List<Item> records = page.getRecords();
            if(records == null || records.isEmpty()) {
                return;
            }
            // 2. 准备Request对象
            BulkRequest request = new BulkRequest();
            // 3. 准备请求参数
            for (Item item : records) {
                request.add(new IndexRequest("items")
                        .id(item.getId().toString())
                        .source(JSONUtil.toJsonStr(BeanUtil.copyProperties(item, ItemDoc.class)), XContentType.JSON));
            }
            // 4. 发送请求
            client.bulk(request, RequestOptions.DEFAULT);
            // 5. 翻页
            pageNo++;
        }
    }
GET /items/_count

如果Page没有of()方法的,在父工程的pom.xml中把mybatisPlus的版本改成3.4.3(JDK11)或以上,或者把分页查询的代码改成如下:

Page<Item> page = itemService.lambdaQuery()
        .eq(Item::getStatus, 1)
        .page(new Page<>(pageNo, pageSize));

云文档地址:Docs

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

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

相关文章

誉天教育近期开班计划(6月15日更新)

云计算HCIP 周末班 2024/6/15 田老师 售前IP-L3 周末班 2024/6/15 陈老师 RHCA442 晚班 2024/6/17邹老师 数通HCIE 晚班 2024/6/24阮老师 云计算HCIE直通车晚班 2024/6/25 曾老师 售前IT-L3 周末班 2024/6/29 伍老师 数通HCIP 晚班 2024/7/1杨老师 存储直通车 晚班 2024/7/1 高…

【ARMv8/ARMv9 硬件加速系列 3 -- SVE 指令语法及编译参数详细介绍】

文章目录 SVE 汇编语法SVE 单通道谓词SVE 测试代码 SVE 软件和库支持SVE 编译参数配置-marcharmv8-alseprofilememtagsve2-aessve2-bitpermcryptosve2sve2-sha3sve2-sm4 SVE 汇编语法 在介绍 SVE 汇编指令语法之前&#xff0c;先介绍下如何判断自己所使用的芯片是否实现了SVE功…

算法01 递推算法及相关问题详解【C++实现】

目录 递推的概念 训练&#xff1a;斐波那契数列 解析 参考代码 训练&#xff1a;上台阶 参考代码 训练&#xff1a;信封 解析 参考代码 递推的概念 递推是一种处理问题的重要方法。 递推通过对问题的分析&#xff0c;找到问题相邻项之间的关系&#xff08;递推式&a…

践行国产化替代,优刻得私有云勇当先锋

编辑&#xff1a;阿冒 设计&#xff1a;沐由 阳泉&#xff0c;十万火急&#xff01; 位于太行山西麓的山西省阳泉市&#xff0c;是一座历史悠久、底蕴深厚、资源丰富的名城&#xff0c;拥有超百万常住人口&#xff0c;国内生产总值在2022年成功跨越千亿元大关。然而&#xff0c…

leetcode 56合并区间

思路 合并就是首先应该按照left左边界排序&#xff0c;排完序以后&#xff0c;如果i的左边界小于等于i-1的右边界&#xff0c;说明有重合&#xff0c;此时这两个可以合并&#xff0c;右边界应该取最大值。 代码 排序 我是定义了一个类,存储左右边界&#xff0c;先将数组转化…

传输层udp和tcp协议格式

UDP协议 UDP协议端格式 udp的前八个字节是报头&#xff0c;后面部分就是有效载荷。而目的端口号就保证了udp向应用层交付的问题。 而针对于报头和有效载荷分离是根据固定八字结的报头长度。数据的长度就是取决于报头中udp长度字段的大小来确定udp报文长度&#xff0c;因此也可…

【Matlab编程学习】 | matlab语言编程基础:常用图形绘制基础学习

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

C++面向对象程序设计 - 函数库

C语言程序中各种功能基本上都是由函数来实现的&#xff0c;在C语言的发展过程中建立了功能丰富的函数库&#xff0c;C从C语言继承了些函数功能。如果要用函数库中的函数&#xff0c;就必须在程序文件中包含文件中有关的头文件&#xff0c;在不同的头文件中&#xff0c;包含了不…

解决Unity-2020 安卓异形屏黑边

背景 Unity 2020.3.17 版本开发的游戏&#xff0c;打apk包&#xff0c;发现两个问题 如图下午所示&#xff0c;实体白色导航栏&#xff0c;阻挡了整个安卓UI界面&#xff0c;难看还影响美观。 安卓系统 12-13 版本手机&#xff0c;异形屏。一侧安全区黑边遮挡&#xff0c;占空间…

pyinstall打包exe报错

1- 报错 Please install pywin32-ctypes. 前提&#xff1a;python安装路径中已经安装了pywin32-ctypes。 运行pyinstaller报错 PyInstaller cannot check for assembly dependencies. Please install pywin32-ctypes. 解决思路&#xff1a; python安装路径下Lib\site-packa…

远程连接路由器:方法大全与优缺点解析

远程连接路由器的方式主要有以下几种&#xff0c;以下是每种方式的详细说明及其优缺点&#xff1a; 使用Web浏览器登录 方法&#xff1a;通过配置路由器的远程管理功能&#xff0c;允许用户通过互联网浏览器访问路由器的管理界面。用户只需输入路由器的公网IP地址或域名&#…

JavaSE 面向对象程序设计 包装类 纯理论详解以及相关综合练习

包装类 实质 基本数据类型对应的引用数据类型 把基本数据类型变成对象 创建对象后 在栈内存里开辟空间 在堆内存里开辟空间 成员变量记录数值 栈内存记录对象的地址 包装类就是创建一个对象&#xff0c;对象记录相应的数据值 用一个对象把数据包装起来 作用 Java中万…

[leetcode]将二叉搜索树转化为排序的双向链表

. - 力扣&#xff08;LeetCode&#xff09; /* // Definition for a Node. class Node { public:int val;Node* left;Node* right;Node() {}Node(int _val) {val _val;left NULL;right NULL;}Node(int _val, Node* _left, Node* _right) {val _val;left _left;right _rig…

新火种AI|苹果终于迈进了AI时代,是创新还是救赎?

作者&#xff1a;一号 编辑&#xff1a;美美 苹果的AI战略&#xff0c;能够成为它的救命稻草吗&#xff1f; 苹果&#xff0c;始终以其独特的创新能力引领着行业的发展方向。在刚结束不久的2024年的全球开发者大会&#xff08;WWDC&#xff09;上&#xff0c;苹果再次证明了…

iSlide软件下载附加详细安装教程

​iSlide 是一款基于 PPT 的插件工具&#xff0c;包含 52 个设计辅助功能&#xff0c;9 大在线资源库&#xff0c;超 50 万专业 PPT 模板/素材 支持 macOS 和 Windows 系统&#xff08;兼容 Office 和 WPS&#xff09;。 可以对一组元素&#xff08;文本框&#xff0c;图形&…

制作自己的 @OnClick、@OnLongClick(告别 setOnClickListener,使用注解、反射和动态代理)

前言 前面我们说过 ButterKnife 这个库&#xff0c;这个库实现不仅实现了 View 的绑定&#xff0c;而且还提供了大量的注解如 BindView、OnClick、OnLongClick 等来简化开发过程中事件绑定。而这些功能的实现是通过 APT 也就是注解处理器&#xff0c;在编译期间生成 Java 代码…

GStreamer——教程——基础教程2:GStreamer concepts

基本教程2&#xff1a;GStreamer概念 1&#xff0c;目标 之前的教程展示了如何自动构建管道。现在我们将手动构建一条pipeline&#xff1a;初始化每一个element并将它们连接起来。在此过程中&#xff0c;我们将学习&#xff1a; 什么是GStreamer元素以及如何创建一个。 如何…

redis设计与实现(五)RDB与AOF持久化

RDB持久化 因为Redis是内存数据库&#xff0c;它将自己的数据库状态储存在内存里面&#xff0c;所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中的数据库状态也会消失不见。 为了解决这个问题&#xff0c;…

CC2500和CC1101移植说明

主要通过如何移植、移植注意、关于芯片配置、如何生成导出配置四大步骤来说明CC2500和CC1101移植 首先通过下图1这个宏进行选择 如何移植 要移植的部分在 CC2500_hal.c 和 CC2500_hal.h中, 搜索 "//移植" 就可以定位到 库 所需的依赖, 需要根据 您的环境实现这些…

sap怎么批量给信息记录打上删除标识

1.MEMASSIN-----事务代码 2.选择完成字段 3.根据条件查询需要冻结的信息记录 4.输入查询条件 5.全部勾选完成标识&#xff0c;点击保存&#xff0c;即可冻结完成