SpringBoot整合ES

ES的基本使用

ES(Elasticsearch)一个分布式全文搜索引擎,重点是全文搜索。

​ 那什么是全文搜索呢?比如用户要买一本书,以Java为关键字进行搜索,不管是书名中还是书的介绍中,甚至是书的作者名字,只要包含java就作为查询结果返回给用户查看,上述过程就使用了全文搜索技术。搜索的条件不再是仅用于对某一个字段进行比对,而是在一条数据中使用搜索条件去比对更多的字段,只要能匹配上就列入查询结果,这就是全文搜索的目的。而ES技术就是一种可以实现上述效果的技术。

​ 要实现全文搜索的效果,不可能使用数据库中like操作去进行比对,这种效率太低了。ES设计了一种全新的思想,来实现全文搜索。具体操作过程如下:

  1. 将被查询的字段的数据全部文本信息进行查分,分成若干个词

    • 例如“中华人民共和国”就会被拆分成三个词,分别是“中华”、“人民”、“共和国”,此过程有专业术语叫做分词。分词的策略不同,分出的效果不一样,不同的分词策略称为分词器。
  2. 将分词得到的结果存储起来,对应每条数据的id

    • 例如id为1的数据中名称这一项的值是“中华人民共和国”,那么分词结束后,就会出现“中华”对应id为1,“人民”对应id为1,“共和国”对应id为1

    • 例如id为2的数据中名称这一项的值是“人民代表大会“,那么分词结束后,就会出现“人民”对应id为2,“代表”对应id为2,“大会”对应id为2

    • 此时就会出现如下对应结果,按照上述形式可以对所有文档进行分词。需要注意分词的过程不是仅对一个字段进行,而是对每一个参与查询的字段都执行,最终结果汇总到一个表格中

      分词结果关键字 对应id
      中华 1
      人民 1,2
      共和国 1
      代表 2
      大会 2
  3. 当进行查询时,如果输入“人民”作为查询条件,可以通过上述表格数据进行比对,得到id值1,2,然后根据id值就可以得到查询的结果数据了。

​ 上述过程中分词结果关键字内容每一个都不相同,作用有点类似于数据库中的索引,是用来加速数据查询的。但是数据库中的索引是对某一个字段进行添加索引,而这里的分词结果关键字不是一个完整的字段值,只是一个字段中的其中的一部分内容。并且索引使用时是根据索引内容查找整条数据,全文搜索中的分词结果关键字查询后得到的并不是整条的数据,而是数据的id,要想获得具体数据还要再次查询,因此这里为这种分词结果关键字起了一个全新的名称,叫做倒排索引

​ 通过上述内容的学习,发现使用ES其实准备工作还是挺多的,必须先建立文档的倒排索引,然后才能继续使用。快速了解一下ES的工作原理,下面直接开始我们的学习,老规矩,先安装,再操作,最后说整合。

Windows安装

​ windows版安装包下载地址:https://www.elastic.co/cn/downloads/elasticsearch

​ 下载的安装包是解压缩就能使用的zip文件,解压缩完毕后会得到如下文件

image

  • bin目录:包含所有的可执行命令
  • config目录:包含ES服务器使用的配置文件
  • jdk目录:此目录中包含了一个完整的jdk工具包,版本17,当ES升级时,使用最新版本的jdk确保不会出现版本支持性不足的问题
  • lib目录:包含ES运行的依赖jar文件
  • logs目录:包含ES运行后产生的所有日志文件
  • modules目录:包含ES软件中所有的功能模块,也是一个一个的jar包。和jar目录不同,jar目录是ES运行期间依赖的jar包,modules是ES软件自己的功能jar包
  • plugins目录:包含ES软件安装的插件,默认为空

启动服务器

elasticsearch.bat

​ 双击elasticsearch.bat文件即可启动ES服务器,默认服务端口9200。通过浏览器访问http://localhost:9200看到如下信息视为ES服务器正常启动

Docker下得安装

下载镜像

docker pull elasticsearch:7.17.3

创建挂载目录

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data

写入配置文件

进入/mydata/elasticsearch/config目录下执行

 echo "http.host: 0.0.0.0">>/mydata/elasticsearch/config/elasticsearch.yml

初始化容器

docker run -h elasticsearch -p 9200:9200 -p 9300:9300  --name elasticsearch  \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms256m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.17.3

如果出现错误AccessDeniedException[/usr/share/elasticsearch/data/nodes]

  • 增加权

    chmod -R 777 /mydata/elasticsearch
    
