第13章 集成NoSQL数据库,实现Elasticsearch和Solr搜索引擎

         关于搜索引擎 我们很难实现 Elasticseach 和 Solr两大搜索框架的效果;所以本章针对两大搜索框架,非常详细地讲解 它们的原理和具体使用方法,

首先 介绍什么是搜索引擎 、如何用 MySQL实现简单的搜索引擎,以及Elasticseach 的 概念和接口类;

然后介绍Elasticseach 的精准、模糊、范围、组合、分页、聚合查询;

最后介绍 Solr的概念、安装、配置和使用,并对两大搜索框架进行比较。

13.1 Elasticseach --- 搜索应用服务器

13.1.1什么是搜索引擎

        搜索引擎(search engine)通常意义上是指:根据特定策略,运用特定的爬虫程序从互联网 上搜集信息,然后对信息进行处理后,为用户提供检索服务,将检索到的相关信息展示给用户的系统。

本章主要讲解的是搜索的索引和检索,不涉及爬虫程序的内容爬取。大部分公司的业务也不会有爬取工作,而只提供查询服务,而且Elasticsearch也只是提供这方面的功能。

13.1.2用数据库实现搜索功能

        在极少量的数据应用中,可以利用关系型数据库的Select语句实现搜索功能。比如有一个电子 商务系统,采用MySQL数据库,其产品数据表如图13-1所示。

需要实现产品搜索功能,可以使用如下SQL语句:

select * from product where 字段 like '%关键词%'
select * from product where 字段 like '关键词%'

  上述SQL语句中,如果要使用索引提高性能,则like就必须写成like 'a%'或 '%a'形式。两边都加上“%”是不会触发索引的。

我们先不考虑性能,只从商业效果上来看下面的演示是否能满足用户的需求。

1.简单查询

        假如,用户想搜索“红富士”的苹果,当搜索关键词“红富士”之后,MySQL执行SQL语句, 如图13-2所示。

                查询结果是“name”字段中出现“红富士”的产品信息。

2.多字段模糊查询

        上面已经实现了简单的查询。但是,我们发现产品“金帅”的“body”字段中是"金帅,苹果中一种好吃的苹果,和红富士一样好吃”,这包含了 “红富士”关键词,如何才能把“body”字段 中的关键词也搜索到呢?

        现在对SQL语句进行改进,改为如图13-3所示的SQL语句。可以看到,"body"字段中有 “红富士”的词也被检索出来了。

3.分词查询

        由于用户输入时可能会存在输入错误的情况,假设用户输入的是“红富s”,那么数据库是无法查询到结果的。这时需要用分词算法对输入数据进行分词,可以分为:

红、富、S

红富、S

红、富S

然后对词进行分别查询,查询结果如图13-4所示。

        我们会发现结果中把“name”和“body”字段都搜索出来了,但是出现了一个问题一苹果XS 手机壳不该出现却出现了。当然这里排在最后,可能对用户影响不大,但是如果加上排序字段,

根据最新添加来排序呢?则变成了如图13-5所示的效果。

        这就会影响用户的搜索体验,本意是查询水果的,结果手机配件却排在了第一位,问题在哪里 呢?其中一个原因就是分词没分好,然后没有权重设置(权重这里暂时不涉及)。所以,此时需要一 个很好的分词系统。

        从MySQL 5.7开始内置了 ngram全文检索插件,用来支持中文分词,并且对MylSAM和 InnoDB引擎有效。在使用中文检索分词插件ngram之前,需要在MySQL配置文件里面设置它的 分词大小,比如设置为2:

ngram_token_size=2

然后可以使用match命令进行查询。

select * from product where match (name, body)  against  ('红富 s');

分词的结果就变成了:

红富、富S、红S

查询结果如图13-6所示。

        乍一看效果很好,但实际并不太科学,配置限定死了,没法根据应用环境来配置。比如搜索“苹 果XR"这个手机,因为分词长度为“2”,那么苹果这种水果也会被搜索岀来,所以结果不尽如 人意。

        通过以上实例可以看出,如果用MySQL等关系型或NoSQL数据库去实现搜索引擎还是很麻 烦的,即使在不考虑性能的情况下,考虑的问题依然非常多。

        如果加上性能因素来考量,即使对数 据库进行索引,在面对复杂情况下的查询时,效果和性能都是不尽如人意的。

        所以,在现在信息爆炸的时代,处理大规模数据就显得力不从心,这时需要一种专业、配置简 单、性能极优的搜索引擎系统,如果能开源、可实现分布式、支持RESTfulAPI,则学习、使用成 本、性能和实

现就非常完美了。这时,Elasticsearch和Solr就出现在我们面前了。

13.1.3 认识 Elasticsearch

        Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎。通过它,能够执行及合并多种类型的搜索(结构化数据、非结构化数据、地理位置、指标),解决不断涌现出的各种需求。

        Elasticsearch使用的是标准的RESTful风格的API,使用JSON提供多种语言(Java、 Python、Net、SQL和PHP)的支持,它可以快速地存储、搜索和分析海量数据。

        Elasticsearch是用Java语言开发的,并使用Lucene作为其核心来实现所有索引和搜索的功 能。它的目的是:通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

        Elasticsearch是一个幵源的高扩展的分布式全文检索引擎,可以近乎实时地存储、检索数据; 本身扩展性很好,允许多台服务器协同工作,每台服务器可以运行多个实例。单个实例称为一个节 点(node),—组节点构成一个集群(cluster)„分片是底层的工作单元,文档保存在分片内,分片 又被分配到集群内的各个节点里,每个分片仅保存全部数据的一部分。

        当Elasticsearch的节点启动后,它会使用多播(multicast)或单播(用户更改了配置)寻找 集群中的其他节点,并与之建立连接。

13.1.4 Elasticsearch 应用案例

  • GitHub: 2013年年初,GitHub把Solr缓存改成了 Elasticsearch,以便用户搜索20TB 的数据,包括13亿个文件和1300亿行代码。
  • 维基百科:启动以Elasticsearch为基础的核心搜索架构SoundCloud,为1.8亿用户提供 即时而精准的音乐搜索服务。
  • 百度:百度使用Elasticsearch作为数据分析引擎,20多个业务线采集服务器上的各类数 据及用户自定义数据,通过对各种数据进行多维分析,辅助定位异常。其单集群最大100台 机器,200个Elasticsearch节点,每天导入超过30TB的数据。

除这些公司外,Stack Overflow.新浪、阿里、360、携程、有赞、苏宁都在使用它。它被广泛地用于各大公司的站内搜索、IT系统搜索(OA、CRM、ERP)、数据分析等工作中。

