搜索引擎Solr详解:从0开始搭建、维护及各类使用

一. Solr简介及入门

1.1 什么是搜索引擎

1.1 什么是搜索引擎?

  • 专业角度:
    • 搜索引擎(Search Engine)是指根据一定的策略、运用特定的计算机程序从互联网上回搜集信息,在对信答息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
    • 所谓搜索引擎,就是根据用户需求与一定算法,运用特定策略从互联网检索出制定信息反馈给用户的一门检索技术。搜索引擎依托于多种技术,如网络爬虫技术、检索排序技术、网页处理技术、大数据处理技术、自然语言处理技术等,为信息检索用户提供快速、高相关性的信息服务。搜索引擎技术的核心模块一般包括爬虫、索引、检索和排序等,同时可添加其他一系列辅助模块,以为用户创造更好的网络使用环境。
    • 一个搜索引擎由搜索器 、索引器 、检索器 和用户接口 四个部分组成。搜索器的功能是在互联网 中漫游,发现和搜集信息。索引器的功能是理解搜索器所搜索的信息,从中抽取出索引项,用于表示文档 以及生成文档库的索引表。检索器的功能是根据用户的查询在索引库中快速检出文档,进行文档与查询的相关度评价,对将要输出的结果进行排序,并实现某种用户相关性反馈机制。用户接口的作用是输入用户查询、显示查询结果、提供用户相关性反馈机制。
  • 通俗来说,搜索引擎就是协助用户完成搜索指定内容需求的一个工具。它拥有及其优良的查询性能,优秀的查询适配功能,能够满足大数据中迅速搜索目标数据的能力。
  • 常见的搜索有: 百度、谷歌等。他们是最终落地的产品,其内部借助的搜索引擎则是其核心。谷歌搜索就使用到了ElasticSearch作为核心进行搜索。作为更加老牌的搜索引擎产品Solr,也值得学习。

1.2 什么是solr

  1. Solr是一个开源搜索平台,用于构建搜索应用程序。 它建立在Lucene(全文搜索引擎)之上。 Solr是企业级的,快速的和高度可扩展的。 使用Solr构建的应用程序非常复杂,可提供高性能。
  2. Solr是一个发展中的,不断演进优化的产品,它虽然老牌,但仍跟随时代。比如自带Netty容器,不需要再继续安装Tomcat才能运行了。支持Solr集群,支持并行查询等。
  3. Solr是一个Restful 风格的全文检索引擎,所以它可以在任何语言上运行。它使用Java基于Lucene开发的全文检索服务,是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。
  4. Solr(读作“solar”)是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。
  5. Solr是用Java编写、运行在Servlet容器(如 Apache Tomcat 或Jetty)的一个独立的全文搜索服务器。 Solr采用了 Lucene Java 搜索库为核心的全文索引和搜索,并具有类似REST的HTTP/XML和JSON的API。Solr强大的外部配置功能使得无需进行Java编码,便可对 其进行调整以适应多种类型的应用程序。Solr有一个插件架构,以支持更多的高级定制。
  6. 因为2010年 Apache Lucene 和 Apache Solr 项目合并,两个项目是由同一个Apache软件基金会开发团队制作实现的。提到技术或产品时,Lucene/Solr或Solr/Lucene是一样的。

1.3 Solr与其他产品对比

  1. 与数据库相比:
    • 倒排索引,预先分词,相比于数据库,join和like本身就是性能杀手,所以它在大部分场景下查询效率更快。
    • 地理GPS查询、高亮搜索、高级聚合查询,它能够完成数据库有些做得不够好或者做不到的地方。
    • 基于Restful风格,兼容性高,可扩展性强,比MySQL更易于使用。
  2. 与ES对比:
    • 当实时建立索引的时候,solr会产生io阻塞,而es则不会,es查询性能要高于solr。
    • 在不断动态添加数据的时候,solr的检索效率会变的低下,而es则没有什么变化。
    • Solr利用zookeeper进行分布式管理,而es自身带有分布式系统管理功能。Solr一般都要部署到web服务器上,比如tomcat(新版本已自带Web服务器了,可以直接解压后启动)。启动tomcat的时候需要配置tomcat与solr的关联。【Solr 的本质 是一个动态web项目】
    • Solr支持更多的格式数据[xml,json,csv等],而es仅支持json文件格式。
    • Solr是传统搜索应用的有力解决方案,但是es更适用于新兴的实时搜索应用。
      单纯的对已有数据进行检索的时候,solr效率更好,高于es。
    • Solr官网提供的功能更多,而es本身更注重于核心功能,高级功能多有第三方插件

    ES更适合动态数据处理,海量数据处理。而Solr更适合复杂,多情况的处理,因为它原生提供的功能更多。综合来讲,ES要优于Solr,但是Solr也是一个不错的选择。

1.4 Solr的特点、优缺点、使用场景

  • 特点:
    1.基于标准的开放接口:Solr搜索服务器支持通过XML、JSON和HTTP查询和获取结果。
    2.易管理:Solr可以通过HTML页面管理,Solr配置通过XML完成。
    3.可伸缩性:能够有效地复制到另外一个Solr搜索服务器。
    4.灵活的插件体系:新功能能够以插件的形式方便的添加到Solr服务器上。
    5.强大的数据导入功能:数据库和其他结构化数据源现在都可以导入、映射和转化。
  • 优缺点:
    • 优点:
    1. 软件成熟,有广大的用户群体,活跃的社区,更新迭代的团队。
    2. 支持多种格式的索引,如: HTML、PDF、微软 Office 系列软件格式以及 JSON、XML、CSV 等纯文本格式。
    3. 功能丰富,满足多种业务场景,是传统搜索应用的有力解决方案。它支持GPS经纬度查询(可实现附近的数据,经纬度范围内的数据等),高亮查询,各类聚合查询等
    • 缺点:
    1. 建立索引时,搜索效率下降,实时索引效率不高。
    2. 相比于ES,竞争力稍弱。
    3. 随着数据量的增加,Solr的搜索效率会变得更低,而ElasticSearch却没有明显的变化。
  • 使用场景:
  1. 传统搜索应用的有力助手,如租房平台的多条件房源搜索(附近的房源,商圈内房源,按租金,关键词,户型,朝向,套几等等关键词搜索)
  2. 可服务于数据量不是特别大,更新频率不是特别快的场景
  3. 另外可根据Solr的优缺点来看,优点就是适合的场景,缺点就是不适合的场景。

w3c的solr文档:https://www.w3cschool.cn/solr_doc/

二. Solr搭建及维护