{
  "name" : "CZBK-**********",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "j137DSswTPG8U4Yb-0T1Mg",
  "version" : {
    "number" : "7.16.2",
    "build_flavor" : "default",
    "build_type" : "zip",
    "build_hash" : "2b937c44140b6559905130a8650c64dbd0879cfb",
    "build_date" : "2021-12-18T19:42:46.604893745Z",
    "build_snapshot" : false,
    "lucene_version" : "8.10.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

注意: ES8.X配置文件目录实际上是在 /config/elasticsearch.yml

--name elasticsearch 给容器的名字
-h 指定hostname 名称 两个容器同一网络使用 hostname ping 通
--network 指定docker网络
-p 9200:9200 发送http请求分配的端口
-p 9200:9200 分布式节点下的通讯端口
-e "discovery.type=single-node" 单节点运行
-e ES_JAVA_OPTS="-Xms64m -Xmx=128m" 初始内存64m 最大内存512m 实际开发可以自己调节 不指定 es启动会吧内存占用完直接卡死
-v 挂载到之前创建的文件目录
-d 后台启动

创建密码

进入目录:/mydata/elasticsearch/config

  • 编辑编辑elasticsearch.yml
http.host: 0.0.0.0
# 设置前,需要先enable X-Pack security。
xpack.security.enabled: true
xpack.security.authc.api_key.enabled: true
  • 重启elasticsearch
docker restart elasticsearch
  • 进入容器
# 进入容器
docker exec -it elasticsearch  /bin/bash
# 创建密码
bin/elasticsearch-setup-passwords interactive
  • 可以看到Elasticsearch预置了许多角色和用户。
$ sudo /usr/share/elasticsearch/bin/elasticsearch-setup-passwords interactive
Initiating the setup of passwords for reserved users elastic,apm_system,kibana,logstash_system,beats_system,remote_monitoring_user.
You will be prompted to enter passwords as the process progresses.
Please confirm that you would like to continue [y/N]y


Enter password for [elastic]:
Reenter password for [elastic]:
Enter password for [apm_system]:
Reenter password for [apm_system]:
Enter password for [kibana]:
Reenter password for [kibana]:
Enter password for [logstash_system]:
Reenter password for [logstash_system]:
Enter password for [beats_system]:
Reenter password for [beats_system]:
Enter password for [remote_monitoring_user]:
Reenter password for [remote_monitoring_user]:
Changed password for user [apm_system]
Changed password for user [kibana]
Changed password for user [logstash_system]
Changed password for user [beats_system]
Changed password for user [remote_monitoring_user]
Changed password for user [elastic]


  • curl访问
curl localhost:9200

image

  • 带密码访问
curl localhost:9200 -uelastic:123456789

image

基本操作

​ ES中保存有我们要查询的数据,只不过格式和数据库存储数据格式不同而已。在ES中我们要先创建倒排索引,这个索引的功能又点类似于数据库的表,然后将数据添加到倒排索引中,添加的数据称为文档。所以要进行ES的操作要先创建索引,再添加文档,这样才能进行后续的查询操作。

​ 要操作ES可以通过Rest风格的请求来进行,也就是说发送一个请求就可以执行一个操作。比如新建索引,删除索引这些操作都可以使用发送请求的形式来进行。

  • 创建索引,books是索引名称,下同

    PUT请求		http://localhost:9200/books
    

    发送请求后,看到如下信息即索引创建成功

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

    重复创建已经存在的索引会出现错误信息,reason属性中描述错误原因

    {
        "error": {
            "root_cause": [
                {
                    "type": "resource_already_exists_exception",
                    "reason": "index [books/VgC_XMVAQmedaiBNSgO2-w] already exists",
                    "index_uuid": "VgC_XMVAQmedaiBNSgO2-w",
                    "index": "books"
                }
            ],
            "type": "resource_already_exists_exception",
            "reason": "index [books/VgC_XMVAQmedaiBNSgO2-w] already exists",	# books索引已经存在
            "index_uuid": "VgC_XMVAQmedaiBNSgO2-w",
            "index": "book"
        },
        "status": 400
    }
    

查询索引

GET请求		http://localhost:9200/books

查询索引得到索引相关信息,如下

{
    "book": {
        "aliases": {},
        "mappings": {},
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "books",
                "creation_date": "1645768584849",
                "number_of_replicas": "1",
                "uuid": "VgC_XMVAQmedaiBNSgO2-w",
                "version": {
                    "created": "7160299"
                }
            }
        }
    }
}

如果查询了不存在的索引,会返回错误信息,例如查询名称为book的索引后信息如下

{
    "error": {
        "root_cause": [
            {
                "type": "index_not_found_exception",
                "reason": "no such index [book]",
                "resource.type": "index_or_alias",
                "resource.id": "book",
                "index_uuid": "_na_",
                "index": "book"
            }
        ],
        "type": "index_not_found_exception",
        "reason": "no such index [book]",		# 没有book索引
        "resource.type": "index_or_alias",
        "resource.id": "book",
        "index_uuid": "_na_",
        "index": "book"
    },
    "status": 404
}

删除索引

DELETE请求	http://localhost:9200/books

删除所有后,给出删除结果

{
    "acknowledged": true
}

如果重复删除,会给出错误信息,同样在reason属性中描述具体的错误原因

{
    "error": {
        "root_cause": [
            {
                "type": "index_not_found_exception",
                "reason": "no such index [books]",
                "resource.type": "index_or_alias",
                "resource.id": "book",
                "index_uuid": "_na_",
                "index": "book"
            }
        ],
        "type": "index_not_found_exception",
        "reason": "no such index [books]",		# 没有books索引
        "resource.type": "index_or_alias",
        "resource.id": "book",
        "index_uuid": "_na_",
        "index": "book"
    },
    "status": 404
}

创建索引并指定分词器

​ 前面创建的索引是未指定分词器的,可以在创建索引时添加请求参数,设置分词器。目前国内较为流行的分词器是IK分词器,使用前先在下对应的分词器,然后使用。IK分词器下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

​ 分词器下载后解压到ES安装目录的plugins目录中即可,安装分词器后需要重新启动ES服务器。使用IK分词器创建索引格式:

PUT请求		http://localhost:9200/books

