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"
}
}
}
}
- 新增文档
/**
* 新增文档
* @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);
}
- 查询文档
/**
* 查询文档
* @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());
}
- 更新文档
/**
* 更新文档
* @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)控制,如果没有该文档则添加新文档。
- 删除文档
/**
* 删除文档
* @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);
}
}
}
}
浙公网安备 33010602011771号