13.1.5 对比 Elasticsearch 与 MySQL

        尽管将Elasticsearch与MySQL进行对比并不科学,但是这样的对比能区分日asticsearch 和MySQL数据库的区别,便于快速用熟悉的知识来理解Elasticsearcho所以,本节采用对比的

方式来讲解Elasticsearch。Elasticsearch与MySQL的结构对比见表13-1。

表 13-1 Elasticsearch 与 MySQL 的结构对比

ElasticSearch

MySQL

ElasticSearch

MySQL

index

database

everything is indexed

index

type

table

query dsl

sql

document

row

get url

select *from table

field

column

put url

update table set

mapping

schema

(1)关系型数据库中的数据库,相当于Elasticsearch中的索引(index )

(2) —个数据库下面有多张表(table),相当于一个索引(index)下面有多个类型(type)。

(3) —个数据库表(table)下的数据由多行(row)多列(column,属性)组成,相当于一个 type由多个文档(document)和多个field组成。

(4) 在关系型数据库中,schema定义了表、每个表的字段,还有表和字段之间的关系;     在 Elasticsearch中,mapping定义索引下的type的字段处理规则,即索引如何建立、索引类型、 是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处 理等。

(5) 在 MySQL 数据库中的增(insert )、删(delete )、改(update )、查(select)操作相当于 Elasticsearch 中的增(put/post )、删(delete )、改(update)、查(get)。

        客户端主要通过“方法(PUT/POST/GET/DELETE ) + http://ip:端口/索引名称/类型/主键” 来访问内容。

13.1.6 认识 ElasticSearchRepository

        Spring-data-elasticsearch 是 Spring 提供的操作 Elasticsearch 的数据接口,它封装了大量的基础操作,通过它可以很方便地操作Elasticsearch的数据。

        通过继承ElasticsearchRepository来完成基本的CRUD及分页操作,和普通的JPA没有什么区别。比如下面实体Product的Repository继承ElasticsearchRepository后,可以在 Elasticsearch文档中进行查找和比较等操作。具体使用方法见以下代码:

©Component
public interface ProductRepository extends ElasticsearchRepository<Product,Long> {
    Product findByld(long id);
    Product findByName(String name);
    List<Product> findByPriceBetween(double price 1, double price2);
}

  

        ElasticsearchRepository有几个特有的search方法,用来构建一些Elasticsearch查询, 主要由QueryBuilder 和 SearchQuery两个参数来完成一些特殊查询。

        实现类NativeSearchQuery实现了 QueryBuilder和SearchQuery方法,要构建复杂查询, 可以通过构建NativeSearchQuery类来实现。

        —般情况下,不是直接新建NativeSearchQuery类,而是使用NativeSearchQueryBuilder 来完成NativeSearchQuery的构建。具体用法见以下代码:

NativeSearchQueryBuilder
.withQuery(QueryBuilder1)
.withFilter(QueryBuilder2) 
.withSort(SortBuilder1) 
.withXxx().build()

  

13.1.7 认识 ElasticsearchTemplate

        ElasticsearchTemplate是Spring对Elasticsearch的API进行的封装,主要用来对索引进行仓U建、删除等操作。它继承了 Elasticsearchdperations 和 ApplicationContextAware 接口。 ElasticSearchTemplate 提供一些比 ElasticsearchRepository 更底层的方法。

ElasticsearchOperations接口中常用的方法如下。

  • createlndex()方法:创建索引,返回值为布尔类型数据。
  • indexExists()方法:查询索引是否存在,返回值为布尔类型数据。
  • putMapping()方法:创建映射,返回值为布尔类型数据。
  • getMapping()方法:得到映射,返回值为一个Map数据。
  • deletelndex()方法:删除索引,返回值为布尔类型数据。

13.1.8 认识注解@Document

        注解@Document作用于类,用于标记实体类为文档对象。

存储在Elasticsearch中的一条数据,即是一个文档,类似关系型数据库的一行数据。 Elasticsearch会索引每个文档的内容,以便搜索。它使用JSON格式,将数据存储到Elasticsearch 中,实际上是将JSON格式的字符串发送给了 Elasticsearch。

1.、document的核心元数据

document有三个核心元数据,分别是_index、_type、_id。

(1) _index;代表一个document存放在哪个index中,类似的数据放在一个索引中,非类似的数据放在不同的索引中。index中包含了很多类似的document,这些document的field很大一部分是相同的。索引名称必须小写,不能用下画线开头,不包含逗号。

(2) _type;代表document属于index的哪个类别,一个索引通常会划分为多个type,逻辑 上对index不同的数据进行分类。type名称可以是大写或小写,但是不能用下画线开头,不能包含 逗号。

(3 ) _id;代表document的唯一标识,与Jndex和_type —起可以标识和定位一个 documento默认自动创建id,也可以手动指定document的id。

2、document id的手动指定和自动生成

(1)手动指定document id。

        如果需要从某些其他系统中导入一些数据到Elasticsearch,则会采用手动指定id的形式,因为一般情况下系统中已有数据的唯一标识,可以用作Elasticsearch中的document的id

其语法格式为:

put /index/type/id
{
    "json"
}
(2)自动生成 document id。
其语法格式为:

post /index/type
{
    "json"
}

  自动生成的id长度为20个字符,URL安全、Base64编码、GUID、分布式系统并行生成时不会发生冲突。

3、document_source元数据,以及定制返回结果

        _source元数据是在创建document时放在body中的JSON数据。在默认情况下,查找数据 时会返回全部数据。如果要定制返回结果,则可以指定_source中返回哪些field

例如:

GET /_index/_type/1?_source=field

  

13.1.9 管理索引

1、创建索引

(1) 根据类的信息自动生成创建索引。

        下面代码是根据实体类创建一个名为“ec”的索引,并定义tpye是“product”。由于是单机环境,所以定义副本为0,分片为默认值5。

@Document(indexName = "product", type = "product", replicas = 0, shards = 5)
public class Product implements Serializable {
)

 代码解释如下。

  • indexName :对应索引库名称,可以理解为数据库名。必须小写,否则会报 "org.elasticsearch.indices.InvalidlndexNameException"异常。
  • type:对应在索引库中的类型,可以将其理解为“表名”。
  • shards:分片数量,默认值为5。
  • replicas:副本数量,默认值为1。如果是单机环境,则健康状态为“yellow”。如果要成为 “green”,则指定值为0即可。