请求参数如下(注意是json格式的参数)
{
    "mappings":{							#定义mappings属性,替换创建索引时对应的mappings属性		
        "properties":{						#定义索引中包含的属性设置
            "id":{							#设置索引中包含id属性
                "type":"keyword"			#当前属性可以被直接搜索
            },
            "name":{						#设置索引中包含name属性
                "type":"text",              #当前属性是文本信息,参与分词  
                "analyzer":"ik_max_word",   #使用IK分词器进行分词             
                "copy_to":"all"				#分词结果拷贝到all属性中
            },
            "type":{
                "type":"keyword"
            },
            "description":{
                "type":"text",	                
                "analyzer":"ik_max_word",                
                "copy_to":"all"
            },
            "all":{							#定义属性,用来描述多个字段的分词结果集合,当前属性可以参与查询
                "type":"text",	                
                "analyzer":"ik_max_word"
            }
        }
    }
}

​ 创建完毕后返回结果和不使用分词器创建索引的结果是一样的,此时可以通过查看索引信息观察到添加的请求参数mappings已经进入到了索引属性中

{
    "books": {
        "aliases": {},
        "mappings": {						#mappings属性已经被替换
            "properties": {
                "all": {
                    "type": "text",
                    "analyzer": "ik_max_word"
                },
                "description": {
                    "type": "text",
                    "copy_to": [
                        "all"
                    ],
                    "analyzer": "ik_max_word"
                },
                "id": {
                    "type": "keyword"
                },
                "name": {
                    "type": "text",
                    "copy_to": [
                        "all"
                    ],
                    "analyzer": "ik_max_word"
                },
                "type": {
                    "type": "keyword"
                }
            }
        },
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "books",
                "creation_date": "1645769809521",
                "number_of_replicas": "1",
                "uuid": "DohYKvr_SZO4KRGmbZYmTQ",
                "version": {
                    "created": "7160299"
                }
            }
        }
    }
}

目前我们已经有了索引了,但是索引中还没有数据,所以要先添加数据,ES中称数据为文档,下面进行文档操作。

添加文档,有三种方式

POST请求	http://localhost:9200/books/_doc		#使用系统生成id
POST请求	http://localhost:9200/books/_create/1	#使用指定id
POST请求	http://localhost:9200/books/_doc/1		#使用指定id,不存在创建,存在更新(版本递增)

文档通过请求参数传递,数据格式json
{
    "name":"springboot",
    "type":"springboot",
    "description":"springboot"
}  
  • 查询文档

    GET请求	http://localhost:9200/books/_doc/1		 #查询单个文档 		
    GET请求	http://localhost:9200/books/_search		 #查询全部文档
    
  • 条件查询

    GET请求	http://localhost:9200/books/_search?q=name:springboot	# q=查询属性名:查询属性值
    
  • 删除文档

    DELETE请求	http://localhost:9200/books/_doc/1
    
  • 修改文档(全量更新)

    PUT请求	http://localhost:9200/books/_doc/1
    
    文档通过请求参数传递,数据格式json
    {
        "name":"springboot",
        "type":"springboot",
        "description":"springboot"
    }
    
  • 修改文档(部分更新)

    POST请求	http://localhost:9200/books/_update/1
    
    文档通过请求参数传递,数据格式json
    {			
        "doc":{						#部分更新并不是对原始文档进行更新,而是对原始文档对象中的doc属性中的指定属性更新
            "name":"springboot"		#仅更新提供的属性值,未提供的属性值不参与更新操作
        }
    }
    

整合方式1

​ 使用springboot整合ES该如何进行呢?老规矩,导入坐标,做配置,使用API接口操作。整合Redis如此,整合MongoDB如此,整合ES依然如此。太没有新意了,其实不是没有新意,这就是springboot的强大之处,所有东西都做成相同规则,对开发者来说非常友好。

​ 下面就开始springboot整合ES,操作步骤如下:

步骤①:导入坐标

导入springboot整合ES的starter坐标

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

步骤②:进行基础配置

spring:
  elasticsearch:
    rest:
      uris: 192.168.56.20:9200
      host: 192.168.56.20
      port: 9200
      username: elastic
      password: password

​ 配置ES服务器地址,端口9200

步骤③: 配置


import lombok.Data;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;

@ConfigurationProperties(prefix = "spring.elasticsearch.rest")
@Configuration
@Data
public class RestHighLevelConfig extends AbstractElasticsearchConfiguration {

    private String host;
    private int port;
    private String userName;
    private String passWord;

    @Override
    public RestHighLevelClient elasticsearchClient() {
        //创建凭证
        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        //设置账号密码
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, passWord));

        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
                new HttpHost(host, port)).setHttpClientConfigCallback(h -> h.setDefaultCredentialsProvider(credentialsProvider)));
        return client;

    }
}

步骤④: 创建对象

  • 实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "student")