2.1 Windows端搭建Solr

  1. 进入官网 选择下载合适的版本,可选择下载较为新的版本,此处以 Solr 7.7.2为例
  2. 解压后进入cmd进入bin目录执行 solr start命令,命令行显示如下,启动成功,默认端口8983,也可通过-p指定端口启动:
    在这里插入图片描述
  3. 打开浏览器,输入:
    http://localhost:8983/solr
    
    出现如下界面后表示安装成功:
    在这里插入图片描述

Windows需要安装JDK

2.2 Linux端搭建Solr教程

  1. 确认服务器中是否有Jdk等环境支持。
  2. 下载Solr:
    wget http://mirror.bit.edu.cn/apache/lucene/solr/7.6.0/solr-7.6.0.tgz
    
  3. 解压solr:
    tar -xzvf solr-7.6.0.tgz
    
  4. 输入命令运行Solr:
    bin/solr start -force
    
  5. 输入命令停止运行Solr:
    bin/solr stop -force
    

    也可以直接杀死进程

  6. 输入命令重启solr:
    bin/solr restart -force
    

2.3 Solr的管理界面的使用

2.3.1 Dashboard(仪表盘)

  • 它是一个监控界面,可以直观的看到JVM的运行参数,虚拟机内存使用情况,Solr版本,系统内存等信息:
    在这里插入图片描述

2.3.2 Logging(日志)

  • 它是关于日志以及日志级别相关的,Logging里面显示的是Solr运行出现的异常或错误:
    在这里插入图片描述

2.3.3 Core Admin(core管理)

  • 它是整个库的核心。在这里面:
    • Add Core:添加新的库(也可以叫核心、或者Core)
    • Reload:重新加载指定的库
    • Unload:卸载库(此功能慎用0.0)
    • Rename: 重新命名库
    • Optimize:优化索引库
      它里面还有别的一些功能以及展示一些别的信息:
      在这里插入图片描述
  • 新增索引库时,可注意如下名称含义:
    1. name: 给core起的名字
    2. instanceDir: 与我们在配置solr到tomcat里时的solr_home里新建的core文件夹名一致;
    3. dataDir:确认Add Core时,会在new_core目录下生成名为data的文件夹
    4. config:new_core下的conf下的config配置文件(solrconfig.xml)
    5. schema: new_core下的conf下的schema文件(schema.xml)

确认Add Core时,会在new_core下生成data文件夹,与core.properties文件。

2.3.5 Core新增索引库的方法

  • windows下:
  1. 打开dos命令窗口,切换目录到${solr.home}\bin,然后输入:solr create -c corename之后回车;
  2. 打开solr安装文件,在/server/solr下就会出现新的文件夹corename(就是新创建的core);
  3. 打开浏览器,输入solr访问路径:http://localhost:8983/solr,就会看到新建的core
  • Linux下:
  1. 直接在/server/solr下创建新文件夹,名字自定义,此处命名为newcore,作为新建的core;
  2. 找到/server/solr/configsets/_default目录下的conf文件夹,然后拷贝一份到/server/solr/newcore目录节点下。
  3. 然后按照下图操作
    在这里插入图片描述

2.3.6 Java Properties(Java 属性)

  • 顾名思义,它展示了一些Java的配置:
    在这里插入图片描述

2.3.7 Thread Dump(相关线程)

在这里插入图片描述