(2) 手动创建索引。

    可以使用createlndex方法手动指定indexName和Settings,再进行映射。在使用前,要先注入ElasticsearchTemplate。使用方法如下。

  • 根据索引名创建索引:lasticsearchTemplate.create!ndex("indexname");
  • 根据类名创建索引:lasticsearchTemplate.createlndex(Product.class);

2、查询索引

  • 根据索引名查询:elasticsearchTemplate.indexExists("indexname");
  • 根据类名查询:elasticsearchTemplate.indexExists(Product.class);

3、删除索引

  可以根据索引名和类名对索引进行删除。

  • 根据索引名删除:elasticsearchTemplate.deletelndex("indexname");
  • 根据类名删除:elasticsearchTemplate.deletelndex(Product.class);

13.2 实例55:用ELK管理Spring Boot应用程序的日志

        ELK 是 Elasticsearch+Logstash+Kibana 的简称。

        Logstash负责将数据信息从输入端传输到输出端,比如将信息从MySQL传入Elasticsearch, 还可以根据自己的需求在中间加上滤网。Logstash提供了很多功能强大的滤网,以满足各种应 用场景。

Logstash有以下两种工作方式:

(1) 每一台机器启动一个Logstash服务,读取本地的数据文件,生成流传给Elasticsearch

(2) Logback引入Logstash包,然后直接生产JSON流,传给一个中心的Logstash服务 器,Logstash 服务器再传给 Elasticsearch。最后,Elasticsearch 将其流传给 Kibana。

        Kibana是一个开源的分析与可视化平台,和Elasticsearch —起使用。可以用Kibana搜索、 查看、交互存放在日asticsearch索弓I里的数据。使用各种不同的图标、表格、地图等,Kibana能够很轻易地展示高级数据分析与可视化。

        ELK架构为数据分布式存储、日志解析和可视化创建了一个功能强大的管理链。三者相互配合, 取长补短,共同完成分布式大数据处理工作。

本实例通过Logstash收集本地的log文件流,传输给Elasticsearch。

本实例的源代码可以在“/13/ELKDemo”目录下找到。

13.2.1 安装 Elasticsearch

(1) 通过官网下载Elasticsearch 

(2) 在下载完成后,首先将其解压到合适的目录,然后进入解压目录下的bin目录,双击 elasticsearch.bat文件启动Elasticsearch。这里需要确保安装的Java版本在1.8及以上。

(3) 访问“http://127.0.0.1:9200/”,当看到返回如下一串JSON格式的代码时,则说明已经 安装成功了。

{
"name": "1q71xef",
"cluster_name": "elasticsearch",
//省略
"tagline": "You Know, for Search"
}

  根据应用需要,还可以安装Elasticsearch必要的一些插件,如Head、kibana、IK (中文分 词)、graph

        在Elasticsearch 6.0.0或更新版本中,创建的索引只会包含一个映射类型(mapping type ) ;

在Elasticsearch 5.x中创建的具有多个映射类型的索引在Elasticsearch 6.x中依然会正常工作。

在Elasticsearch 7.0.0 中,映射类型被完全移除了。

13.2.2 安装 Logstash

1、安装 Logstash

(1) 访问 Elasticsearch 官网下载 Logstash 

(2) 将下载文件解压到自定义的目录即可。

2、配置 Logstash

(1)在解压文件的config目录下新建Iog4j_to_es.conf文件,写入以下代码:

input{
    tcp{
        host =>"localhost"
        port =>9601
        mode =>"server"
        tags =>["tags"]
##JSON格式
    codec => jsonjines
    }
}

output{
    elasticsearch{
        hosts=>"127.0.0.1:9200"
        index =>"demolog"
    }
    stdout{ codec=>rubydebug }
}

        这里一定要注意:这是UTF-8的格式,不要带BOM。如果启动时岀现错误,则可以用“logstash -f ../config/xxx.conf -tn命令检查配置文件是否错误。

(2) 新建文件run.bato 写入代码 “logstash -f ../config/log4j_to_es.conf”,保存。然后双击该配置文件,启动Logstash

(3) 访问“localhost:9600”,如出现以下内容,则代表配置成功。