public class Student {
    @Id
    private Integer id;
    /**
     *  ik_smart=智能分词   ik_max_word最大分词
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word" )
    private String name;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Text)
    private String gender;
    @Field(type = FieldType.Text)
    private String cardNo;
    @Field(type = FieldType.Text)
    private String email;
    @Field(type = FieldType.Text,analyzer = "ik_smart")
    private String address;
    @Field(type = FieldType.Text,index = false)
    private String mobile;
//    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
//    private Date createTime;
}

  • 创建接口继承并继承ElasticsearchRepository
@Repository
public interface StudentDao extends ElasticsearchRepository<Student,String> {
     List<Student> findStudentByName(String name);
}

使用方式1-整合方式

使用springboot整合ES的专用客户端接口ElasticsearchRestTemplate来进行操作


import cn.binarywang.tools.generator.*;
import cn.hutool.core.util.RandomUtil;
import com.xiesn.es.data.dao.StudentDao;
import com.xiesn.es.data.entity.Student;
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.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
public class StudentServiceTest {
    @Autowired
    private StudentDao studentDao;

    @Test
    public void save() {
        //准备数据
        Student student = new Student();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        student.setId(2);
        student.setName(ChineseNameGenerator.getInstance().generate());
        student.setAge(RandomUtil.randomInt(100));
        student.setGender(idCard.charAt(idCard.length() - 2) % 2 == 0 ? "女" : "男");
        student.setEmail(EmailAddressGenerator.getInstance().generate());
        student.setAddress(ChineseAddressGenerator.getInstance().generate());
        student.setCardNo(idCard);
        student.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        Student save = studentDao.save(student);

        System.err.println(save);

    }

    @Test
    public void saveAll() {
        List<Student> studentList = new ArrayList<>();
        for (int i = 1; i <= 100; i++) {
            //准备数据
            Student student = new Student();
            String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
            student.setId(i);
            student.setName(ChineseNameGenerator.getInstance().generate());
            student.setAge(RandomUtil.randomInt(100));
            student.setGender(idCard.charAt(idCard.length() - 2) % 2 == 0 ? "女" : "男");
            student.setEmail(EmailAddressGenerator.getInstance().generate());
            student.setAddress(ChineseAddressGenerator.getInstance().generate());
            student.setCardNo(idCard);
            student.setMobile(ChineseMobileNumberGenerator.getInstance().generate());
            studentList.add(student);
        }

        Iterable<Student> students = studentDao.saveAll(studentList);

        System.err.println(students);

    }


    /**
     * 修改
     */
    @Test
    public void update() {
        //准备数据
        Student student = new Student();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        // TODO id相同则想改
        student.setId(1);
        student.setName(ChineseNameGenerator.getInstance().generate());
        student.setAge(RandomUtil.randomInt(100));
        student.setGender(idCard.charAt(idCard.length() - 2) % 2 == 0 ? "女" : "男");
        student.setEmail(EmailAddressGenerator.getInstance().generate());
        student.setAddress(ChineseAddressGenerator.getInstance().generate());
        student.setCardNo(idCard);
        student.setMobile(ChineseMobileNumberGenerator.getInstance().generate());


        Student save = studentDao.save(student);

        System.err.println(save);

    }


    /**
     * 根据id查询
     */
    @Test
    public void findBId() {

        Student student = studentDao.findById("1").get();
        System.err.println(student);

    }


    /**
     * 根据id查询
     */
    @Test
    public void findAll() {

        Iterable<Student> studentDaoAll = studentDao.findAll();
        for (Student student : studentDaoAll) {
            System.err.println(student);
        }

    }

    /**
     * 根据id删除
     */
    @Test
    public void delete() {
        studentDao.deleteById("1");

    }

    /**
     * 删除条件
     */
    @Test
    public void deleteBy() {
        Student student = new Student();
        student.setId(2);
        studentDao.delete(student);
    }


    /**
     * 分页查询
     */
    @Test
    public void findByPage() {
        //排序
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        //当前页
        int page = 0;
        //每页像是 记录
        int pageSize = 5;
        //分页
        PageRequest pageRequest = PageRequest.of(page, pageSize, sort);
        Page<Student> studentPage = studentDao.findAll(pageRequest);
        List<Student> content = studentPage.getContent();
        for (Student student : content) {
            System.err.println(student);
        }

    }


}

使用方式2-原生方式

使用springboot整合ES的专用客户端接口ElasticsearchRestTemplate来进行操作



import cn.binarywang.tools.generator.*;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiesn.es.data.entity.Student;
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
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.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;

/**
 * <p>
 * 原生es测试
 * </p>
 *
 * @author MrWen
 **/
@SpringBootTest
public class RestHighLevelIndexTest {

    @Autowired
    private RestHighLevelClient esClient;

    /**
     * 1)创建索引
     */
    @Test
    public void indexCreate() throws IOException {

        // 创建索引
        CreateIndexRequest request = new CreateIndexRequest("student");
        CreateIndexResponse createIndexResponse =
                esClient.indices().create(request, RequestOptions.DEFAULT);

        // 响应状态
        boolean acknowledged = createIndexResponse.isAcknowledged();
        System.out.println("索引操作 :" + acknowledged);

        esClient.close();
    }


    /**
     * 2)查看索引
     */
    @Test
    public void indexSearch() throws IOException {


        // 查询索引
        GetIndexRequest request = new GetIndexRequest("student");

        GetIndexResponse getIndexResponse =
                esClient.indices().get(request, RequestOptions.DEFAULT);

        // 响应状态
        System.out.println(getIndexResponse.getAliases());
        System.out.println(getIndexResponse.getMappings());
        System.out.println(getIndexResponse.getSettings());

        esClient.close();
    }


    /**
     * 3)删除索引
     */
    @Test
    public void indexDelete() throws IOException {


        // 查询索引
        DeleteIndexRequest request = new DeleteIndexRequest("student");

        AcknowledgedResponse response = esClient.indices().delete(request, RequestOptions.DEFAULT);

        // 响应状态
        System.out.println(response.isAcknowledged());

        esClient.close();
    }