2.4 Core Selector

  • Core Selector 是对Solr中的数据进行操作、分析等。此章节将为大家讲解此模块的各个菜单:
    在这里插入图片描述
  • 点击具体的Core后,出现如下界面:
    在这里插入图片描述
  • 选择 core
    在这里插入图片描述
    如图所示,house_core是我们自己定义的一个core。在Solr中每一个Core代表一个索引库,里面包含索引数据及其配置信息。
    Solr中可以拥有多个Core,也就是同时管理多个索引库,就像mysql中可以有多个数据库一样。我们使用solr的语法就可以访问core中的数据了。solr core内的数据支持增删查改。下一章节会举例说明;
  • Overview(概览): 能够查看我们的运行实例信息
    在这里插入图片描述
  • Analysis(分析): 通过使用Analysis,我们能够实时的分析我们的语句解析情况。如使用ik分词器,检测内容:“我是中国人”的分词结果如图:
    在这里插入图片描述
  • Dataimport(从数据库导入数据):前提是已经配置好了相关的配置,则可以使用此功能进行数据库导入。有兴趣的可以百度了解。此处步骤暂略。
    在这里插入图片描述
    • Comman选项:full_import:全导入;delta_import:增量导入。
      所谓delta-import主要是对于数据库(也可能是文件等等)中增加或者被修改的字段进行导入。主要原理是利用率每次我们进行import的时候在solr.home\conf下面生成的dataimport.properties文件,此文件里面有最近一次导入的相关信息。这个文件如下:
      #Tue Jul 19 10:15:50 CST 2016
      advertiser.last_index_time=2016-07-19 10:15:49
      last_index_time=2016-07-19 10:15:49
      其实last_index_time是最近一次索引(full-import或者delta-import)的时间。
      通过比较这个时间和我们数据库表中的timestamp列即可得出哪些是之后修改或者添加的。
    • Verbose:
      Clean: 在索引开始构建之前是否删除之前的索引,默认为true
      Commit: 在索引完成之后是否提交。默认为true
      Optimize: 是否在索引完成之后对索引进行优化。默认为true
      Debug: 是否以调试模式运行,适用于交互式开发(interactive development mode)之中。请注意,如果以调试模式运行,那么默认不会自动提交,请加参数“commit=true”
      Entity: entity是document下面的标签(data-config.xml)。使用这个参数可以有选择的执行一个或多个entity 。使用多个entity 参数可以使得多个entity同时运行。如果不选择此参数那么所有的都会被运行。
      Start,Rows:分页
      Custom Parameters:导入条件
      Excute:执行导入。
      Refresh Status:刷新后才能看到数据发生了变化,如果刷新后数据还是0,说明未导入。
  • Document(文档操作):(索引文档)索引的相关操作,如:增加,修改,删除等,例如我们要增加一个索引(companyName)的办法:
  1. 先要在solr 的D:\solr_home\mycore1\conf 的 schema.xml配置文件下,增加相关的字段field
    	<field name="companyName" type="text_ik" indexed="false" stored="true" multiValued="false" />
    

    新版本solr可能没有schema.xml的话,可在要配置的core的相应conf 内寻找managed-schema 文件

  2. 使用 Core Admin -> Reload命令进行重载或者对solr服务器重启,刷新索引:
    在这里插入图片描述
  3. 在如下页面,选择/update ,文档格式选择json ,然后submit 提交。这样 索引就增加上了。修改与增加一样,都是/update ,删除为/delete 。成功之后,我们去query里查询数据就能查到我们刚添加的数据.
    在这里插入图片描述

    如果此处的advertiserId与advertiserType没有配置field 的话,可根据后面的教程配置,或者去掉这两个再进行尝试新增、修改、删除功能;

  • File: 能够查看服务器中solr的文件信息,并可进行配置:
    在这里插入图片描述Request-Handler(qt): 要进行的操作(update\delete)
    Document Type:类型,有JSON、XML等格式
    Document(s): 内容,手动写的内容。
    Commit Within: 在内部提交
    Overwrite: 为true,说明如果id重复则覆盖以前的值;为false说明如果id重复不覆盖以前的值.
    Boost: 好像是什么版本,没用过
  • Ping: 网络延迟,按一下可以看这个Core还是不是活着的以及响应时间。

    点击Ping按钮相当于发送一个请求到对应的Core并获得答复。对应的http API是 /admin/ping (http://localhost:8983/solr//admin/ping),后端的处理逻辑是PingRequestHandler类

  • Plugins/Stats: Solr自带的一些插件以及我们安装的插件的信息以及统计。Plugins页面展示的是在Core下运行的各种插件的统计数据和状态信息。比如,可以找到Solr Cache的性能统计数据,Solr Searcher的状态,Request Handler和Search Component的配置信息等等。
  • Query:一个简单的结构化查询工具,下一节详细介绍
  • Replication: 显示你当前Core的副本,并提供disable/enable功能。在单机环境下可进行实例与实例之间的配置。在实例之间复制数据,保证数据的可用性,从而提高系统的服务能力。

    为Master-slave的副本工作方式提供了一个管理和查看副本的页面,SolrCloud中已经取代了这个页面上的大部分功能,单机版也无需关注该功能。(如果使用的是SolrCloud,不要使用该页面上的DisableReplication功能。)

  • schema:展示该Core的shema数据,如果是用ManagedSchema模式,还可以通过该页面修改,增加,删除schema的字段。(对比Solr4,是一个比较实用的提升。)
  • Segments info: 展示底层Lucence的分段信息。展示底层Lucence索引段,包括每个段的大小(字节大小和数据条数)以及其他的一些基本元数据信息,最显眼的是deleted documents数量,把鼠标移动到段上可以看到更多的数据信息。这些信息可以帮管理员做性能优化,优化该数据集的合并段的设置。

2.4 Query的查询详解

q: 查询字符串(必须的)。*:*表示查询所有;keyword:东看 表示按关键字“东看”查询
fq: filter query 过滤查询。使用Filter Query可以充分利用Filter Query Cache,提高检索性能。作用:在q查询符合结果中同时是fq查询符合的(类似求交集),例如:q=mm&fq=date_time:[20081001 TO 20091031],找关键字mm,并且date_time是20081001到20091031之间的。
sort: 排序。格式如下:字段名 排序方式;如advertiserId desc 表示按id字段降序排列查询结果。
start,rows:表示查回结果从第几条数据开始显示,共显示多少条。
fl: field list。指定查询结果返回哪些字段。多个时以空格“ ”或逗号“,”分隔。不指定时,默认全返回。
df: default field默认的查询字段,一般默认指定。
Raw Query Parameters:
wt: write type。指定查询输出结果格式,我们常用的有json格式与xml格式。在solrconfig.xml中定义了查询输出格式:xml、json、python、ruby、php、phps、custom。
indent: 返回的结果是否缩进,默认关闭,用 indent=true | on 开启,一般调试json,php,phps,ruby输出才有必要用这个参数。
debugQuery: 设置返回结果是否显示Debug信息。
dismax:
edismax:
hl: high light 高亮。hl=true表示启用高亮
hl.fl : 用空格或逗号隔开的字段列表(指定高亮的字段)。要启用某个字段的highlight功能,就得保证该字段在schema中是stored。如果该参数未被给出,那么就会高 亮默认字段 standard handler会用df参数,dismax字段用qf参数。你可以使用星号去方便的高亮所有字段。如果你使用了通配符,那么要考虑启用 hl.requiredFieldMatch选项。
hl.simple.pre
hl.requireFieldMatch: 如果置为true,除非该字段的查询结果不为空才会被高亮。它的默认值是false,意味 着它可能匹配某个字段却高亮一个不同的字段。如果hl.fl使用了通配符,那么就要启用该参数。尽管如此,如果你的查询是all字段(可能是使用 copy-field 指令),那么还是把它设为false,这样搜索结果能表明哪个字段的查询文本未被找到
hl.usePhraseHighlighter:如果一个查询中含有短语(引号框起来的)那么会保证一定要完全匹配短语的才会被高亮。
hl.highlightMultiTerm:如果使用通配符和模糊搜索,那么会确保与通配符匹配的term会高亮。默认为false,同时hl.usePhraseHighlighter要为true。
facet:分组统计,在搜索关键字的同时,能够按照Facet的字段进行分组并统计。
facet.query:Facet Query利用类似于filter query的语法提供了更为灵活的Facet.通过facet.query参数,可以对任意字段进行筛选。
facet.field:需要分组统计的字段,可以多个。
facet.prefix: 表示Facet字段值的前缀。比如facet.field=cpu&facet.prefix=Intel,那么对cpu字段进行Facet查询,返回的cpu都是以Intel开头的, AMD开头的cpu型号将不会被统计在内。
spatial:
spellcheck: 拼写检查。

2.5 Solr的配置文件的使用

  • schema.xml这个配置文件的根本目的是为了通过配置告诉Solr如何建立索引。在老版本叫做schema.xml,在较新的solr版本中叫做:managed-schema,注意区分。

2.6 Solr的分词配置

2.7 Solr的定位配置

三. Solr后端引入及基本使用

3.1 引入SpringBoot项目教程

  1. 在项目工程中pom.xml (Maven)配置依赖:
     <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>8.5.2</version>
        </dependency>
    

    solr 支持Restful风格的Http请求,所以当部署成功后,我们可以直接以普通请求访问。而引入solrj则能够使用到第三方封装的一些方法,使用面向对象的方式去方便的操作solr。
    solrj的版本可能会有差异,会导致查询方式有变化,这点请注意。

  2. application.yml中加入配置:
    # Solr相关配置
    solr:
      url: http://ip地址:8983/solr     # solr连接地址
      core_house: xxxx                 # 库,你创建的core叫什么,就传什么
      initialSize: 5                         # 初始化连接数
      maxIdleSize: 5                         # 最大空闲连接数
      maxActiveSize: 10                       # 最大活动连接数
      connTimeOut: 60                      # 等待时间
    
  3. 引入配置类:
    1. Kotlin 代码:
      import gw.commonkotlin.exception.BaseException
      import org.apache.solr.client.solrj.impl.HttpSolrClient
      import org.slf4j.LoggerFactory
      import org.springframework.beans.factory.annotation.Value
      import org.springframework.stereotype.Service
      import java.util.*
      import javax.annotation.PostConstruct
      
      /**
       * @author zhanglei
       * @data: 2020/11/2
       */
      @Service
      class SolrConfig {
          // 空闲连接集合
          private val freeConnection: MutableList<HttpSolrClient> = Vector()
      
          // 活动连接集合
          private val activeConnection: MutableList<HttpSolrClient?> = Vector()
      
          // solr连接地址
          @Value("\${solr.url:}")
          private val url: String? = null
      
          // 初始化连接数
          @Value("\${solr.initialSize:1}")
          private val initialSize = 0
      
          // 最大空闲连接数
          @Value("\${solr.maxIdleSize:1}")
          private val maxIdleSize = 0
      
          // 最大活动连接数
          @Value("\${solr.maxActiveSize:1}")
          private val maxActiveSize = 0
      
          // 等待时间
          @Value("\${solr.connTimeOut:30}")
          private val connTimeOut = 0
      
          // 库
          @Value("\${solr.core_house:}")
          private val coreHouse: String? = null
      
          private val lock = Object()
      
      
          // 初始化
          @PostConstruct
          fun init() {
              try {
                  for (i in 0 until initialSize) {
                      val newConnection = newConnection()
                      freeConnection.add(newConnection)
                  }
              } catch (e: Exception) {
                  logger.error("solr初始化失败", e)
                  throw BaseException("初始化Solr失败,请检查配置参数!")
              }
          }
      
          // 创建新的Connection
          private fun newConnection(): HttpSolrClient {
              val client: HttpSolrClient
              client = try {
                  HttpSolrClient.Builder("$url/$coreHouse").build()
              } catch (e: Exception) {
                  logger.error("创建Solr客户端失败!", e)
                  throw BaseException("创建Solr客户端失败!")
              }
              connCount++
              return client
          }// 大于最大活动连接,进行等待,重新获取连接
          // 递归调用getConnection方法
      // 创建新的连接
      
      
          // 还有活动连接可以使用
          fun connection(): HttpSolrClient {
              var connection: HttpSolrClient? =null
              try {
                  if (connCount < maxActiveSize) {
                      // 还有活动连接可以使用
                      connection = if (freeConnection.size > 0) {
                          freeConnection.removeAt(0)
                      } else {
                          // 创建新的连接
                          newConnection()
                      }
                      if (availableFlag(connection)) {
                          activeConnection.add(connection)
                      } else {
                          connCount--
                          connection = connection()
                      }
                  } else {
                      synchronized(lock) {
                          // 大于最大活动连接,进行等待,重新获取连接
                          lock.wait(connTimeOut.toLong())
                      }
                      // 递归调用getConnection方法
                      connection = connection()
                  }
              } catch (e: Exception) {
                  e.printStackTrace()
              }
              return connection!!
          }
      
          // 判断连接是否可用
          fun availableFlag(connection: HttpSolrClient?): Boolean {
              return connection != null
          }
      
          // 关闭连接
          fun close(connection: HttpSolrClient) {
              try {
                  if (availableFlag(connection)) {
                      // 判断空闲连接集合是否大于最大空闲连接数
                      if (freeConnection.size < maxIdleSize) {
                          freeConnection.add(connection)
                      } else {
                          // 空闲连接数已经满了
                          connection.close()
                          connCount--
                      }
                      activeConnection.remove(connection)
                      synchronized(lock) { lock.notifyAll() }
                  }
              } catch (e: Exception) {
                  logger.error("删除连接出错", e)
                  throw BaseException("删除连接出错!")
              }
          }
      
          companion object {
              private val logger = LoggerFactory.getLogger(SolrConfig::class.java)
      
              // 记录连接总数
              private var connCount = 0
          }
      }
      
    2. Java版本代码:
      import java.util.List;
      import java.util.Vector;
       
      import org.apache.solr.client.solrj.impl.HttpSolrClient;
       
      /**
       * solr连接池
       * 
       * @author a_bo
       * @version 创建时间:2019年4月1日 上午10:24:05
       */
      public class SolrConnectionPool {
       
      	// 空闲连接集合
      	private List<HttpSolrClient> freeConnection = new Vector<HttpSolrClient>();
      	// 活动连接集合
      	private List<HttpSolrClient> activeConnection = new Vector<HttpSolrClient>();
      	// 记录连接总数
      	private static int connCount;
      	// solr连接地址
      	private String url;
      	// 初始化连接数
      	private int initialSize;
      	// 最大空闲连接数
      	private int maxIdleSize;
      	// 最大活动连接数
      	private int maxActiveSize;
      	// 等待时间
      	private int connTimeOut;
       
      	public static int getConnCount() {
      		return connCount;
      	}
       
      	public static void setConnCount(int connCount) {
      		SolrConnectionPool.connCount = connCount;
      	}
       
      	public String getUrl() {
      		return url;
      	}
       
      	public void setUrl(String url) {
      		this.url = url;
      	}
       
      	public int getInitialSize() {
      		return initialSize;
      	}
       
      	public void setInitialSize(int initialSize) {
      		this.initialSize = initialSize;
      	}
       
      	public int getConnTimeOut() {
      		return connTimeOut;
      	}
       
      	public void setConnTimeOut(int connTimeOut) {
      		this.connTimeOut = connTimeOut;
      	}
       
      	public int getMaxIdleSize() {
      		return maxIdleSize;
      	}
       
      	public void setMaxIdleSize(int maxIdleSize) {
      		this.maxIdleSize = maxIdleSize;
      	}
       
      	public int getMaxActiveSize() {
      		return maxActiveSize;
      	}
       
      	public void setMaxActiveSize(int maxActiveSize) {
      		this.maxActiveSize = maxActiveSize;
      	}
       
      	// 初始化
      	public void init() {
      		try {
      			for (int i = 0; i < initialSize; i++) {
      				HttpSolrClient newConnection = newConnection();
      				if (newConnection != null) {
      					// 添加到空闲连接中...
      					freeConnection.add(newConnection);
      				}
      			}
      		} catch (Exception e) {
      			e.getStackTrace();
      			throw new RuntimeException("初始化Solr失败,请检查配置参数!");
      		}
      	}
       
      	// 创建新的Connection
      	private HttpSolrClient newConnection() {
      		HttpSolrClient client = null;
      		try {
      			HttpSolrClient.Builder builder = new HttpSolrClient.Builder(url);
      			client = builder.build();
      		} catch (Exception e) {
      			e.getStackTrace();
      			throw new RuntimeException("创建Solr客户端失败!");
      		}
      		connCount++;
      		return client;
      	}
       
      	public HttpSolrClient getConnection() {
      		HttpSolrClient connection = null;
      		try {
      			if (connCount < maxActiveSize) {
      				// 还有活动连接可以使用
      				if (freeConnection.size() > 0) {
      					connection = freeConnection.remove(0);
      				} else {
      					// 创建新的连接
      					connection = newConnection();
      				}
      				if (isAvailable(connection)) {
      					activeConnection.add(connection);
      				} else {
      					connCount--;
      					connection = getConnection();
      				}
      			} else {
      				synchronized (this) {
      					// 大于最大活动连接,进行等待,重新获取连接
      					wait(connTimeOut);
      				}
      				connection = getConnection();// 递归调用getConnection方法
      			}
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      		return connection;
      	}
       
      	// 判断连接是否可用
      	public boolean isAvailable(HttpSolrClient connection) {
      		// 此处没想好怎么判断连接是否可用
      		if (connection == null) {
      			return false;
      		}
      		return true;
      	}
       
      	// 关闭连接
      	public void close(HttpSolrClient connection) {
      		try {
      			if (isAvailable(connection)) {
      				// 判断空闲连接集合是否大于最大空闲连接数
      				if (freeConnection.size() < maxIdleSize) {
      					freeConnection.add(connection);
      				} else {
      					// 空闲连接数已经满了
      					connection.close();
      					connCount--;
      				}
      				activeConnection.remove(connection);
      				synchronized (this) {
      					notifyAll();
      				}
      			}
      		} catch (Exception e) {
      			e.printStackTrace();
      			throw new RuntimeException("删除连接出错!");
      		}
      	}
      }
      
    > 如果使用的是java,则使用下面的连接池即可。2个代码可能少部分不同,因语言差异导致,可以忽略。
    
    

3.2 基本查询方法:增、删、查、改

  • 测试代码如下:
    	
    import com.yzz.house.config.SolrConfig;
    import org.apache.solr.client.solrj.SolrQuery;
    import org.apache.solr.client.solrj.SolrServerException;
    import org.apache.solr.client.solrj.beans.Field;
    import org.apache.solr.client.solrj.impl.HttpSolrClient;
    import org.apache.solr.client.solrj.response.QueryResponse;
    import org.apache.solr.client.solrj.response.UpdateResponse;
    import org.apache.solr.common.SolrDocument;
    import org.apache.solr.common.SolrDocumentList;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import java.io.IOException;
    import java.io.Serializable;
    
    /**
     * @author CSDN-暗余
     */
    public class Student implements Serializable {
    
        @Field("name")
        private String name;
    
        @Field("age")
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
    }
    
    @Service
    class Test {
    
        @Autowired
        private SolrConfig solrConfig;
    
        /**
         * 测试新增/修改
         * 若无此id的数据,则新增,若有此id的数据则修改。
         *
         * @return boolean 是否成功的状态
         * @throws IOException
         * @throws SolrServerException
         */
        public Boolean testInsert() throws IOException, SolrServerException {
            Student student = new Student();
            student.setName("张三");
            student.setAge(15);
            HttpSolrClient connection = solrConfig.connection();
            connection.addBean(student);
            connection.close();
            solrConfig.close(connection);
            return true;
        }
    
        /**
         * 测试删除
         * @return boolean 是否删除成功的状态值
         * @throws IOException
         * @throws SolrServerException
         */
        public Boolean testDelete() throws IOException, SolrServerException {
            HttpSolrClient connection = solrConfig.connection();
            UpdateResponse updateResponse = connection.deleteById("1");
            return true;
        }
    
        /**
         * 查询
         */
        public SolrDocument testGetById() throws IOException, SolrServerException {
            HttpSolrClient connection = solrConfig.connection();
            SolrDocument solrDocument = connection.getById("1");
            return solrDocument;
        }
    
        /**
         * 查询
         * @return
         */
        public SolrDocumentList testQuery() throws IOException, SolrServerException {
            HttpSolrClient connection = solrConfig.connection();
            SolrQuery solrQuery= new SolrQuery();
            solrQuery.set("q", "*:*");
            QueryResponse query = connection.query(solrQuery);
            SolrDocumentList results = query.getResults();
            return results;
        }
    }
    

    首先需要在Solr的配置文件中配置好索引index。 然后定义一个合规的Field对象。对此对象进行赋值,然后保存并更新至Solr,最后关闭solrj的连接流和我们配置的流。我们自身的solrConfig可以不用配置连接池也可以,可自行选择。
    这里的代码只是一些示例作用,如果你能够看懂它,那么你已经基本能够使用Solr了,当在你的项目中,你可以基于demo去按自己的风格实现或者封装;后面会分享博主自己的封装solrj代码示例;

3.3 注意事项

  1. 使用Solrj进行新增、修改时,需要对传输的对象的每个变量加上 @Field注解,具体的使用方式可以百度。
  2. 加上@Field 的变量,必须与Solr中的索引对应上,即字段名一样,类型是对应关系的。
  3. 主键id必须是字符串,在调用Solrj的 getById()方法以及 deleteById()都是按照id来进行删除的。
  4. Solrj的每个版本可能连接方式以及方法可能会有变化,以实际为准。
  5. Solrj中的addBean()方法,可新增和修改。如果存在此id的对象则新增,若不存在则修改。

四. Solr高级语法

4.1 分词

  • 用solr自带的text分词它会把一句话分成单个的字。如果是英文环境就可以适配。但是在中文环境下无法适配词汇。所以我们可以使用分词器。Solr有自带的分词器,但是博主建议使用IK分词器进行分词。首先它的分词效果更好、支持敏感词过滤、自定义词汇等扩展性功能。
  • IK分词器介绍
  • IK分词器安装步骤:
    1. 下载Ik分词器资源 https://github.com/magese/ik-analyzer-solr
    2. 将ik-analyzer-solr7-7.x.jar包上传到 $SOLR_INSTALL_HOME/server/solr-webapp/webapp/WEB-INF/lib目录下
    3. 在$SOLR_INSTALL_HOME/server/solr-webapp/webapp/WEB-INF目录下新建目录classes目录,并将IK分词器中源码的资源文件上传到该目录下,如图所示:
      在这里插入图片描述

      ext.dic自定义词 如沙雕 在汉语里面不是一个词 ,它只是一个网络用语,可以配置到这里面让它成为一个词
      stopword.dic 停止字典, 如:啊 吧 唉 不作分词
      IKAnalyzer.cfg.xml配置ik的配置文件 不用改
      Jar:如果要使用ik分词要导入的jar包

    4. 修改managed-sahma:
      <!--添加一个中文分词器IK-->
      <fieldType name="text_ik" class="solr.TextField">
       	<analyzer type="index">
      		<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="false" conf="ik.conf"/>
      		<filter class="solr.LowerCaseFilterFactory"/>
      	</analyzer>
      	<analyzer type="query">
      		<tokenizer class="org.wltea.analyzer.lucene.IKTokenizerFactory" useSmart="true" conf="ik.conf"/>
      		<filter class="solr.LowerCaseFilterFactory"/>
      	</analyzer>
      </fieldType>
      

      useSmart 和分词的粒度相关:false: 分词的粒度大,一句话里面分的词语少; true:分词的粒度细,一句话里面分的词语多.那我们在导入时需要的关键字多吗?让索引的数据量少一点。我们的粒度大:false;我们在搜索时需要的关键字多吗?我们想尽可能的覆盖所有的范围,我们的粒度要细:true

    5. 重启solr,在界面测试,如图所示:
      在这里插入图片描述
  • 使用自带分词器步骤:
  1. 分词器的jar包到solr-webapp目录下:
    cp $SOLR_INSTALL_HOME/contrib/analysis-extras/lucene-libs/lucene-analyzers-smartcn-7.7.2.jar $SOLR_INSTALL_HOME//server/solr-webapp/webapp/WEB-INF/lib/
    
  2. 编辑$SOLR_INSTALL_HOME/server/solr/my_core/conf/managed-schema文件,引入自带分词器,在文件底部加入以下内容(PS:在schema标签内):
    <!-- solr自带分词器 --> 
    <fieldType name="text_smartcn" class="solr.TextField" positionIncrementGap="0">
    	<analyzer type="index">
    		<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
     	</analyzer>
    	<analyzer type="query">
    		<tokenizer class="org.apache.lucene.analysis.cn.smart.HMMChineseTokenizerFactory"/>
    	</analyzer>
    </fieldType>
    
  3. 重启solr,在界面测试,如图所示:
    在这里插入图片描述

4.2 高亮

4.3 分组

4.4 统计

4.5 其他语法

五. Solr优化

5.1 Solr查询连接池

  • 参照本篇文章3.1即可

5.2 Solr查询封装-Kotlin

  • SolrApi :
import com.yzz.house.solr.service.SolrService
import com.yzz.house.solr.service.SolrWrapper
import gw.commonkotlin.respond.PageResult
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller

@Controller
class SolrApi {

    @Autowired
    private lateinit var solrService: SolrService

    /**
     * 多条件查询
     */
    fun <T> list(solrWrapper: SolrWrapper, clazz: Class<T>): PageResult<T> {
        return solrService.list(solrWrapper, clazz)
    }

    /**
     * 查询单个
     */
    fun <T> getById(id: String, clazz: Class<T>): T? {
        return solrService.getById(id, clazz)
    }

    /**
     * 新增或修改
     */
    fun saveOrUpdate(field: Any): Boolean {
        return solrService.saveOrUpdate(field)
    }

    /**
     * 批量新增或修改
     */
    fun saveOrUpdateBatch(fields: List<Any>): Boolean {
        return solrService.saveOrUpdateBatch(fields)
    }

    /**
     * 删除
     */
    fun removeById(id: String): Boolean {
        return solrService.removeByIds(listOf(id))
    }

    /**
     * 批量删除
     */
    fun removeByIds(ids: List<String>): Boolean {
        return solrService.removeByIds(ids)
    }

    /**
     * 根据搜索条件删除
     */
    fun remove(solrWrapper: SolrWrapper): Boolean {
        return solrService.remove(solrWrapper)
    }

    /**
     * 根据搜索条件删除
     */
    fun remove(query: String): Boolean {
        return solrService.remove(query)
    }
}
  • SolrService :

import com.xxx.house.config.SolrConfig
import com.xxx.house.solr.SolrApi
import gw.commonkotlin.respond.PageResult
import gw.commonkotlin.utils.issEmpty
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

/**
 * @author:Anyu-csdn
 * @date: 2020/11/19|
 */
@Service
class SolrService : AbstractSolrService() {

    private final val logger: Logger = LoggerFactory.getLogger(SolrApi::class.java)

    @Autowired
    private lateinit var solrConfig: SolrConfig

    fun <T> list(solrWrapper: SolrWrapper, clazz: Class<T>): PageResult<T> {
        // 1. 建立连接   使用之前的连接池连接
        val connection = solrConfig.connection()
        // 2. 建立查询请求参数并查询
        val query = connection.query(solrWrapper.buildSolrQuery())
        // 3.获取结果
        val results = query.results
        // 4.关闭或归还连接至线程池
        solrConfig.close(connection)
        return PageResult(query.getBeans(clazz), results.numFound)
    }

    fun <T> getById(id: String, clazz: Class<T>): T? {
        return try {
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 进行查询
            val solrDocument = connection.getById(id)
            solrConfig.close(connection)
            // 3. 进行封装
            getBean(solrDocument, clazz)
        } catch (e: Exception) {
            logger.error("查询失败", e)
            null
        }
    }

    fun saveOrUpdate(field: Any): Boolean {
        return try {
            // 0. 校验参数
            if (field.issEmpty()) return false
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 进行新增或修改
            connection.addBean(field)
            // 3. 提交
            connection.commit()
            // 4. 关闭连接或将连接返回连接池
            solrConfig.close(connection)
            true
        } catch (e: Exception) {
            logger.error("solr新增或修改失败", e)
            false
        }
    }

    fun saveOrUpdateBatch(fields: List<Any>): Boolean {
        return try {
            // 0. 校验参数
            if (fields.issEmpty()) return false
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 进行新增或修改
            connection.addBeans(fields)
            // 3. 提交
            connection.commit()
            // 4. 关闭连接或将连接返回连接池
            solrConfig.close(connection)
            true
        } catch (e: Exception) {
            logger.error("solr新增或修改失败", e)
            false
        }
    }

    fun removeByIds(ids: List<String>): Boolean {
        return try {
            // 0. 校验参数
            if (ids.issEmpty()) return false
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 删除数据
            connection.deleteById(ids)
            // 3. 确认提交
            connection.commit()
            // 4. 关闭连接或将连接返回连接池
            solrConfig.close(connection)
            true
        } catch (e: Exception) {
            logger.error("删除失败", e)
            false
        }
    }

    /**
     * 根据搜索条件删除
     */
    fun remove(solrWrapper: SolrWrapper): Boolean {
        return try {
            // 0. 校验参数
            if (solrWrapper.issEmpty()) return false
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 删除数据
            connection.deleteByQuery(solrWrapper.buildSolrQuery().query)
            // 3. 确认提交
            connection.commit()
            // 4. 关闭连接或将连接返回连接池
            solrConfig.close(connection)
            true
        } catch (e: Exception) {
            logger.error("删除失败", e)
            false
        }
    }

    /**
     * 根据搜索条件删除
     */
    fun remove(query: String): Boolean {
        return try {
            // 0. 校验参数
            if (query.issEmpty()) return false
            // 1. 建立连接
            val connection = solrConfig.connection()
            // 2. 删除数据
            connection.deleteByQuery(query)
            // 3. 确认提交
            connection.commit()
            // 4. 关闭连接或将连接返回连接池
            solrConfig.close(connection)
            true
        } catch (e: Exception) {
            logger.error("删除失败", e)
            false
        }
    }
}
  • SolrWrapper :

import com.yzz.house.dto.OrderBy
import gw.commonkotlin.utils.gtZero
import gw.commonkotlin.utils.issEmpty
import gw.commonkotlin.utils.issNotEmpty
import org.apache.commons.lang3.StringUtils
import org.apache.solr.client.solrj.SolrQuery
import org.slf4j.LoggerFactory.getLogger
import java.math.BigDecimal
import java.util.*
import java.util.stream.Collectors


/**
 * @author:anyu-csdn
 * @date: 2020/11/19
 * @desc: solr的查询拼接类,类似于Mybatis-plus的简单版本,基于Solrj对其的进一步封装。
 */
class SolrWrapper : AbstractSolrService() {

    /**
     * 目前封装支持的查询: and且  or或  range范围  sort排序   limit分页
     */
    private val logger = getLogger(SolrWrapper::class.java)

    // eq 存储的数据集
    private var eqMap = IdentityHashMap<String, Any>()

    // range范围查询存储的数据集
    private var rgMap = IdentityHashMap<String, String>()

    // or 存储的数据集
    private var orMap = IdentityHashMap<String, Any>()

    // order排序
    private var orderMap = IdentityHashMap<String, SolrQuery.ORDER>()

    // 返回指定字段的参数
    private var selectSet = hashSetOf<String>()

    // geo数据   查询字段|经度,纬度
    private var solrGeo: SolrGeo? = null

    private var pageIndex: Int = 0

    private var pageSize: Int = 10

    private var q: String = ""


    /**
     * 默认自定义查询q
     */
    fun q(query: String): SolrWrapper {
        q = query
        return this
    }

    /**
     * 默认自定义查询q
     */
    fun q(boolean: Boolean, query: String): SolrWrapper {
        if (boolean) {
            q = query
        }
        return this
    }

    /**
     * 匹配查询:equals
     */
    fun eq(key: String, value: Any = "*"): SolrWrapper {
        eqMap[key] = value
        return this
    }

    /**
     * or查询
     */
    fun or(key: String, value: Any = "*"): SolrWrapper {
        orMap[key] = value
        return this
    }

    /**
     * or查询: 或
     */
    fun or(boolean: Boolean, key: String, value: Any = "*"): SolrWrapper {
        if (boolean) {
            orMap[key] = value
        }
        return this
    }

    fun orList(boolean: Boolean, key: String = "*", values: List<Any> = listOf("*")): SolrWrapper {
        if (boolean) {
            values.forEach {
                orMap[key] = it
            }
        }
        return this
    }


    /**
     * 匹配查询:equals
     */
    fun eq(boolean: Boolean, key: String, value: Any = "*"): SolrWrapper {
        if (boolean) {
            eqMap[key] = value
        }
        return this
    }

    /**
     * 匹配查询: 传入列表
     */
    fun eqList(boolean: Boolean, key: String = "*", values: List<Any> = listOf("*")): SolrWrapper {
        if (boolean) {
            val stringBuffer = StringBuffer()
            values.forEach {
                val value = if (StringUtils.isEmpty(it.toString())) "*" else it.toString()
                if (stringBuffer.isEmpty()) stringBuffer.append(value) else stringBuffer.append(" OR $value")
            }
            if (StringUtils.isNotEmpty(stringBuffer.toString())) eqMap[key] = "($stringBuffer)"
        }
        return this
    }


    /**
     * 分页查询: page
     */
    fun pg(boolean: Boolean, pageIndex: Int, pageSize: Int): SolrWrapper {
        if (boolean) {
            this.pageIndex = pageIndex
            this.pageSize = pageSize
        }
        return this
    }

    /**
     * 分页查询: page
     */
    fun pg(pageIndex: Int, pageSize: Int): SolrWrapper {
        this.pageIndex = pageIndex
        this.pageSize = pageSize
        return this
    }

    /**
     * 范围查询:range
     */
    fun rg(boolean: Boolean = true, key: String, start: Any = "*", end: Any = "*"): SolrWrapper {
        if (boolean) {
            rgMap[key] = "$start TO $end"
        }
        return this
    }

    /**
     * 范围查询:range
     */
    fun rg(key: String, start: Any = "*", end: Any = "*"): SolrWrapper {
        rgMap[key] = "[$start TO $end]"
        return this
    }

    /**
     * 范围查询:range
     */
    fun rgList(boolean: Boolean, key: String, values: List<String> = listOf("* TO *")): SolrWrapper {
        if (boolean) {
            val stringBuffer = StringBuffer()
            values.forEach {
                val value = if (StringUtils.isEmpty(it)) "[* TO *]" else it
                if (stringBuffer.isEmpty()) stringBuffer.append(value) else stringBuffer.append(" OR $value")
            }
            if (StringUtils.isNotEmpty(stringBuffer.toString())) rgMap[key] = "($stringBuffer)"
        }
        return this
    }


    /**
     * 排序:排序顺排
     */
    fun orderByAsc(boolean: Boolean, key: String): SolrWrapper {
        if (boolean) {
            orderMap[key] = SolrQuery.ORDER.asc
        }
        return this
    }

    /**
     * 排序: 顺序排序
     */
    fun orderByAsc(key: String): SolrWrapper {
        orderMap[key] = SolrQuery.ORDER.asc
        return this
    }

    /**
     * 排序:倒叙排序
     */
    fun orderByDesc(key: String): SolrWrapper {
        orderMap[key] = SolrQuery.ORDER.desc
        return this
    }

    /**
     * 排序:倒叙排序
     */
    fun orderByDesc(boolean: Boolean, key: String): SolrWrapper {
        if (boolean) {
            orderMap[key] = SolrQuery.ORDER.desc
        }

        return this
    }

    /**
     * 排序: 根据对象排序
     */
    fun orderByList(orderBy: List<OrderBy>): SolrWrapper {
        orderBy.forEach {
            if (StringUtils.isNotEmpty(it.field) && (StringUtils.equals(it.orderBy, "asc") || StringUtils.equals(it.orderBy, "desc"))) {
                orderMap[it.field] = if (SolrQuery.ORDER.asc.name == it.orderBy) SolrQuery.ORDER.asc else SolrQuery.ORDER.desc
            }
        }
        return this
    }

    /**
     * 设置定位搜索
     */
    fun geo(boolean: Boolean, key: String, latitude: BigDecimal, longtude: BigDecimal, range: Int): SolrWrapper {
        if (boolean) {
            // 当key不为空、经纬度大于0、范围大于0则加入查询条件
            if (StringUtils.isNotEmpty(key) && latitude > BigDecimal.ZERO && longtude > BigDecimal.ZERO && range > 0) {
                val sg = SolrGeo()
                sg.fieldName = key
                sg.latitude = latitude.toString()
                sg.longtude = longtude.toString()
                sg.range = range
                solrGeo = sg
            }
        }
        return this
    }

    /**
     * 设置定位搜索
     */
    fun geo(key: String, latitude: BigDecimal, longtude: BigDecimal, range: Int): SolrWrapper {
        // 当key不为空、经纬度大于0、范围大于0则加入查询条件
        if (StringUtils.isNotEmpty(key) && latitude > BigDecimal.ZERO && longtude > BigDecimal.ZERO && range > 0) {
            val sg = SolrGeo()
            sg.fieldName = key
            sg.latitude = latitude.toString()
            sg.longtude = longtude.toString()
            sg.range = range
            solrGeo = sg
        }
        return this
    }

    /**
     * 获取排序集合
     */
    private fun orderMap(): Map<String, SolrQuery.ORDER> {
        return orderMap
    }

    /**
     * 返回指定字段的参数
     */
    fun select(vararg key: String): SolrWrapper {
        key.filter { StringUtils.isNotEmpty(it) }.forEach { selectSet.add(it) }
        return this
    }

    /**
     * 返回指定字段的参数
     */
    fun select(boolean: Boolean, list: List<String>): SolrWrapper {
        if (boolean) {
            selectSet.addAll(list.filter { StringUtils.isNotEmpty(it) })
        }
        return this
    }

    private fun rgMapSQL(): String {
        val rgBuffer = StringBuffer()
        //拼装其他条件语句
        rgMap.forEach { (key, value) ->
            run {
                val range = "$key:$value"
                if (rgBuffer.isEmpty()) {
                    rgBuffer.append(range)
                } else {
                    rgBuffer.append(" AND $range")
                }
            }
        }
        return rgBuffer.toString()
    }

    private fun eqMapSQL(): String {
        val eqBuffer = StringBuffer()
        //拼装其他条件语句
        eqMap.forEach { (key, value) ->
            run {
                if (eqBuffer.isEmpty()) {
                    eqBuffer.append("($key:$value)")
                } else {
                    eqBuffer.append(" AND ($key:$value)")
                }
            }
        }
        return eqBuffer.toString()
    }

    private fun orMapSQL(): String {
        val orBuffer = StringBuffer()
        //拼装其他条件语句
        orMap.forEach { (key, value) ->
            run {
                if (orBuffer.issEmpty()) {
                    orBuffer.append("$key:$value")
                } else {
                    orBuffer.append(" OR $key:$value")
                }
            }
        }
        return orBuffer.toString()
    }


    private fun allSQL(): String {
        // 封装sql
        val mergeSolrSQL = mergeSolrSQL(eqMapSQL(), rgMapSQL(), orMapSQL())
        return if (StringUtils.isEmpty(q)) {
            // 如果封装查询为空,则默认查询全部
            if (StringUtils.isEmpty(mergeSolrSQL)) "*:*" else mergeSolrSQL
        } else {
            // 如果封装查询不为空,则判断封装查询是否为空。
            if (StringUtils.isEmpty(mergeSolrSQL)) q else "($mergeSolrSQL) AND ($q)"
        }
    }

    fun buildSolrQuery(): SolrQuery {
        val solrQuery = SolrQuery()
        // 1. 拼接查询参数
        val allSQL = this.allSQL()
        solrQuery.set("q", allSQL)
        // 2. 拼接经纬度参数
        if (solrGeo.issNotEmpty()) {
            solrQuery.set("d", solrGeo!!.range)
                    .set("pt", "${solrGeo!!.latitude},${solrGeo!!.longtude}")
                    .set("geodist()", "geodist()  asc")
                    .set("sfield", "houseLocation")
                    .set("fq", "{!geofilt}")
                    .set("fl", "*,dist:geodist()")
        }
        // 3. 拼接分页参数
        if (this.pageIndex.gtZero() && this.pageSize.gtZero()) {
            solrQuery.start = (this.pageIndex * this.pageSize) - (this.pageSize)
            solrQuery.rows = this.pageSize
        }
        // 4.拼接排序参数
        if (orderMap.issNotEmpty()) {
            this.orderMap().forEach { (key, value) -> solrQuery.setSort(key, value) }
        }
        // 确定返回值
        if (selectSet.issNotEmpty()) {
            solrQuery.set("fl", selectSet.stream().collect(Collectors.joining(",")))
        }
        logger.info("\r\nSolr查询语句为:\r\n$allSQL\r\n")
        return solrQuery
    }

}
  • 其他附带引用类: AbstractSolrService:

import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONArray
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils
import org.apache.commons.lang3.StringUtils

/**
 * @author:anyu
 * @date: 2020/11/19
 */
abstract class AbstractSolrService{

    /**
     * 将一个对象转为指定的另外一个对象
     */
    fun <T> getBean(any: Any, clazz: Class<T>): T? {
        return if (!ObjectUtils.isEmpty(any)) {
            val solrResult = JSON.parseObject(JSON.toJSONString(any), clazz)
            solrResult
        } else {
            null
        }
    }

    /**
     * 将一个集合转为指定的集合
     */
    fun <T> getArrayBean(any: Any, clazz: Class<T>): List<T>? {
        return if (!ObjectUtils.isEmpty(any)) {
            val solrResult = JSONArray.parseArray(JSON.toJSONString(any), clazz)
            solrResult
        } else {
            return null
        }
    }


    /**
     * 添加内容到List  自动排除为空的部分
     */
    fun mergeSolrSQL(vararg objs: String?): String {
        val mergeBuffer = StringBuffer()
        for (obj in objs) {
            if (StringUtils.isNotEmpty(obj)) {
                if (mergeBuffer.isEmpty()) {
                    mergeBuffer.append("($obj) ")
                } else {
                    mergeBuffer.append(" AND ($obj)")
                }
            }
        }
        return mergeBuffer.toString()
    }
}
  • 其他附带引用类: SolrGeo:

/**
 * @author:anyu
 * @date: 2020/11/19
 * @desc: GPS封装对象
 */
class SolrGeo {

    /**
     * 要搜索定位的字段名
     */
    var fieldName: String=""

    /**
     * 纬度
     */
    var latitude: String=""

    /**
     * 经度
     */
    var longtude: String=""

    /**
     * 查询公里范围,为方便,取整数
     */
    var range: Int=0

}

5.3 Solr其他优化

六. 扩展

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暗余

码字来之不易,您的鼓励我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值