{"host"":"zhonghua","version":"6.5.0","http_address":"127.0.0.1:9600","id":"03472165-2b17-4e5f-a1a1-f4 8ea4deb9a1","name":"zhonghua","build_date":"2018-11-09T19:43:40+00:00","build_sha":"4b3a404d6751 261 d155458c1 a8454a22167b1954","build_snapshot":false}

  

13.2.2 安装 Kibana

        Kibana是官方推出的Elasticsearch数据可视化工具。

(1 )通过访问Elasticsearch官网下载Kibana。

(2) 解压下载的压缩文件,进入解压目录,双击Kibana目录的bin/kibana.bat,以启动 Kibana,当出现以下提示时,代表启动成功。

       log  [08:23:47.611] [info][status][plugin:spaces@6.5.0] Status changed from yellow to green - Ready

(3) 访问localhost:5601就可以进入Kibana控制台。

        单击控制台左边导航栏的“Dev-tools”按钮,可以进入Dev-tools界面。单击“Get to work“, 然后在控制台输入“GET/_cat/health?”命令,可以查看服务器状态。如果在右侧返回的结果中看到green或yellow ,则表示服务器状态正常。

        yellow表示所有主分片可用,但不是所有副本分片都可用。如果Elasticsearch只是安装在本地,且设置了副本大于0,则会出现黄色,这是正常的状态。因为并没有分布式部署,是单节点。另外,由于Elasticsearch默认有1个副本,主分片和副本不能在同一个节点上,因此副本就是未分配(unassigned )

所以,在设计实体时可以设置@Document(indexName= "goods",type = "goods",shards = 5, yreplicas = 0),即 “repIicas=0” 就会变成 green;

13.2.4 配置 Spring Boot 项目

(1)添加项目依赖,见以下代码:

<dependency>
    <groupld>org.springframework.boot</groupld> 
    <artifactld>spring-boot-starter-log4j</artifactld> 
    <version>1.3.8.RELEASE</version>
</dependency>
<dependency>
    <groupld>net.logstash.logback</groupld>
    <artifactld>logstash-logback-encoder</artifactld>
    <version>5.3</version>
</dependency>

 

(2)添加配置文件logback.xml,这里在Spring Boot项目里添加一个配置文件,见以下代码:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="LOGSTASH"
            class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>localhost:9601</destination>
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!--encoder必须配置,有多种可选字符集 -->
        <encoder charset="UTF-8">
            <pattern>%d{HH:mm:ss.SSS} [%thread]%-5level %logger - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="LOGSTASH" />
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

 

13.2.5创建日志计划任务

        在Spring Boot项目中创建logTest类,用于测试将日志通过Logstash发送到日asticsearch, 见以下代码

©Component
public class logTest {

    private Logger logger = LoggerFactory.getLogger(logTest.class);

    @Scheduled(fixedRate = 1000)
    public void logtest() {

        logger.trace("trace 日志");
        logger.debug("debug 日志");
        logger.info("info 日志");
        logger.warn("warn 日志");
        logger.error("error 日志");
    }
}

  

(1)在入口类中添加注解@EnableScheduling,开启计划任务,然后运行项目。

(2 )访问http://localhost:5601 

        选择左侧导航栏的“Management —>Index Patterns —> Create index pattern"命令,输 入"demolog",单击"Next”按钮,选择时间过滤器字段名,单击“Create index pattern”按钮, 创建完成。

        进入Kibana的Discover,就可以查看日志信息了。

13.2.6用Kibana查看管理日志

        在Kibana的Discover页面中,可以交互式地探索自己的数据。这里可以访问与所选择的索引 默认匹配的每个索引中的文档,可以提交查询请求、过滤搜索结构,并查看文档数据,

        还可以看到 匹配查询请求的文档数量,以及字段值统计信息。

        如果选择的索引模式配置了 time字段,则文档随时间的分布将显示在页面顶部的直方图中。 Discover

        Kibana的功能非常强大,还可以进行可视化设计,创建热点图、区域图、饼图、时间线等,也 可以监控Elasticsearch的健康状态。如果安装了 APM支持,还可以进行性能监控。

13.3 实例 56:在 Spring Boot 中集成 Elasticsearch,实现增加、 删除、修改、查询文档的功能

        本实例讲解如何在Spring Boot中实现增加、删除、修改、查询文档的功能,以理解Spring Boot 的相关Starter的使用和Elasticsea「ch的知识点和具体应用。

本实例的源代码可以在"/13/ElasticsearchProductDemo”目录下找到。

13.3.1 集成 Elasticsearch

        Spring Boot 提 供 了 Starter ( spring-boot-starter-data-elasticsearch ) 来集成 Elasticsearch 

  • 优点:开发速度快,不要求熟悉Elasticsearch的一些API,能快速上手。即使之前对 Elasticsearch不了解,也能通过方法名或SQL语句快速写出自己需要的逻辑。而具体转换成API层的操作则是由框架底层实现的。
  • 缺点:使用的Spring Boot的版本对Elasticsearch的版本也有了要求,不能超过某些版本号,在部署时需要注意。如果采用API方式,则能解决这个问题。

(1 )添加依赖,见以下代码:

<!--Elasticsearch 支持-->
<dependency>
  <groupld>org.springframework.boot</groupld> 
  <artifactld>spring-boot-starter-data-elasticsearch</artifactld> 
</dependency>

  

(2)添加application.properties配置 见以下代码:

spring.data.elasticsearch.cluster-name=elasticsearch
#节点的地址。注意,API模式下端口号是9300,千万不要写成9200 
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#是否开启本地存储
spring.data.elasticsearch.repositories.enable=true

  

13.3.2创建实体

(1)创建实体。

这里根据类的信息自动生成,也可以手动指定索引名称。ElasticsearchTemplate中提供了创 建索引的API,因为进行本机测试,没做集群,所以replicas副本先设置为0。见以下代码:

//省略
//索引名称可以理解为数据库名,必须为小写,否则会报"org.elasticsearch.indices.lnvalidlndexNameException" 异常
@Document(indexName = "ec", type = "product", replicas = 0, shards = 5)
//type (类型)可以理解为表名
@Data
public class Product implements Serializable {

    /**
     * Description: @ld 注解必须是 springframework 包下的
          org.springframework.data.annotation.Id
     /
    private Long id;

    @Field(type = FieldType.Text, analyzer ="ik_max_word")//ik_max_word 使用IK分词器
    private String name;

    @Field(type = FieldType.Keyword)//在存储数据时,不会对category进行分词
    //分类
    private String category;

    //价格
    @Field(type = FieldType.Double)
    private Double price;

    @Field(index = false, type = FieldType.Keyword)// index=false,表示不建立索引
    //图片地址
    private String images;

    private String body;
}
//省略

 代码解释如下。

  • @ld注解:作用于成员变量,标记一个字段作为id主键。
  • @Field注解:作用于成员变量,标记为文档的字段,需要指定字段映射属性type。
  • index:是否索引,布尔类型,默认为true。
  • store:是否存储,布尔类型,默认为false。
  • analyzer:分词器名称,这里的ik_max_word即使用IK分词器。

(2)创建数据操作接口。

        继承ElasticsearchRepository即可创建数据操作接口,这样不用写方法,就具备了

Elasticsearch文档的增加、删除、修改和查询功能。见以下代码:

package com.example.demo.repository;
//省略
@Component
public interface ProductRepository extends ElasticsearchRepository<Product,Long> {

    Product findByld(long id);
    Product findByName(String name);
}

  

13.3.3实现增加、删除、修改和查询文档的功能

在测试类中,实现对Elasticsearch文档进行增加、删除、修改和查询的功能,见以下代码:

//省略
@SpringBootTest
@RunWith(SpringRunner.class)
public class ProductControllerTest {

    //每页数量
    private Integer PAGESIZE=10;

    @Autowired
    private ProductRepository productRepository;

    @Test
    public void save() {
        long id= System.currentTimeMillis();
        Product product = new Product(id,
            "红富士","水菓", 7.99, "/img/p1.jpg", "这是一个测试商品");
        productRepository.save(product); 
        System.out.println(product.getld());
    }

    @Test
    public void getProduct() {
        Product product = productRepository.findByName("红富士");                            
        System.out.println(product.getld()); 
    }

    @Test
    public void update() {

        long id=1557032203515L;
        Product product = new Product(id,
                   "金帅水果", 7.99, "/img/p1.jpg", "金帅也和红富士一样,非常好吃,脆脆的");
        productRepository.save(product);
    }

    @Test
    public void getProductByld() {
        Product product = productRepository.findByld(1557032203515L);                 
        System.out.println(product.getName()+product.getBody());
    }

    @Test
    public void delete() {
        long id=1557032203515L;
        productRepository.deleteByld(id);
    }

    @Test
    public void getAll() {
        lterable<Product> list = productRepository.findAll(Sort.by("id").ascending());
        for (Product product: list) {
            System.out.println(product);
        }
    }
}

  请根据自己测试“save”方法返的id值进行测试。这里的id值(1557032203515L ) 是笔者本机上得到的,读者不能用这个id值进行测试。

启动项目,运行测试getAII,控制台返回如下值:

Product(id=1557031659306, name=红富士,category二水果 price=7.99, images=/img/p1.jpg, body二这是一个 测试商品)
Product(id=1557032088459, name二金帅,category=水果,price二7.99, images=/img/p1.jpg, body=金帅也和红 富士一样,非常好吃,脆脆的)
Product(id=1557032203515, name=红富士,category=水果 price=7.99, images=/img/p1 .jpg, body二这是一个 测试商品)
Product(id=1557034189287, name=红富士,category二水果,price=7.99, images=/img/p1.jpg, body=这是一个 测试商品)

  

13.4 Elasticsearch 查询

        可以根据Spring Data提供的方法名称,实现自己想自定义的查询功能:无须写实现类,只要 继承ElasticsearchRepository接口即可。

如“findByTitle”表示根据“title”进行查询,具体方 法见表

关键词

例 子

And

findByNameAndPrice

Or

findByNameOrPrice

Is

findByName

Not

findByNameNot

Between

findByPriceBetween

关键词

例 子

LessThanEqual

findByPriceLessThan

GreaterThanEqual

findByPriceGreaterThan

Before

findByPriceBefore

After

findByPriceAfter

Like

findByNameLike

StartingWith

findByNameStartingWith

EndingWith

findByNameEndingWith

Contains/Containing

findByNameContaining

In

findByNameln(Collection<String>names)

Notln

findByNameNotln(Collection<String>names)

Near

findByStoreNear

TRUE

findByAvailableT rue

FALSE

findByAvailableFalse

OrderBy

findByAvailableTrueOrderByNameDesc

如果要查询价格在7〜8元的商品,贝!J可以在接口类加上“List〈P「oduct> findByFYiceBetween (double pricel, double price2); ” 方法,见以下代码:

@Component

public interface ProductRepository extends ElasticsearchRepository<Product,Long> { Product findByld(long id);

        Product findByName(String name);

        List<Product> findByPriceBetween(double pricel, double price2);

}

        然后,在测试类中直接使用自定义的“findByP「iceBetween”方法查询出数据,见以下代码:

@Test
public void queryByPriceBetween() {
    Iterable<Product> list = productRepository.findByPriceBetween(7.00, 8.00); 
    for (Product product: list) {
        System.out.println(product);
    }
}
其他的使用方法以此类推。

13.4.2精准查询

1、单参数 termQuery

用法见以下代码:

QueryBuilder queryBuilder = QueryBuilders.termQuery("字段名","查询值");

  它是不分词查询。因为不分词,所以汉字只能查询一个字,而多字母的英语单词算一个字。

具体实现见以下代码:

@Test
public void termQuery(){
    //构建查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new         NativeSearchQueryBuilder();
    //查询词,只能查询一个汉字,或一个英文单词     nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
//搜索,获取结果
Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
    //总条数
    for (Product product: products) {
        System.out.println(product);
    }
}

  

2、多参数   termsQuery

        terms可以提供n个查询的参数对一个字段进行查询,用法见以下代码。注意,这里是term的复数形式terms 

QueryBuilder queryBuilder=QueryBuilders.termsQuery("字段名","查询值", "查询值")

  

具体实现见以下代码:

@Test
//多参数 termsQuery
public void termsQuery() {
    //构建查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new     NativeSearchQueryBuilder(); //查询词,只能查询一个汉字或一个英文单词
        nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termsQuery("name",'富',"帅"));         
    //搜索,获取结果
    Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
    //总条数
    for (Product product: products) {
        System.out.println(product);
    }
}

  

3、分词查询   matchQuery

分词查询采用默认的分词器,用法见以下代码:

QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("字段名","查询值");

  具体实现见以下代码:

@Test
//分词查询采用默认的分词器
public void matchQuery() {
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    //查询词     
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.matchQuery("name","红士"));
    //搜索,获取结果
    Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build()); 
    for (Product product: products) {
        System.out.println(product);
    }
}

  

4、 多字段查询 multiMatchQuery

多字段查询采用multiMatchQuery方法,用法见以下代码:

QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("查询值","字段名","字段名","字段名");

  

具体实现见以下代码:

@Test
//多字段查询
public void multiMatchQuery() {
    //构建查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();     
    nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery("红富士金帅","name", "body")); 
    //搜索,获取结果
    Page<Product> products = productRepository.search(nativeSearchQueryBuilder.build());
    //总条数
    for (Product product: products) {
        System.out.println(product);
    }
}

  

13.4.3模糊查询

常见的模糊查询的方法有4种。

1.左右模糊

用法见以下代码:

QiteryBuilders.queryStringQuery("查询值").field("字段名");

  

具体实现见以下代码:

@Test
public void queryStringQuery() {
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    //左右模糊         
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.queryStringQuery("我覚得红富士好吃").field("name"));
    //搜索,获取结果
    Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build()); 
    for (Product product: products) {
        System.out.println(product);
    }
}

  

2、前缀查询  prefixQuery

        如果字段没分词,则匹配整个字段前缀,用法见以下代码:

QueryBuilders.prefixQuery("字段名","查询值");

具体实现见以下代码:

@Test
public void prefixQuery() {
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    //左右模糊
    ativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.prefixQuery("name","士"));
    //搜索,获取结果
    Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
    for (Product product: products) {
        System.out.println(product);
    }
}

  

3、通配符查询 wildcard query

        使用通配符方式进行查询,支持通配符“*”和“?”。“*”代表任意字符串,“?”代表任意一 个字符。

(1) 使用通配符 “*”。

        通配符“*”可以匹配多个值,用法见以下代码:

QueryBuilders.wildcardQuery("字段名", "查询值*");

  具体实现见以下代码:

@Test
public void wildcardQuery() {
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.wildcardQuery("name","金*"));
    //搜索,获取结果
    Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build()); 
    for (Product product: products) {
        System.out.println(product);
    }
}

  

(2)使用通配符“? ”。

通配符“?”匹配一个词,用法见以下代码:

QueryBuilders.wildcardQuery("字段名", "查?值");

  

具体实现见以下代码:

@Test
//通配符查询
public void wildcardQuery2() {
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
         
 
 nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.wildcardQuery("name","金?"));    
    //搜索,获取结果
    Page<Product> products= productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
    for (Product product: products) {
        System.out.println(product);
    }
)

  

4.分词模糊查询  fuzzy query

        分词模糊查询即匹配截取字符串为字前或后加1个词的文档,这里通过增加fuzziness (模糊) 属性来查询,fuzziness的含义是检索的term前后增加或减少n个词的匹配查询。

用法见以下代码:

QueryBuilders.fuzzyQuery("字段名","查询值").fuzziness(Fuzziness.ONE);

  具体实现见以下代码:

@Test
public void fuzzyQuery(){
    //查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder
    = new NativeSearchQueryBuilder(); 
        
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.fuzzyQuery("name","士")
            .fuzziness(Fuzziness.ONE));
    //搜索,获取结果
    Page<Product> products= productRepository.search (nativeSearchQueryBuilderQueryBuilder.build());
    for (Product product: products){
        System.out.println(product);
    }
}

  

5.相似内容推荐

        相似内容的推荐是给定一篇文档信息,然后向用户推荐与该文档相似的文档。通过 Elasticsearch的More like this查询接口,可以非常方便地实现基于内容的推荐,

用法见以下代码:

 QueryBuilders.moreLikeThisQuery(new String[] {"字段名"}).addLikeText("查询值");

如果不指定字段名,则默认全部,常用在相似内容的推荐上。

13.4.4范围查询

将文档与具有一定范围内字词的字段进行匹配,用法如下。

  • 闭区间查询:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").from(“值 1").to("值 2");
  • 开区间查询:QueryBuilder queryBuilder = QueryBuilders.rangeQuery(”字段名”).from("值 2").includeUpper(false).includeLower(false);//默认是true,也就是包含
  • 大于:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").gt("查询值”);
  • 大于或等于:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").gte(" 查询值,
  • 小于:QueryBuilderqueryBuilder = QueryBuilders.rangeQuery(”字段名”).lt(”查询值”);
  • 小于或等于:QueryBuilder queryBuilder = QueryBuilders.rangeQuery("字段名").lte(" 查询值”);

13.4.5组合查询

组合查询是可以设置多个条件的查询方式,用来组合多个查询,有4种方式。

  • must:代表文档必须完全匹配条件,相当于and,会参与计算分值。
  • mustnot:代表必须不满足匹配条件。
  • filter:代表返回的文档必须满足filter条件,但不会参与计算分值。
  • should:代表返回的文档可能满足条件,也可能不满足条件,有多个should时满足任何一 个就可以,相当于or,可以通过minimum_should_match设置至少满足几个。

13.4.6分页查询

使用NativeSearchQueryBuilder实现分页查询,用法见以下代码:

@Test
public void termQuery() {
    //分页
    int page = 0;
    //每页文档数
    int size=5;
    //构建查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    //查询词,只能查询一个汉字,或一个英文单词     
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
    //搜索,获取结果 nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(page, size));
    Page<Product> products =
productRepository.search(nativeSearchQueryBuilderQueryBuilder.buildO);
    //总条数
    for (Product product: products) {
        System.out.println(product);
    }
}

  

如果要逬行排序,只要在分页查询上构建withSort参数即可,用法见以下代码:

@Test
//分页查询+排序
public void searchByPageAndSort() {
    //分页:
    int page = 0;
    //每页文档数
    int size = 5;
    //构建查询条件
    NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();
    //查询词,只能查询一个汉字,或一个英文单词     
    nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.termQuery("name","富"));
    //搜索,获取结果         
 nativeSearchQueryBuilderQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)); 
    nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(page, size));
    Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build());
    //总条数
    for (Product product: products) {
        System.out.println(product);
    }
}

 

13.4.7 聚合查询

        聚合(aggregation )是Elasticsearch的一个强大功能,可以极其方便地实现对数据的统计、 分析工作。搜索是查找某些具体的文档,聚合就是对这些搜索到的文档进行统计,可以聚合出更加细致的数据。它有两个重要概念。

 

  • Bucket (桶/集合):满足特定条件的文档的集合,即分组。
  • Metric (指标/度量):对桶内的文档进行统计计算(最小值、最大值),简单理解就是进行 、A-A-运算。

聚合由AggregationBuilders类来构建,它提供的静态方法见表13-3。

功 能

语 法

统计数量

ValueCountBuilder vcb= AggregationBuilders.count("count id").field("id");

去重统计数量

CardinalityBuilder cb= AggregationBuilders.cardinality("distinct count id").field("id");

聚合过滤

FilterAggregationBuilder fab二 AggregationBuilders.filter("id_filter").filter(QueryBuilders.query

StringQuery("id:1"));

按字段分组

TermsBuilder tb= AggregationBuilders.terms("group name").field("name");

求和

SumBuilder sumBuilder= AggregationBuilders.sum("suin price").field("price");

求平均值

AvgBuilder ab= AggregationBuilders.avg("avg price").field("price");

求最大值

MaxBuilder mb= AggregationBuilders.max("max price").field("price");

求最小值

MinBuilder min 二 AggregationBuilders.min("min price").field("price");

按日期间隔分组

DateHistogramBuilder dhb二 AggregationBuilders.dateHistogram("dhb dt").field("date");

获取聚合结果

TopHitsBuilder thb二 AggregationBuilders.topHits("top hit result");

嵌套的聚合

NestedBuilder nb= AggregationBuilders.nested("negsted quests").path("quests");

反转嵌套

AggregationBuilders.reverseNested("res negsted").path("kps ");

具体用法见以下代码:

//测试桶
public String searchBybucket() {
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();         
    queryBuilder.withSourceFilter(new FetchSourceFilterfnew String[]{""}, null));
    //指定索引的类型,只先从各分片中查询匹配的文档,再重新排序和排名,取前size个文档
    queryBuilder.withSearchType(SearchType.QUERY_THEN_FETCH);
    //指定要查询的索引库的名称和类型,其实就是文档©Document中设置的indedName和type     
    queryBuilder.withlndices("goods").withTypes("goods");
    //添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand     
    queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
    //查询,需要把结果强转为AggregatedPage类型,AggregatedPage:聚合查询的结果类。它是Page<T> 的子接口
    AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>) 
    goodsRepository.search(queryBuilder.build());
    //从结果中取出名为brands的聚合解析
    //强转为St「ingTerm类型
    StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
    //获取桶
    List<StringTerms.Bucket> buckets = agg.getBuckets();
    //遍历
    for (StringTerms.Bucket bucket: buckets) {
        //获取桶中的key
        System.out.println(bucket.getKeyAsString());
        //获取桶中的文档数量
        System.out.println(bucket.getDocCount());
    }
    return buckets;
}

  还可以嵌套聚合,在聚合AggregationBuilders中使用subAggregation,用法见以下代码:

queryBuilder.addAggregation(
        AggregationBuilders.terms("brands").field("brand")
        .subAggregation(AggregationBuilders.avg("price_avg").field("price")) //在品牌聚合桶内进行嵌套聚合);

  这里一定要注意Spring Boot和Elasticsearch的版本是否对应。

13.5 实例57:实现产品搜索引擎

        本实例通过实现一个产品信息的搜索引擎来帮助读者理解本章所讲的知识点及具体使用。创建 实体和接口的方法在前面小节中已经讲解过,本节只讲解创建控制器实现搜索API和搜索的视图展 示方法。

本实例的源代码可以在,713/ElasticsearchProductSearch”目录下找到。

(1 )创建搜索控制器,用于构建搜索框架,见以下代码:

〃省略
©Controller
public class Searchcontroller {
    @Autowired
    private ProductRepository productRepository;

    @GetMapping("search")
    public ModelAndView searchByPageAndSort(lnteger start,String key) (
        //分页:
        if (start == null) {
            start = 0;
        }
        int size=2;〃每页文档数
        //构建查询条件
        NativeSearchQueryBuilder nativeSearchQueryBuilderQueryBuilder = new NativeSearchQueryBuilder();         
        //nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.matchQuery("name", key));
    
 nativeSearchQueryBuilderQueryBuilder.withQuery(QueryBuilders.multiMatchQuery(key,"name",
"body"));
        /搜索,获取结果
        nativeSearchQueryBuilderQueryBuilder.withSort(SortBuilders.fieldSort("id")
                .order(SortOrder.DESC));
        nativeSearchQueryBuilderQueryBuilder.withPageable(PageRequest.of(start, size));
        Page<Product> products = productRepository.search(nativeSearchQueryBuilderQueryBuilder.build()); 
        //总条数 
        for (Product product: products) {
            System.out.println(product);
        }
        ModelAndView mav = new ModelAndView("search");
        mav.addObject("page", products);
        mav.addObject("keys", key);
        return mav;
    }
}

  

(2)创建显示视图。

创建用于展示数据的前端页面,具体见以下代码:

<!—省略...一〉
<body>
<div class ="contain。「一 fluid"〉
<div><span >搜索词:</span><span th:text="$(keys)">mav</span></div>
<div class="row-fluid">
<div class="span12">
<div th:each="item : $(page.content}">
<span th:text=,,$(item.id}">id</span>
<span th:text="$(item.name}">name</span> <span th:text="$(item.body}">body</span></div>
</div>
<div>
<a th:href=,,@{/search(key=${keys},start=O,)}n>[MZS]</a>
<a th:if="$(not page.isFirst())" th:href="@(/search(key=${keys},start=${page.number-1 })}">[± 页]<7a>
<a th:if="$(not page.isLast())" th:href="@(/search(key=${keys),start=${page.number+1})}">[T 页]</a>
<a th:href="@(/search(key=${keys},start=$(page.totalPages-1})}">[^^]</h>
</div>
</div>
</div>
</body>
</html>

  

13.6 Solr—搜索应用服务器

13.6.1 了解 Solr

Solr是一个独立的企业级搜索应用服务器,对外提供API接口。用户可以通过HTTP请求向 搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过HTTP GET操作提出查找请求, 并得到XML格式的返回结果。Solr现在支持多种返回结果。

13.6.2安装配置Solr

1、Solr安装

(1 )访问镜像网站,下载Solr压缩包。

(2)在下载完成后解压文件,在"cmd”控制台进入"solr/bin”目录下,输入“solr start” 命令启动Sol「o

如果出现以下提示,则表示成功启动。

INFO - 2019-05-09 10:30:09.043; org.apache.solr.util.configuration.SSLCredentialProviderFactory; Processing SSL Credential Provider chain: env;sysprop
Waiting up to 30 to see Solr running on port 8983
Started Solr server on port 8983. Happy searching!

  

(3)访问 uhttp://localhost:8983/solr/#/n,就可以看到已经启动了。

常用命令如下。

  • 停止:Msolr stop -p 8983 ” 或 “sole stop - oil”。
  • 查看运行状态:sol「status。

2、Solr配置

(1 )进入Solr的安装目录下的server/solr/,创建一个名字为new_core的文件夹。

(2)将 conf 目录(在安装目录/servei7soli7configsets/sample_techproducts_configs 下) 复制到new_core目录下。

(3 )访问 “http://localhost:8983/sol「/#/”。

单击导航栏的“Core Admin”,在弹出窗口中单击“Add Core”命令,弹出如图13-8所示 的对话框,输入名字和目录名,再单击“AddCore”按钮,完成创建。

13.6.3 整合 Spring Boot 和 Solr

(1) 添加依赖,见以下代码:

<dependency>
    <groupld>org.springframework.boot</groupld>
    <artifactld>spring-boot-starter-data-solr</artifactld>
</dependency>

  

(2) 写入Solr配置。

在application.properties文件中进行Sol「配置,写入下面配置信息即可。

spring.data.solr.host=http://localhost:8983/solr/new_core

  

13.7 实例58:在Sping Boot中集成Solr,实现数据的增加、删除、 修改和查询

本实例在Sol「中实现数据的增加、删除、修改和查询。

本实例的源代码可以在“/13/Solr”目录下找到。

13.7.1 创建 User 类

User类必须继承可序列化接口,见以下代码:

@Data
public class User implements Serializable {
    @Field("id")
    //使用这个注释,里面的名字是根据Solr数据库中的配置来决定的 
    private String id;

    @Field("name") 
    private String name;
}

  

13.7.2测试增加、删除、修改和查询功能

(1) 测试增加功能。

构造一篇文档,实例化一个对象,向Solr中添加数据,见以下代码:

@Test
public void addUser() throws lOException, SolrServerException { 
    User user = new User(); 
    user.setld("8888888"); 
    user.setName("龙知然"); 
    solrClient.addBean(user);
    solrClient.commit();
}

  

(2) 测试增加功能,根据id查询刚刚添加的内容,见以下代码:

@Test
public void getByldFromSolr() throws lOException, SolrServerException { 
    //根据id查询内容
    String id = "8888888";
    SolrDocument solrDocument = solrClient.getByld(id);
    //获取 filedName
    Collection<String> fieldNames = solrDocument.getFieldNames();
    //获取file名和内容
    Map<String, Object> fieldValueMap = solrDocument.getFieldValueMap();
    List<SolrDocument> childDocuments = solrDocument.getChildDocuments();
    String results = solrDocument.toString();
    System.out.println(results);
}

  

运行测试,控制台输出如下结果:

SolrDocument{id=8888888, name=龙知然,version = 1633023077954617344}

 

(3) 测试修改功能,根据id修改内容,见以下代码 

@RequestMapping("/updateUser")
public void updateUser() throws lOException, SolrServerException {
    User user = new User();
    user.setld("8888888");
    user.setName("知然");
    solrClient.addBean(user);
    soIrClient.commit();
}

  

修改之后的值如下:

Solr Document{id=8888888, name=知然,_version_=1633023690698391552}

  

可以看到,内容已经变化,所谓Sol「的更新操作,就是对相同id的文档重新添加一次。修改之 后,Version变得不一样了。

(4) 测试删除功能,根据id删除内容,见以下代码:

@Test
public void delByld() throws lOException, SolrServerException {
    //根据id删除信息
    UpdateResponse updateResponse = solrClient.deleteByld("8888888");
    //执行的时间
    long elapsedTime = updateResponse.getElapsedTime();
    int qTime = updateResponse.getQTime();
    //请求地址
    String requestUrl = updateResponse.getRequestUrl();
    //请求的结果{responseHeader=(status=0,QTime=2)}
    NamedList<Object> response = updateResponse.getResponse();
    //请求结果的头(status=0,QTime=2)
    NamedList responseHeader = updateResponse.getResponseHeader();
    //请求的状态0
    solrClient.commit();
    int status = updateResponse.getStatus();
    //成功,则返回0,如果没有文档被删除,也会返回0,代表根本没有
}

  

删除全部可以用以下代码:

solrClient.deleteByQuery("*:*");

  

(5) 实现文档高亮显示,见以下代码:

@Test
public void queryAII() throws lOException, SolrServerException {
    SoIrQuery soIrQuery = new SoIrQuery();
    〃设置默认搜索域
    solrQuery.setQuery("*:*");
    soIrQuery.set("q", "知然"); 
    solrQuery.add("q", "name:然");
    〃设置返回结果的排序规则
    soIrQuery.setSort("id", SolrQuery.ORDER.asc);
    〃设置查询的条数
    solrQuery.setRows(50);
    〃设置查询的开始
    solrQuery.setStart(0);
    //设置分页参数
    solrQuery.setStart(0); 
    solrQuery.setRows(20);
    〃设置高亮
    solrQuery.setHighlight(true);
    〃设置高亮的字段
    solrQuery.addHighlightField("name");
    〃设置高亮的样式
    solrQuery-setHighlightSimplePre("<font color='red'>");           
    solrQuery.setHighlightSimplePost("</font>");
    System.out.println(solrQuery);
    QueryResponse response = solrClient.query(solrQuery);
    //返回高亮显示结果
    Map<String, Map<String, List<String>>> highlighting = response.getHighlighting();         
    //response.getResults();查询返回的结果
    SoIrDocumentList documentList = response.getResults();
    long numFound = documentList.getNumFound();
    System.out.println("共查询到的文档数量:"+ numFound);
    for (SoIrDocument soIrDocument: documentList) { 
        System.out.println(solrDocument);
        System.out.println(solrDocument.get("name"));
    }
    System.out.println(highlighting);
}

  

运行上面代码,在控制台中输出如下结果:

q=知然&q=name:然&sort=id+asc&rows=20&start=0&hl=true&hl.fl=name&hl.simple.pre=<font+color='red,>
&hl.simple.post=</font>
总共查询到的文档数量:3
SolrDocument(id=ld3cbb8a541 b45759b 1 a59a86ddd0f9b, name=龙知然 4,
_version_=l 633022994022400000)
茏知然4
SolrDocument {id=3 ddb0995b0c04fc0be3 c34612c33992c, name=龙知然 4, _version_=1633022357082734594}
茏知然4
SolrDocument{id=bb37d6fI96ad43bc8654f29f2e9f389f, name=龙知然 4, sex=男,address=武汉 4, _version_=1626411498284777473)
茏知然j
(1 d3cbb8a541 b45759b 1 a59a86ddd0f9b= {name=[^.<fbnt coloi^'red'>^p</fbntxfbnt colorTedA 然 </fbnt>4]), 3ddb0995b0c04fc0be3c34612c33992c= {name=[^.<fbnt coloi=Yed^^p^fbntXfbnt coloi='red^^^/fbnW]), bb37d6f!96ad43bc8654f29f2e9f389仁{name=[龙 vfbnt coloi^'red^^p^fontxfbnt color='red' >然 </fbnt>4]}}

  

从〈font color='red‘>然</font>中可以看出,查询结果已经高亮化了,对查询关键词输岀红色 字体。

13.8 对比 Elasticsearch 和 Solr

1. Elasticsearch和Solr的市场关注度

2. Elasticsearch 和 Solr 的优缺点

(1 ) Solr的优点。

Solr有一个更大' 更成熟的用户' 开发和贡献者社区。

  • 支持添加多种格式的索引,如:HTML. PDF、微软Office系列软件格式,以及JSON、 XML、CSV等纯文本格式。
  • 比较成熟、稳定。
  • 搜索速度更快(不建索引时)。
  • Soli•利用Zookeeper进行分布式管理,而Elasticsearch自身带有分布式协调管理功能。 如果项目本身使用了 Zookeeper,那Solr可能是最好选择。有时缺点在特点场景下可能会 变成优点。
  • 如果项目后期升级,要朝着Hadoop这块发展,当数据量较大时,用Hadoop处理数据, Solr可以很方便地与Hadoop结合。

(2) Elasticsearch 的优点。

  • Elasticsearch本身是分布式、分发实时的,不需要其他组件。
  • Elasticsearch完全支持Apache Lucene的接近实时的搜索。
  • 它处理多用户不需要特殊配置,而Solr则需要更多的高级设置。
  • Elasticsearch采用Gateway的概念,备份更加简单。各节点组成对等的网络结构,某节 点出现故障会自动分配其他节点代替其进行工作。

(3) Solr的缺点。 

  • 建立索引时,搜索效率下降,实时索引搜索效率不高。
  • 实时搜索应用效率明显低于Elasticsearch。

(4 ) Elasticsearch 的缺点。

  • 没有Solr的生态系统发达。
  • 仅支持JSON文件格式。
  • 本身更注重核心功能,高级功能多由第三方插件提供。

总结:Solr是传统搜索应用的有力解决方案,但Elasticsearch更适用于新兴的实时搜索应用。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