    /**
     * 1.1 新增文档
     */
    @Test
    public void docInsert() throws IOException {

        // 插入数据
        IndexRequest request = new IndexRequest();
        request.index("student").id(RandomUtil.randomString(5));

        Student user = new Student();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(Integer.parseInt(RandomUtil.randomNumbers(10)));
        user.setName( ChineseNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        // 向ES插入数据,必须将数据转换位JSON格式
        ObjectMapper mapper = new ObjectMapper();
        String userJson = mapper.writeValueAsString(user);
        request.source(userJson, XContentType.JSON);

        IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);

        System.out.println(response.getResult());

        esClient.close();
    }

    /**
     * 1.2 批量新增:
     */
    /**
     * 批量保存数据
     */
    @Test
    public void bulkData() throws IOException {

        //创建bulk对象
        BulkRequest bulkRequest = new BulkRequest("student");

        //准备数据
        for (int i = 1; i <= 100; i++) {
            IndexRequest indexRequest = new IndexRequest ("student");
            Student user = new Student();
            String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
            user.setId(Integer.parseInt(RandomUtil.randomNumbers(5)));
            user.setName( ChineseNameGenerator.getInstance().generate());
            user.setAge(RandomUtil.randomInt(100));
            user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
            user.setEmail(EmailAddressGenerator.getInstance().generate());
            user.setAddress(ChineseAddressGenerator.getInstance().generate());
            user.setCardNo(idCard);
            user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());
            String jsonString = JSON.toJSONString(user);
            //设置要保存的内容
            indexRequest.id(RandomUtil.randomString(10));
            indexRequest.source(jsonString, XContentType.JSON);
            bulkRequest.add(indexRequest);

        }
        System.err.println(bulkRequest);
        BulkResponse responses = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);

        System.out.println("结果:"+responses.getTook());
        System.out.println("结果:"+responses.getItems().length);
        esClient.close();


    }

    /**
     * 2)修改文档
     */
    @Test
    public void docUpdate() throws IOException {


        // 修改数据
        UpdateRequest request = new UpdateRequest();
        request.index("student").id("1");
        request.doc(XContentType.JSON, "gender", "男","age", 33);

        UpdateResponse response = esClient.update(request, RequestOptions.DEFAULT);

        System.out.println(response.getResult());

        esClient.close();
    }


    /**
     * 3.1 查询文档-简单根据id查询
     */
    @Test
    public void docGet() throws IOException {

        // 查询数据
        GetRequest request = new GetRequest();
        request.index("student").id("1");
        GetResponse response = esClient.get(request, RequestOptions.DEFAULT);

        System.out.println(response.getSourceAsString());

        esClient.close();
    }


    /**
     * 3.2 查询所有数据
     */
    @Test
    public void docGetAll() throws IOException {


        // 1. 查询索引的所有数据
        SearchRequest request = new SearchRequest();
        request.indices("student");

        // 构造查询条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());

        request.source(builder);

        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.println(response.getTook());
        System.out.println(hits.getTotalHits());
        Iterator<SearchHit> iterator = hits.iterator();
        while (iterator.hasNext()) {
            SearchHit hit = iterator.next();
            System.out.println(hit.getSourceAsString());
        }
        esClient.close();
    }


    /**
     * 3.3 高级查询
     */
    @Test
    public void docQuery() throws IOException {



        // 1. 查询索引中全部的数据
        System.out.println("\t\n");
        System.out.println("==========================查询索引中全部的数据==========================");
        SearchRequest request = new SearchRequest();
        request.indices("student");

        request.source(new SearchSourceBuilder().query(QueryBuilders.matchAllQuery()));

        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);

        SearchHits hits = response.getHits();

        System.out.println(hits.getTotalHits());
        System.out.println(response.getTook());

        for (SearchHit hit : hits) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================查询索引中全部的数据==========================end");
        System.out.println("\t\n");

        // 2. 条件查询 : termQuery
        System.out.println("==========================条件查询 : termQuery==========================");
        SearchRequest request2 = new SearchRequest();
        request2.indices("student");

        request2.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("age", 30)));
        SearchResponse response2 = esClient.search(request2, RequestOptions.DEFAULT);

        SearchHits hits2 = response2.getHits();

        System.out.println(hits2.getTotalHits());
        System.out.println(response2.getTook());

        for (SearchHit hit : hits2) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================条件查询 : termQuery==========================end");
        System.out.println("\t\n");

        // 3. 分页查询
        System.out.println("==========================分页查询==========================");
        SearchRequest request3 = new SearchRequest();
        request3.indices("student");

        SearchSourceBuilder builder = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
        // (当前页码-1)*每页显示数据条数
        builder.from(2);
        builder.size(2);
        request3.source(builder);
        SearchResponse response3 = esClient.search(request3, RequestOptions.DEFAULT);

        SearchHits hits3 = response3.getHits();

        System.out.println(hits3.getTotalHits());
        System.out.println(response3.getTook());

        for (SearchHit hit : hits3) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================分页查询end==========================end");
        System.out.println("\t\n");

        // 4. 查询排序
        System.out.println("==========================查询排序==========================");
        SearchRequest request4 = new SearchRequest();
        request4.indices("student");

        SearchSourceBuilder builder4 = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());

        builder4.sort("age", SortOrder.DESC);

        request4.source(builder4);
        SearchResponse response4 = esClient.search(request4, RequestOptions.DEFAULT);

        SearchHits hits4 = response4.getHits();

        System.out.println(hits4.getTotalHits());
        System.out.println(response4.getTook());

        for (SearchHit hit : hits4) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================查询排序==========================end");
        System.out.println("\t\n");


        // 5. 过滤字段
        System.out.println("==========================过滤字段==========================");
        SearchRequest request5 = new SearchRequest();
        request5.indices("student");

        SearchSourceBuilder builder5 = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());
        //
        String[] excludes = {"age"};
        String[] includes = {};
        builder5.fetchSource(includes, excludes);

        request5.source(builder5);
        SearchResponse response5 = esClient.search(request5, RequestOptions.DEFAULT);

        SearchHits hits5 = response5.getHits();

        System.out.println(hits5.getTotalHits());
        System.out.println(response5.getTook());

        for (SearchHit hit : hits5) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================过滤字段==========================end");
        System.out.println("\t\n");

        // 6. 组合查询
        System.out.println("==========================组合查询==========================");
        SearchRequest request6 = new SearchRequest();
        request6.indices("student");

        SearchSourceBuilder builder6 = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        //boolQueryBuilder.must(QueryBuilders.matchQuery("age", 30));
        //boolQueryBuilder.must(QueryBuilders.matchQuery("sex", "男"));
        //boolQueryBuilder.mustNot(QueryBuilders.matchQuery("sex", "男"));
        boolQueryBuilder.should(QueryBuilders.matchQuery("age", 30));
        boolQueryBuilder.should(QueryBuilders.matchQuery("age", 40));

        builder6.query(boolQueryBuilder);

        request6.source(builder6);
        SearchResponse response6 = esClient.search(request6, RequestOptions.DEFAULT);

        SearchHits hits6 = response6.getHits();

        System.out.println(hits6.getTotalHits());
        System.out.println(response6.getTook());

        for (SearchHit hit : hits6) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================组合查询==========================end");
        System.out.println("\t\n");

        // 7. 范围查询
        System.out.println("==========================范围查询==========================");
        SearchRequest request7 = new SearchRequest();
        request7.indices("student");

        SearchSourceBuilder builder7 = new SearchSourceBuilder();
        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age");

        rangeQuery.gte(30);
        rangeQuery.lt(50);

        builder7.query(rangeQuery);

        request7.source(builder7);
        SearchResponse response7 = esClient.search(request7, RequestOptions.DEFAULT);

        SearchHits hits7 = response7.getHits();

        System.out.println(hits7.getTotalHits());
        System.out.println(response7.getTook());

        for (SearchHit hit : hits7) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================范围查询==========================end");
        System.out.println("\t\n");

        // 8. 模糊查询
        System.out.println("==========================模糊查询==========================");
        SearchRequest request8 = new SearchRequest();
        request8.indices("student");

        SearchSourceBuilder builder8 = new SearchSourceBuilder();
        builder8.query(QueryBuilders.fuzzyQuery("name", "张").fuzziness(Fuzziness.TWO));

        request8.source(builder8);
        SearchResponse response8 = esClient.search(request8, RequestOptions.DEFAULT);

        SearchHits hits8 = response8.getHits();

        System.out.println(hits8.getTotalHits());
        System.out.println(response8.getTook());

        for (SearchHit hit : hits8) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================模糊查询==========================end");
        System.out.println("\t\n");

        // 9. 高亮查询
        System.out.println("==========================高亮查询==========================");
        SearchRequest request9 = new SearchRequest();
        request9.indices("student");

        SearchSourceBuilder builder9 = new SearchSourceBuilder();
        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "Caroline");

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.field("name");

        builder9.highlighter(highlightBuilder);
        builder9.query(termsQueryBuilder);

        request9.source(builder9);
        SearchResponse response9 = esClient.search(request9, RequestOptions.DEFAULT);

        SearchHits hits9 = response9.getHits();

        System.out.println(hits9.getTotalHits());
        System.out.println(response9.getTook());

        for (SearchHit hit : hits9) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================高亮查询==========================end");
        System.out.println("\t\n");

        // 10. 聚合查询
        System.out.println("==========================聚合查询==========================");
        SearchRequest request10 = new SearchRequest();
        request10.indices("student");

        SearchSourceBuilder builder10 = new SearchSourceBuilder();

        AggregationBuilder aggregationBuilder = AggregationBuilders.max("maxAge").field("age");
        builder10.aggregation(aggregationBuilder);

        request10.source(builder10);
        SearchResponse response10 = esClient.search(request10, RequestOptions.DEFAULT);

        SearchHits hits10 = response10.getHits();

        System.out.println(hits10.getTotalHits());
        System.out.println(response10.getTook());

        for (SearchHit hit : hits10) {
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("==========================聚合查询==========================end");
        System.out.println("\t\n");

        // 11. 分组查询
        System.out.println("==========================分组查询==========================");
        SearchRequest request11 = new SearchRequest();
        request11.indices("student");

        SearchSourceBuilder builder11 = new SearchSourceBuilder();

        AggregationBuilder aggregationBuilder11 = AggregationBuilders.terms("ageGroup").field("age");
        builder11.aggregation(aggregationBuilder11);

        request11.source(builder11);
        SearchResponse response11 = esClient.search(request11, RequestOptions.DEFAULT);

        SearchHits hits11 = response11.getHits();

        System.out.println(hits11.getTotalHits());
        System.out.println(response11.getTook());

        for (SearchHit hit : hits11) {
            System.out.println(hit.getSourceAsString());
        }
        esClient.close();
        System.out.println("==========================分组查询==========================end");
    }


    /**
     * 4.1 删除文档
     */
    @Test
    public void docDelete() throws IOException {

        DeleteRequest request = new DeleteRequest();
        request.index("student").id("1001");

        DeleteResponse response = esClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(response.toString());

        esClient.close();
    }


    /**
     * 4.2 批量删除:
     */
    @Test
    public void docDeleteBatch() throws IOException {

        RestHighLevelClient esClient = new RestHighLevelClient(
                RestClient.builder(new HttpHost("localhost", 9200, "http"))
        );

        // 批量删除数据
        BulkRequest request = new BulkRequest();

        request.add(new DeleteRequest().index("student").id("1001"));
        request.add(new DeleteRequest().index("student").id("1002"));
        request.add(new DeleteRequest().index("student").id("1003"));

        BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);
        System.out.println(response.getTook());
        System.out.println(Arrays.toString(response.getItems()));
        esClient.close();
    }
}

