1,基本内容
当系统中存在海量数据需要进行全文检索时,传统的检索方法的 性能开销会随着数据量的增长而线性增加,因此数据量越大,性能越差。搜索(全文检索)引擎,则完全不存在这个问题,其性能开销会随着数据量的增长到达一个顶点(在这个顶点依然具有很好的性能),以后无论数据量怎么增长,搜索引擎的性能开销基本都可以稳定在这个顶点处,因此搜索引擎同样是处理海量数据的必需技术。
1.1,LIKE模糊查询和全文索引
在实际应用中不可避免地会遇到全文检索的需求,例如,从电商网站上查询所有商品描述中包含“cow”关键词的商品,或者从站内消息中搜索所有包含“cow”关键词的消息……它们共同的特征就是要求查询某个字段包含特定关键词。如果使用传统RDBMS处理这种需求,就需要使用如下SQL语句进 行查询:
select * from goods where name like '%cow%';
如果你稍有SQL优化的基础,就会知道:这种使用LIKE的“模糊查询”本身就是很影响性能的,再加上海量数据,高并发的场景,这种模糊查询是完全不可接受的。所以早期有些论坛系统(如 Discuz),要么彻底禁用全站检索功能,要么只对高级用户开放全文检索功能,而且往往限制每个小时只能检索一次。
LIKE模糊查询只能这样从目标列的值中“逐个”检查,验证是否有要查询的关键词,因此每搜索一条记录,都需要大致固定的时间开销,如果该列的文本内容很长,那么处理时间就会略长一些。当处理千万条记录时,整个LIKE模糊查询的时间开销就是单条记录的处理时间再乘以千万,因此传统LIKE模糊查询的时间开销与表中记录的数量成正比,这对于处理海量数据检索是完全不可接受的。
1.2,反向索引库与Lucene
为了解决LIKE模糊查询的性能问题,Lucene做了一个革命性的创新:先建立反向索引库,再通过反向索引库进行检索。反向索引库需要先对目标内容进行分词,然后以分好的关键词为key建立索引库,value保存了该key出现在哪些文档中在文档中哪些位置等信息。
Document Words Document_1 the,cow,says,moo Document_2 the,cat,and,the,hat Document_3 the,dish,ran,away,with,the,spoon
Word Documents the Document_1, Document_3, Document_4, Document_5, Document_7 cow Document_2, Document_3, Document_4 says Document_5 moo Document_6 上方的表格中保存了原始的要检索的数据;下方的表格就是为它建立的反向索引库的示意图,该反向索引库的“关键词”列保存了所有关键词,该列本身也是有聚簇索引的,因此对该列执行查询的效率非常高。例如,程序依然要查询哪些文档中出现了“cow”关键词,此时程序不需要在上方的表格中执行查询,而是对下方表格中的 “关键词”列执行查询。对“关键词”列执行查询有两个特征:
- 不需要使用LIKE模糊查询,性能很好。
- “关键词”列本身带有聚簇索引,性能很好。
还有一点需要说明的是,不管哪一种语言,它能支持的“词”是有限的,以英语为例,大部分母语为英语的大学生的词汇量在3万个左右;类似地,中文的汉字,单词也是有限的。这就意味着:不管目标文档是百万个也好,是百亿个也罢,反向索引库的关键词并不会显著增加,因此对“关键词”列的检索性能总是有保证的。
Lucene正是因为利用了反向索引库的特征,从而为全文检索提供了性能保证。Lucene是目前世界上最流行的全文检索框架,它解决了传统SQL查询搞不定的情况,或者使用SQL语句能够搞定查询,但要用到很 多LIKE…OR,查询速度很慢,此时就要用到Lucene全文检索技术。
但如果直接使用Lucene,又会存在如下问题:
- Lucene本身的API比较难用,Lucene框架的开发者自身应该不是Java的开发者,因此他设计的Lucene API比较晦涩难用。
- Lucene只是一个Java框架,因此只有Java程序员才能使用 Lucene 为项目增加全文检索功能。
考虑到其他语言的开发者也需要为应用增加全文检索功能,而他 们又没有类似于Lucene的搜索引擎框架可供使用,因此 Solr、Elasticsearch 等技术对 Lucene 进行了包装,包装之后的 Solr、Elasticsearch 不再是简单的框架,它们更像搜索引擎的服务器。
虽然Solr、Elasticsearch底层都是基于Lucene的,但它们自己提供了对Lucene索引库的操作管理,开发者不再需要直接面向Lucene API编程,而是面向Solr、Elasticsearch所提供的RESTful接口编程。
1.3,安装 Solr
Apache Solr 是一个开源的搜索引擎,它的底层就是基于Lucene(全文检索引擎)构建的。现在的 Solr 以独立应用的方式运行,就像一个NoSQL存储引擎一样,它既可用于管理Lucene索引库,也可用于作为同样的NoSQL存储库。
与Lucene相比,Solr具有如下优势:
- Solr是独立应用,而不是简单的框架。Lucene只是一个Java框架,如果开发者不懂Java,那么就没法调用Lucene的API来编写全文检索功能。
- Solr提供了RESTful接口,开发者能以多种文档格式(XML、JSON或CSV)来输入数据,Solr也能提供对应格式的响应。这种RESTful接口完全与编程语言无关。
- Solr 是企业级的存储引擎,既支持独立部署,也支持作为大数据存储的分布式 NoSQL 数据库,还能以云端方式部署。
- 全文检索,Solr提供了全文检索所需的所有功能,如令牌,短语,拼写检查,通配符和自动完成等。
- 作为独立应用,Solr提供了一个易于使用,用户友好,功能强大的用户界面,使用它可以执行所有可能的任务,如管理日志以及添加,删除,更新和搜索文档。降低了Lucene的使用门槛。
【安装Solr】
- 从官网下载(下载地址)
- 下载后得到一个压缩包:solr-8.11.2.zip
- 配置环境变量
JAVA_HOME:由于Solr和Lucene都是基于Java的,因此需要Java环境。配置该环境变量指向JDK的安装路径(不要指向JDK的bin目录),这就是告诉Solr到哪里去找Java环境。 PATH:在PATH环境变量中添加Solr的bin目录所在的路径。这一步是为了让操作系统能找到Solr工具。该环境变量不是必需的,但配置该环境变量可以更方便地在命令行窗口中执行Solr命令。
2,基本操作
2.1,启动关闭
solr start -p <端口>
- start:启动Solr
- stop -all:停止Solr服务器。
- restart:重启Solr服务器。
- healthcheck:执行状态检查。
- create_core:用于为Solr服务器创建Core。
- create_collection:用于为Solr服务器创建Collection。
- create:根据Solr的运行状态选择创建Core或Collection。如果Solr以单机模式运行,则该命令是创建core;若Solr以云模式运行,则该命令是创建Collection。
- delete:删除Core或Collection。
- version:显示Solr的版本。
2.2,用户管理
通过 “solr start -p <端口>” 命令启动Solr之后,启动浏览器,访问 http://localhost:8983/(假设没有使用-p选项改变Solr的默认端口),将看到如图所示的管理界面。
- Dashboard(仪表盘):显示Solr运行状态一览。
- Logging(日志):显示Solr运行日志。
- Core Admin(Core管理):提供了图形用户界面来管理Core。
- Java Properties(Java属性):显示当前运行的JVM属性一览。
- Thread Dump(线程Dump):显示Solr内部的线程Dump。
【修改密码】默认启动的 Solr 不需要用户名,密码,任何人都可直接访问,很明显这是不合适的。为了支持权限控制,Solr提供了如下几种身份验证插件:
- Kerberos Authentication Plugin:Kerberos身份验证插件。
- Basic Authentication Plugin:基本身份验证插件。
- Hadoop Authentication Plugin:Hadoop身份验证插件。
- JWT Authentication Plugin:JWT身份验证插件。
这些身份验证插件的用法都很简单,此处以“基本身份验证插件”为Solr配置用户名,密码。
在Solr安装路径下的server\solr子目录下添加一个security.json文件,该文件的内容如下:
{ "authentication":{ "blockUnknown": true, "class":"solr.BasicAuthPlugin", "credentials":{"root":"pPKs8BkTXNNLlzipK0LAm6gh64kBEfIuKx1HYU4rHnc= hOJ+WQ/ubP/DPfTnGbjF+ANOZHmnaQ8jAnJh4xxdYu8="}, "realm":"Solr users", "forwardCredentials": false }, "authorization":{ "class":"solr.RuleBasedAuthorizationPlugin", "permissions":[{"name":"security-edit", "role":"admin"}], "user-role":{"root":"admin"} } }
blockUnknown属性指定为true,表明阻止所有未知用户访问;class属性指定使用基本身份验证插件;credentials属性配 置了一个超级用户,其用户名是root,密码是32147。permissions属性定义了一个admin角色,该角色允许执行“secur ity-edit”操作;user-role属性定义了root用户的角色是admin,这样root用 户就拥有执行“security-edit”操作的权限。
添加用户(添加用户需要输入加盐加密的密码),对用户的其他操作(如删除用户、为用户添加权限等)都可通过Solr 管理网站来实现。
2.3,管理 Core
在单机模式下,一个Core等于一个Collection。Solr的Core有点类似于RDBMS的表,Solr Core同样具有支持唯一标识的主键,也需要定义多个Field。与RDBMS不同的是,Core中存放的是各种文档,且这些文档不需要具有相同的Field。因此,在正式使用Solr之前,必须先创建Core。
- 使用solr命令的 create_core子命令创建 Core。
使用solr命令的create_core子命令创建Core时,没有提供选项来指 定用户名和密码,因此需要先将security.json文件中的blockUnknown属性 设为false,它表示关闭Solr的用户认证功能。
solr create_core -c Core 名称 [-d 配置文件目录] [-p 端口] -p:用于指定Solr实例的端口,如果不指定该选项,该命令将自动使用它搜索得到的第一个Solr实例的端口。 -d:用于指定这些配置模板所在的路径,如果不指定-d 选项,Solr 将默认为该选项使用_default值,也就是使用server\solr\configsets路径下_default目录下的配置文件作为配置模板。
PS:不推荐将_default目录下的配置文件作为产品级的Core来使用。 在server\solr\configsets路径下还提供了一个sample_techproducts_configs目录,该目录下的配置文件可作为产品级的Core来使用,因此推荐使用该目录作为Core配置文件的目录。对于有经验的开发者来说,可以针对不同特征的索引库预先准备好不同的配置模板,完全可改为使用自定义的配置模板目录。
solr create_core -c test -d sample_techproducts_configs
使用如下命令即可删除Core
solr delete [-c Core 名称] [-p 端口]
- 通过图形用户界面创建 Core:只需要在name和instanceDir文本框中分别输入Core名称和保存目录。
在通过图形用户界面创建Core时,Solr并不会为Core创建目录及配置文件,因此在通过图所示界面中的“Add Core”按钮创建Core之前,先要完成如下两步:
- 在server\solr路径下创建一个new_core目录,将instanceDir指定为new_core。
- 将server\solr\configsets\sample_techproducts_configs目录下的conf整个目录复制到第1步创建的new_core目录中。如果之前预定义好了配置文件,当然也可使用自己的配置文件。
如果要通过图形用户界面删除 Core,只要在界面中选中指定的 Core,然后单击“Unload”按钮即可删除该Core。 使用“solr delete”命令删除Core和通过图形用户界面删除Core是有区别的:使用“solr delete”命令删除Core时,会把整个Core对应的目录都彻底删除;但通过图形用户界面删除Core时,只是将该Core从Solr系统中删除,并未删除该Core对应的目录,因此以后还可重载添加回来。
在默认情况下,每个Core都对应于server\solr目录下的一个子目录,以前面创建的test Core为例,它对应于test子目录,该子目录下有如下文件夹和文件。
- conf:保存该Core的配置文件。
- data:保存该Core的索引库数据。
- core.properties文件。
core.properties文件包含如下内容,指定了该Core的名称。
#Written by CorePropertiesLocator #Tue Nov 01 13:34:24 UTC 2022 name=test
在Core目录的conf目录下可以看到如下常见的配置文件:
- managed-schema:定义该Core的整体Schema,包括该Core包括哪 些Field类型、Field约束、哪些Field、哪些动态Field、哪些Copy Field。该文件以前的文件名是schema.xml,用户可通过文本编辑器直接编辑,现在则推荐使用图形用户界面编辑,这样更安全、有效。
- solrconfig.xml:该Core的索引库相关配置。
- protwords.txt:该Core 额外的保护词配置。所谓“保护词”就是停止对该词“词干化”,在正常词干化的处理方式下,如managing、managed、manageable 这些单词最终都会变成manage。如果不希望某个单词被词干化,就将该单词添加到此文件中。
- stopword.txt:该 Core 额外的停用词配置。Lucene 不会对停用词创建反向索引库,因此程序也不能对停用词执行搜索。
- synonyms.txt:该Core的所有同义词配置。 通过可视化界面左边的“Core Selector”列表框(只有当前系统有可用的Core时,此处才会显示列表框)可选择要操作的 Core,以选择“Test”Core 为例,将会看到如图所示的Core概览界面。
- “Analysis” 标签可以对选定的字段进行索引或查询分析。
- “Documents” 标签可以向当前Core中添加文档或删除文档。
删除文档:{"delete":{"id":1}}
- “Query”标签可以检索文档。
- “Schema”标签可以对Field或Dynamic Field执行删除或修改操作。
3,SpringBoot整合Solr
3.1,使用 SolrClient 连接Solr
SolrClient是Solr本身提供的一个API,它是一个操作Solr索引库的门面类,它几乎可以完成对索引库的各种操作:
- add(String collection, Collection<SolrInputDocument> docs, int commitWithinMs):对collection添加或更新多个文档。每个SolrInputDocument代表一个文档。commitWithinMs参数指定在多少毫秒之内提交修改。
- add(String collection, SolrInputDocument doc, int commitWithinMs):添加或更新单个文档。
- addBean(String collection, Object obj, int commitWithinMs):以面向对象的方式添加或更新单个文档。其中obj代表映射文档的实体对象。该方法的本质就是添加单个文档的add()方法。
- addBeans(String collection, Collection<?> beans,int commitWithinMs):以面向对象的方式添加或更新多个文档。
- deleteById(String collection, List<String> ids,int commitWithinMs):根据id删除一个或多个文档。其中第2个参数既可以是List集 合,也可以是单个id值。
- deleteByQuery(String collection, String query, int commitWithinMs):删除符合query条件的所有文档。
- getById(String collection, String id, SolrParams params):根据id加载文档。其中第3个参数params用于指定额外的Solr参数。
- optimize(String collection, boolean waitFlush, boolean waitSearcher):优化索引库。
- query(String collection, SolrParams params, SolrRequest.METHOD method):以params参数从Solr索引库执行检索。
- request(SolrRequest request, String collection):向Solr索引库发送请求。
- commit(String collection):提交修改。
- rollback(String collection):回滚修改。
上面对文档进行修改的方法都可指定一个commitWithinMs参数,该参数指定在多少毫秒内会对所做的修改进行提交;如果上面的修改方法没有指定commitWithinMs参数,就必须显式地使用commit()方法来提交修改。
这些方法都可省略collection参数,如果省略了该参数,那么就要求程序在创建SolrClient时 指定Collection(在单机模式下对应于Core)。
#指定 SolrClient 连接的是Solr 实例的 springboot 这个 Core 。 spring.data.solr.host=http:///127.0.0.1:8983/solr/test #仅指定 SolrClient 连接的是 Solr 实例,并未指定连接哪个 Core,这样该SolrClient在执行上面这些方法时就需要指定collection参数。 spring.data.solr.host=http:///127.0.0.1:8983/solr/
Spring Boot为整合Solr提供了一个Starter:spring-boot-starter-data-solr,但这个Starter的功能很简陋,也就提供了两个类:SolrAutoConfiguration 和 SolrProperties,其中SolrProperties用于加载以“spring.data.solr”开头的属性,SolrAutoConfiguration则负责在容器中自动配置一个SolrClient。而且,该SolrClient还不支持读取用户名,密码信息。
以“spring.data.solr”开头的属性只支持host和zk-host:
- host用于指定在单机模式下Solr实例的地址;
- zk-host 则用于指定在云模式下Solr所在ZooKeeper的主机地址。
(1)添加 spring-boot-starter-data-solr.jar 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-solr</artifactId> <version>2.4.10</version> </dependency>
(2)定义 application.properties 文件
spring.data.solr.host=http:///127.0.0.1:8983/solr/ spring.data.solr.username = root spring.data.solr.password = 32147
该配置文件中的spring.data.solr.host只指定了连接的Solr服务器实例的地址,并未指定要操作的Core,因此后面调用SolrClient的方法时需要指定collection参数。 该配置文件还配置了用户名和密码,Spring Boot 默认并不会加载这两个属性,因此添加一个自定义的配置类来创建SolrClient。
package com.example.springboot; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.HttpSolrClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.solr.SolrProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import java.io.IOException; import java.util.Arrays; import java.util.Optional; @Configuration(proxyBeanMethods = false) @ConditionalOnClass({HttpSolrClient.class, CloudSolrClient.class}) @EnableConfigurationProperties(SolrProperties.class) public class SolrConfig { @Value("${spring.data.solr.username}") private String username; @Value("${spring.data.solr.password}") private String password; @Bean public SolrClient solrClient(SolrProperties properties) throws IOException, SolrServerException { // 设置使用基本认证的客户端 System.setProperty("solr.httpclient.builder.factory", "org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory"); // 设置认证的用户名和密码 System.setProperty("basicauth", username + ":" + password); // 如果zk-host属性存在,使用CloudSolrClient创建SolrClient if (StringUtils.hasText(properties.getZkHost())) { return new CloudSolrClient.Builder(Arrays.asList( properties.getZkHost()), Optional.empty()).build(); } // 创建单机模式下的SolrClient return new HttpSolrClient.Builder(properties.getHost()).build(); } }
SolrConfig类就是对Spring Boot提供的SolrAutoConfiguration 的改进,主要就是系统属性指定了使用基本认证的客户端,这与前面 Solr 配置的认证实现类对应,并为认证信息指定了用户名和密码,从而使得该SolrClient能连接带用户认证的Solr服务器。
(3)定义实体类:该实体类只需要简单地使用 @Field 注解,该注解指定将被修饰的属性(实例变量加上getter,setter方法就变成属性)映射到Core的哪个字段。在使用@Field注解时,可通过value属性指定将它映射到哪个字段;如果省略该属性,则默认映射同名字段。
import org.apache.solr.client.solrj.beans.Field; public class Book { @Field("id") private Integer id; @Field private String name; @Field private String description; @Field private Double price; //构造器、getter、setter }
(4)创建用SolrClient来操作Solr索引库
package com.example.springboot.Controller; import com.example.springboot.domin.Book; import org.apache.solr.client.solrj.SolrClient; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocumentList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; import java.util.ArrayList; @RestController public class HelloController { @Autowired private SolrClient solrClient; @RequestMapping("/save") public void Save() throws SolrServerException, IOException { var book1 = new Book(1, "狂人日记", "小说通过被迫害者“狂人”的形象以及“狂人”的自述式的描写,揭示了封建礼教的“吃人”本质,表现了作者对以封建礼教为主体内涵的中国封建文化的反抗;也表现了作者深刻的忏悔意识。作者以彻底的“革命民主主义”的立场对中国的文化进行了深刻的反思,同时对中国的甚至是人类的前途表达了深广的忧愤。", 55.5); var book2 = new Book(2, "孔乙己", "小说描写了孔乙己在封建腐朽思想和科举制度毒害下,精神上迂腐不堪、麻木不仁,生活上四体不勤、穷困潦倒,在人们的嘲笑戏谑中混度时日,最后被封建地主阶级所吞噬的悲惨形象。篇幅不长,但是深刻揭露了当时科举制度对知识分子精神的毒害和封建制度“吃人”的本质,具有强烈的反封建意义。", 44.4); var book3 = new Book(3, "故乡", "小说以“我”回故乡的活动为线索,按照“回故乡”——“在故乡”——“离故乡”的情节安排,依据“我”的所见所闻所忆所感,着重描写了闰土和杨二嫂的人物形象,从而反映了辛亥革命前后农村破产、农民痛苦生活的现实。", 33.3); ArrayList a = new ArrayList(); a.add(book1); a.add(book2); a.add(book3); // 指定向springboot Core保存文档 // 调用addBeans()方法则可保存多个文档 solrClient.addBeans("test", a, 100); } @RequestMapping("/select") public void Query(String field, String term) throws IOException, SolrServerException { //GET http://localhost:8080/select?field=name&term=孔* //GET http://localhost:8080/select?field=description&term=精神* // 创建SolrQuery SolrQuery query = new SolrQuery(); // 设置查询语法 query.setQuery(field + ":" + term); // 指定对springboot Core执行查询 QueryResponse queryResponse = solrClient.query("test", query); // 获取查询结果 SolrDocumentList docs = queryResponse.getResults(); for (var doc : docs) { System.out.println(doc); } } @RequestMapping("/delete") public void Delete(String id) throws IOException, SolrServerException { // 指定对springboot Core执行删除 solrClient.deleteById("test", id, 100); } }
其中 * 表示:查询des cription Field中包含“精神不振”,“精神萎靡” 等关键词。
3.2,使用Spring Data连接Solr与SolrTemplate
虽然Spring Boot的 SolrAutoConfiguration 只自动配置了一个SolrClient,但加上Spring DataSolr,则同样还会自动配置一个SolrTemplate。当然,该SolrTemplate底层其实也是依赖SolrClient的。与所有自动配置的处理方式类似,当开发者在容器中配置了SolrClient之后,Spring Boot将不再自动配置SolrClient。SolrTemplate则是SpringData Solr提供的一个门面类,它提供了如下方法来操作Solr索引库:
- count(String collection,SolrDataQueryquery,Class<?> domainType):从collection中查询符合query条件的文档的数量。
- delete(String collection,SolrDataQueryquery,Class<?> domainType):从collection中删除符合query条件的文档。
- deleteByIds(String collection,Collection<String> ids):根据 id 从collection中删除文档。
- getById(String collection,Object id,Class<T> clazz):根据单个id返回单个文档对应的实体对象。
- getByIds(String collection,Collection<?> ids,Class<T> clazz):根据多个id返回多个文档对应的实体对象。
- query(String collection,Query query,Class<T> clazz,RequestMethod method):查询多个符合query条件的文档对应的实体对象。
- queryForObject(String collection,Query query,Class<T> clazz,RequestMethod method):查询第一个符合query条件的文档对应的实体对象。
- saveBeans(String collection,Collection<?> beans,Duration commitWithin):保存多个实体对象对应的文档。
- saveDocuments(String collection,Collection<SolrInputDocument> documents,Duration commitWithin):保存多个文档。
- execute(SolrCallback<T> action):这是最灵活的方法,调用该方法时需要传入一个SolrCallback参数,而程序在实现SolrCallback时又可访问到SolrClient,并通过SolrClient操作Solr索引库。
SolrTemplate与SolrClient:将Solr本身提供的SolrClient与SolrTemplate进行对比,不难发现SolrTemplate并没有太大的优势,无非就是 SolrTemplate 更加以操作实体为主。实际上,SolrClient 也能面向实体编程,只不过SolrTemplate更彻底 一些。
Spring Data Solr大致包括如下几方面功能:
- DAO接口只需继承CrudRepository,Spring Data Solr能为DAO组件提供实现类。
- Spring Data Solr支持方法名关键字查询,只不过Solr查询都是全文检索查询。
- Spring Data Solr同样支持DAO组件添加自定义的查询方法—通过添加额外的接口,并为额外的接口提供实现类,Spring Data Solr就能将该实现类中的方法“移植”到DAO组件中。
Solr属于全文检索引擎,因此它的方法名关键字查询也是基于全文检索的。Spring Data Solr的Repository操作的数据类除了用@Field注解修饰,还可用Spring Data Solr提供的@SolrDocument注解修饰,该注解可指定一个collection属性,用于指定该实体类被映射到哪个Core。
实体类
import org.apache.solr.client.solrj.beans.Field; import org.springframework.data.solr.core.mapping.SolrDocument; @SolrDocument(collection = "test") public class Book { @Field("id") private Integer id; @Field private String name; @Field private String description; @Field private Double price; //省略构造器,getter和setter方法 ... }
DAO
package com.example.springboot.Dao; import com.example.springboot.domin.Book; import org.springframework.data.repository.CrudRepository; import org.springframework.data.solr.repository.Query; import org.springframework.stereotype.Component; import java.util.List; public interface BookDao extends CrudRepository<Book, Integer>, BookCustomDao { // 方法名关键字查询 List<Book> findByName(String name); List<Book> findByIdIn(List<Integer> list); List<Book> findByPriceBetween(double start, double end); List<Book> findByDescriptionMatches(String descPattern); // 使用@Query定义查询语句 @Query(value = "?0: ?1") List<Book> findByQuery(String field, String term); }
BookDao还继承了BookCustomDao接口,该接口用于为该DAO组件添加自定义的查询方法。下面是BookCustomDao接口的代码。
package com.example.springboot.Dao; import com.example.springboot.domin.Book; public interface BookCustomDao { List<Book> customQuery(String name, String description); }
Controller
package com.example.springboot.Controller; import com.example.springboot.Dao.BookDao; import com.example.springboot.domin.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.solr.core.SolrTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class HelloController { @Autowired private BookDao bookDao; @RequestMapping("/save") public void Save(Integer id, String name, String description, Double price) { var book = new Book(id, name, description, price); bookDao.save(book); } @RequestMapping("/delete") public void Delete(Integer id) { bookDao.deleteById(id); } @RequestMapping("/selectByName") public void FindByName(String name) { bookDao.findByName(name).forEach(System.out::println); } @RequestMapping("/selectByIdIn") public void FindByIdIn(Integer id1, Integer id2) { bookDao.findByIdIn(List.of(id1, id2)).forEach(System.out::println); } @RequestMapping("/selectByPriceBetween") public void FindByPriceBetween(double start, double end) { bookDao.findByPriceBetween(start, end).forEach(System.out::println); } @RequestMapping("/selectByDescriptionMatches") public void FindByDescriptionMatches(String descPattern) { bookDao.findByDescriptionMatches(descPattern) .forEach(System.out::println); } @RequestMapping("/selectByQuery") public void FindByQuery(String field, String term) { bookDao.findByQuery1(field, term).forEach(System.out::println); } @RequestMapping("/selectByCustomQuery") public void CustomQuery(String name, String description) { bookDao.customQuery1(name, description).forEach(System.out::println); } }
【自定义查询】下面为BookCustomDao提供实现类,该实现类通过SolrTemplate来实现自定义的查询方法。使用SolrTemplate执行查询也很简单, 程序只要通过查询语句创建Query对象,然后调用SolrTemplate的query()方法即可完成查询。
package com.example.springboot.Dao; import com.example.springboot.domin.Book; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.solr.core.SolrTemplate; import org.springframework.data.solr.core.query.Query; import java.util.List; public class BookCustomDaoImpl implements BookCustomDao { @Autowired private SolrTemplate solrTemplate; @Override public List<Book> customQuery1(String name, String description) { // 定义查询语句 Query query = Query.query("name:" + name +" AND description:" + description); // 调用SolrTemplate的方法执行查询 return solrTemplate.query("test", query, Book.class).toList(); } }
Spring Data Solr也提供了@EnableSolrRepositories注解,该注解用于手动启用Solr Repository支持。一旦程序显式使用该注解,Spring Data Solr的Repository自动配置就会失效。因此,当需要连接多个Solr索引库或进行更多定制时,可手动使用该注解。使用@EnableSolrRepositories注解时,也要指定如下几个属性:
- basePackages:指定扫描哪个包下的DAO组件(Repository组 件)。
- solrClientRef:指定基于哪个SolrClient来实现Repository组件, 默认值是solrClient。
- solrTemplateRef:指定基于哪个SolrTemplate来实现Repository组件,默认值是solrTemplate。
上面solrClientRef与solrTemplateRef两个属 性只要指定其中之一即可。