​ 上述操作形式是ES早期的操作方式,使用的客户端被称为Low Level Client,这种客户端操作方式性能方面略显不足,于是ES开发了全新的客户端操作方式,称为High Level Client。高级别客户端与ES版本同步更新,但是springboot最初整合ES的时候使用的是低级别客户端,所以企业开发需要更换成高级别的客户端模式。


不过ES在 7.15.0 以后的版本中弃用Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client) https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

改为: Java API Client
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/index.html

​ 下面使用高级别客户端方式进行springboot整合ES,操作步骤如下:

整合方式2

步骤①:导入坐标

导入springboot整合ES高级别客户端的坐标,此种形式目前没有对应的starter

     <!--在 7.15.0 中已弃用。-->
        <!--<dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
             <version>7.17.3</version>
        </dependency>-->
        <!--在 7.17 以上版本使用 start-->
        <!-- 参考文档:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/installation.html-->
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>7.17.3</version>
        </dependency>
		 <!--只有在使用spring-boot Maven插件时才需要 否则会报错 ClassNotFoundException: jakarta.json.spi.JsonProvider.-->
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.0.1</version>
        </dependency>

步骤②:配置文件

spring:
  elasticsearch:
    rest:
      # 是否启用es
      enable: true
      uris: 192.168.56.30:9200
      host: 192.168.56.30
      port: 9200
      username: elastic
      password: 123456789

步骤③:获取客户端对象

使用编程的形式设置连接的ES服务器,并获取客户端对象


import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticSearchConfig {

    @Value("${spring.elasticsearch.rest.host}")
    private String host;
    @Value("${spring.elasticsearch.rest.enable:true}")
    private boolean enable;

    @Value("${spring.elasticsearch.rest.port}")
    private int port;
    @Value("${spring.elasticsearch.rest.username}")
    private String userName;

    @Value("${spring.elasticsearch.rest.password}")
    private String passWord;


    /**
     * 文档: https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/connecting.html
     */
    @Bean
    public ElasticsearchClient elasticsearchClient() {
        ElasticsearchClient client = new ElasticsearchClient(null);
        if (enable) {
            //创建凭证
             CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            //设置账号密码
            credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(userName, passWord));

            RestClient restClient = RestClient.builder(new HttpHost(host, port))
                    .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)).build();


            ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());

            client = new ElasticsearchClient(transport);
        }
        return client;

    }


    /**
     * 异步客户端
     */
    @Bean
    public ElasticsearchAsyncClient elasticsearchAsyncClient() {
        ElasticsearchAsyncClient esAsyncClient = new ElasticsearchAsyncClient(null);
        if (enable) {
            //创建凭证
            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
            //设置账号密码
            credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(userName, passWord));
            RestClient restClient = RestClient.builder(new HttpHost(host, port)).setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)).build();

            ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
            esAsyncClient = new ElasticsearchAsyncClient(transport);
        }
        return esAsyncClient;

    }
}

​ 配置ES服务器地址与端口9200,记得客户端使用完毕需要手工关闭。由于当前客户端是手工维护的,因此不能通过自动装配的形式加载对象。

​ 高级别客户端操作是通过发送请求的方式完成所有操作的,ES针对各种不同的操作,设定了各式各样的请求对象,上例中创建索引的对象是CreateIndexRequest,其他操作也会有自己专用的Request对象。

​ 当前操作我们发现,无论进行ES何种操作,第一步永远是获取RestHighLevelClient对象,最后一步永远是关闭该对象的连接。在测试中可以使用测试类的特性去帮助开发者一次性的完成上述操作,但是在业务书写时,还需要自行管理。将上述代码格式转换成使用测试类的初始化方法和销毁方法进行客户端对象的维护。

使用测试


import cn.binarywang.tools.generator.*;
import cn.hutool.core.util.RandomUtil;
import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.IndexRequest;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.xiesn.es.java.entity.User;
import lombok.SneakyThrows;
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 java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.concurrent.CompletableFuture;

/**
 * 代码实例:https://github.com/elastic/elasticsearch-java/blob/7.17/java-client/src/test/java/co/elastic/clients/documentation/usage/IndexingTest.java
 */
@Slf4j
@SpringBootTest
public class IndexDemo {
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private ElasticsearchAsyncClient esAsyncClient;

    /**
     * 基本
     *
     * @throws IOException
     */
    @Test
    public void indexData() throws IOException {


        User user = new User();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(1);
        user.setName( EnglishNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        IndexResponse response = elasticsearchClient.index(i -> i
                .index("users")
                .id(user.getId().toString())
                .document(user)
        );

        System.err.println(response.version());


    }

    /**
     * 客户端类of()
     */
    @SneakyThrows
    @Test
    public void indexDate2() {

        User user = new User();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(2);
        user.setName( EnglishNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        IndexRequest<User> request = IndexRequest.of(i -> i
                .index("users")
                .id(user.getId().toString())
                .document(user)
        );

        IndexResponse response = elasticsearchClient.index(request);

        log.info("Indexed with version " + response.version());
    }

    /**
     * 使用经典构建器
     */
    @SneakyThrows
    @Test
    public void index3() {

        User user = new User();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(3);
        user.setName( EnglishNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        IndexRequest.Builder<User> indexReqBuilder = new IndexRequest.Builder<>();
        indexReqBuilder.index("usersss");
        indexReqBuilder.id(user.getId().toString());
        indexReqBuilder.document(user);
        indexReqBuilder.type("person");

        IndexResponse response = elasticsearchClient.index(indexReqBuilder.build());

        log.info("Indexed with version " + response.version());
    }


    /**
     * 异步
     */
    @SneakyThrows
    @Test
    public void asychIndex() {


        User user = new User();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(4);
        user.setName( EnglishNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());

        final CompletableFuture<IndexResponse> complete = esAsyncClient.index(i -> i
                .index("users")
                .id(user.getId().toString())
                .document(user)

        ).whenComplete((response, exception) -> {
            if (exception != null) {
                log.error("Failed to index", exception);
            } else {
                log.info("Indexed with version " + response.version());
            }
        });
    }

    /**
     * 使用原始 JSON 数据
     */
    @SneakyThrows
    @Test
    public void jsonIdex() {

        User user = new User();
        String idCard = ChineseIDCardNumberGenerator.getInstance().generate();
        user.setId(4);
        user.setName( EnglishNameGenerator.getInstance().generate());
        user.setAge(RandomUtil.randomInt(100));
        user.setGender(idCard.charAt(idCard.length()-2)%2 == 0?"女":"男");
        user.setEmail(EmailAddressGenerator.getInstance().generate());
        user.setAddress(ChineseAddressGenerator.getInstance().generate());
        user.setCardNo(idCard);
        user.setMobile(ChineseMobileNumberGenerator.getInstance().generate());


        Reader input = new StringReader(JSON.toJSONString(user));
        IndexRequest<JsonData> request = IndexRequest.of(i -> i
                .index("logs")
                .withJson(input)
        );

        IndexResponse response = elasticsearchClient.index(request);

        log.info("Indexed with version " + response.version());
    }
}

​ 现在的书写简化了很多,也更合理。下面使用上述模式将所有的ES操作执行一遍,测试结果

IK分词器是通过请求参数的形式进行设置的,设置请求参数使用request对象中的source方法进行设置,至于参数是什么,取决于你的操作种类。当请求中需要参数时,均可使用当前形式进行参数设置。

按条件查询文档

@Test
    /**
     * Simple search query
     */
    @SneakyThrows
    @Test
    public void simpleSearch() {

        /*

           GET bank/_search
            {
              "query": {
                "match": {
                    "firstname": "Dorothy"
                }
              },
              "from": 0,
              "size": 5,
              "sort": [
                {
                    "account_number": {
                    "order": "desc"
                  }
                }
              ]
            }
         */
        //-----------------第一种写法-----------------------------------
        SearchResponse<Bank> response = esClient.search(s -> s
                        .index("bank")
                        .query(q -> q
                                .match(t -> t
                                        .field("firstname")
                                        .query("Elinor")
                                )
                        ).sort(st -> st
                                .field(a -> a
                                        .field("account_number")
                                        .order(SortOrder.Desc)
                                )
                        ).from(0).size(5)

                ,
                Bank.class
        );
        //-----------------第二种写法-----------------------------------
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index("bank");
        builder.query(QueryBuilders.match().field("address").query("mill").build()._toQuery());
        //builder.query(Query.of(q->q.match(m->m.field("address").query("mill"))));
        SearchResponse<Bank> searchResponse = esClient.search(builder.build(), Bank.class);



        TotalHits total = response.hits().total();
        boolean isExactResult = total.relation() == TotalHitsRelation.Eq;

        if (isExactResult) {
            log.info("There are " + total.value() + " results");
        } else {
            log.info("There are more than " + total.value() + " results");
        }

        List<Hit<Bank>> hits = response.hits().hits();

        for (Hit<Bank> hit : hits) {
            Bank bank = hit.source();
            log.info(JSON.toJSONString(bank));
        }
    }

​ 按条件查询文档使用的请求对象是SearchRequest,查询时调用SearchRequest对象的termQuery方法,需要给出查询属性名,此处支持使用合并字段,也就是前面定义索引属性时添加的all属性。

总结

  1. springboot整合ES步骤
    1. 导入springboot整合ES的High Level Client坐标
    2. 手工管理客户端对象,包括初始化和关闭操作
    3. 使用High Level Client根据操作的种类不同,选择不同的Request对象完成对应操作
posted @ 2023-03-05 16:41  小明の烦恼  阅读(734)  评论(0编辑  收藏  举报