1. 什么是solr
Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化。
Solr可以独立运行,运行在Jetty、Tomcat等这些Servlet容器中,Solr 索引的实现方法很简单,用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr根据xml文档添加、删除、更新索引 。Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr 返回Xml、json等格式的查询结果进行解析,组织页面布局。Solr不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
基于Solr实现站内搜索扩展性较好并且可以减少程序员的工作量,因为Solr提供了较为完备的搜索引擎解决方案,因此在门户、论坛等系统中常用此方案。
2. solr与lucene的区别
Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索引擎,Lucene提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索引擎。
Solr的目标是打造一款企业级的搜索引擎系统,它是一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。
3. solr安装及配置(windows)
3.1. solr的下载
从Solr官方网站(http://lucene.apache.org/solr/ )下载Solr4.10.3,根据Solr的运行环境,Linux下需要下载lucene-4.10.3.tgz,windows下需要下载lucene-4.10.3.zip。
Solr使用指南可参考:https://wiki.apache.org/solr/FrontPage。
3.2. solr解压的文件夹结构
bin:solr的运行脚本
contrib:solr的一些贡献软件/插件,用于增强solr的功能。
dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。
docs:solr的API文档
licenses:solr相关的一些许可信息
example:solr工程的例子目录:
- example/solr:
该目录是一个包含了默认配置信息的Solr的Core目录。 - example/multicore:
该目录包含了在Solr的multicore中设置的多个Core目录。 - example/webapps:
该目录中包括一个solr.war,该war可作为solr的运行实例工程。
3.3. 运行环境
solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(java写的Servlet容器),本例使用Tocmat作为Servlet容器,环境如下:
Solr:Solr4.10.3
Jdk:jdk1.7.0_72
Tomcat:apache-tomcat-7.0.53
3.4. solr整合tomcat
3.4.1. Solr Home与SolrCore
创建一个Solr home目录,SolrHome是Solr运行的主目录,目录中包括了运行Solr实例所有的配置文件和数据文件,Solr实例就是SolrCore,一个SolrHome可以包括多个SolrCore(Solr实例),每个SolrCore提供单独的搜索和索引服务。
说明:
collection1:叫做一个Solr运行实例SolrCore,SolrCore名称不固定,一个solr运行实例对外单独提供索引和搜索接口。
solrHome中可以创建多个solr运行实例SolrCore。
一个solr的运行实例对应一个索引目录。
conf是SolrCore的配置文件目录 。
data目录存放索引文件需要创建
3.4.2. 整合步骤
- 安装tomcat。D:\temp\apache-tomcat-7.0.53
- 把solr的war包复制到tomcat 的webapp目录下。
2.1
把\solr-4.10.3\dist\solr-4.10.3.war复制到apache-tomcat-7.0.53\webapps下。
2.2
改名为solr.war - solr.war解压。使用压缩工具解压或者启动tomcat自动解压。解压之后删除solr.war
- 把\solr-4.10.3\example\lib\ext目录下的所有的jar包添加到tomcat中solr工程中的lib目录下
- 配置solrHome和solrCore。
1)创建一个solrhome(存放solr所有配置文件的一个文件夹)。\solr-4.10.3\example\solr目录就是一个标准的solrhome。
2)把\solr-4.10.3\example\solr文件夹复制到D:\temp\0108路径下,改名为solrhome,改名不是必须的,是为了便于理解。
3)在solrhome下有一个文件夹叫做collection1这就是一个solrcore。就是一个solr的实例。一个solrcore相当于mysql中一个数据库。Solrcore之间是相互隔离。
4)在solrcore中有一个文件夹叫做conf,包含了索引solr实例的配置信息。
5)在conf文件夹下有一个solrconfig.xml。配置实例的相关信息。如果使用默认配置可以不用做任何修改。
solrconfig.xml的配置信息:
Lib:solr服务依赖的扩展包,默认的路径是collection1\lib文件夹,如果没有就创建一个
dataDir:配置了索引库的存放路径。默认路径是collection1\data文件夹,如 果没有data文件夹,会自动创建。
requestHandler:
查询时使用的url
explicit
10
text
维护索引是使用的url
- 告诉solr服务器配置文件也就是solrHome的位置。修改web.xml使用jndi的方式告诉solr服务器。
Solr/home名称必须是固定的。
- 启动tomcat
- 访问http://localhost:8080/solr/
4. solr后台管理
4.1. 管理页面
4.2. Dashboard
仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。
4.3. Logging
Solr 运行日志信息
4.4. Cloud
Cloud即SolrCloud,即Solr云(集群),当使用Solr Cloud模式运行时会显示此菜单,如下图是Solr Cloud的管理界面:4.5. Core Admin
Solr Core的管理界面。Solr Core 是Solr的一个独立运行实例单位,它可以对外提供索引和搜索服务,一个Solr工程可以运行多个SolrCore(Solr实例),一个Core对应一个索引目录。
添加solrcore:
第一步:复制collection1改名为collection2
第二步:修改core.properties。name=collection2
第三步:重启tomcat
4.6. java properties
Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。
4.7. Tread Dump
显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。
4.8. Core selector
选择一个SolrCore进行详细操作
4.9. Analysis
4.10. Dataimport
可以定义数据导入处理器,从关系数据库将数据导入 到Solr索引库中。
4.11. Document
4.12. Query
5. 配置中文分析器
5.1. Schema.xml
schema.xml,在SolrCore的conf目录下,它是Solr数据表配置文件,它定义了加入索引的数据的数据类型的。主要包括FieldTypes、Fields和其他的一些缺省设置。
- FieldType 域类型定义
下边“text_general”是Solr默认提供的FieldType,通过它说明FieldType定义的内容:
FieldType子结点包括:name,class,positionIncrementGap等一些参数:
name
:是这个FieldType的名称
class
:是Solr提供的包solr.TextField,solr.TextField 允许用户通过分析器来定制索引和查询,分析器包括一个分词器(tokenizer)和多个过滤器(filter)
positionIncrementGap
:可选属性,定义在同一个文档中此类型数据的空白间隔,避免短语匹配错误,此值相当于Lucene的短语查询设置slop值,根据经验设置为100。
在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤
索引分析器中:使用solr.StandardTokenizerFactory
标准分词器,solr.StopFilterFactory
停用词过滤器,solr.LowerCaseFilterFactory
小写过滤器。
搜索分析器中:使用solr.StandardTokenizerFactory
标准分词器,solr.StopFilterFactory
停用词过滤器,这里还用到了solr.SynonymFilterFactory
同义词过滤器。
- Field 域定义
在fields结点内定义具体的Field,filed定义包括name,type(为之前定义过的各种FieldType),indexed(是否被索引),stored(是否被储存),multiValued(是否存储多个值)等属性。
如下:
multiValued
:该Field如果要存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),商品的图片(多个,大图和小图),通过使用solr查询要看出返回给客户端是数组:
uniqueKey
Solr中默认定义唯一主键key为id域,如下:
id
Solr在删除、更新索引时使用id域进行判断,也可以自定义唯一主键。
注意在创建索引时必须指定唯一约束。
dynamicField(动态字段)
动态字段就是不用指定具体的名称,只要定义字段名称的规则,例如定义一个 dynamicField,name 为*_i,定义它的type为text,那么在使用这个字段的时候,任何以_i结尾的字段都被认为是符合这个定义的,例如:name_i,gender_i,school_i等。
5.2 安装中文分词器
- 使用IKAnalyzer中文分析器。
第一步:
把IKAnalyzer2012FF_u1.jar添加到solr/WEB-INF/lib目录下。
第二步:
复制IKAnalyzer的配置文件和自定义词典和停用词词典到solr的classpath下。
cp IkAnnlyzer.cfg.xml ext_stopword.dic mydict.dic /usr/local/solr/tomcat/webapps/slor/WEB-INF/classes
第三步:
在schema.xml中添加一个自定义的fieldType,使用中文分析器。
第四步:
定义field,指定field的type属性为text_ik
第五步:
重启tomcat
5.3 设置业务系统Field
如果不使用Solr提供的Field可以针对具体的业务需要自定义一套Field,如下是商品信息Field:
6. solr后台管理索引库
6.1. 添加文档
6.2. 删除文档
6.3. 修改文档
6.4. 查询文档
6.1 添加文档
- 添加单个文档
-
批量导入数据
使用Dataimport插件批量导入数据
- 把dataimport插件依赖的jar包和mysql的数据库驱动jar包添加到solrcore(collection1\lib)中
配置solrconfig.mxl文件,添加一个requestHandler
data-config.xml
- 创建一个data-config.xml,保存到collection1\conf\目录下
-
重启tomcat
点击“execute”按钮导入数据
导入数据前会先清空索引库,然后再导入。
6.2. 删除文档
删除索引格式如下
格式 | 说明 |
---|---|
删除指定ID的索引 | |
删除查询到的索引数据 | |
删除所有索引数据 |
6.3. 修改文档
修改跟添加效果一样,修改是先删除再添加,先删除对应的id文档,再添加到索引库中
6.4 查询索引
7. 使用SolrJ管理索引库
7.1. 添加文档
7.2. 修改文档
7.3. 删除文档
7.4. 查询文档
什么是solrJ
solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,SolrJ通常在嵌入在业务系统中,通过SolrJ的API接口操作Solr服务,如下图:
依赖的jar包
7.1. 添加文档
实现步骤:
第一步:创建一个java工程
第二步:导入jar包。solr的jar包和包括solrJ的jar包。还需要
第三步:和Solr服务器建立连接。HttpSolrServer对象建立连接。
第四步:创建一个SolrInputDocument对象,然后添加域。
第五步:将SolrInputDocument添加到索引库。
第六步:提交。代码实现
/**
* solrj向索引库添加文档
*/
@Test
public void testAddDocument() throws Exception {
// 创建SolrServer对象,创建链接
// 参数:solr服务的地址 ,默认连接collection1
// http://localhost:8080/solr/collection2 连接2
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 创建一个文档对象
SolrInputDocument document = new SolrInputDocument();
// 向文档中添加域
// 每个文档必须有Id域,而且其它域必须在schema.xml
document.addField("id", "test001");
document.addField("product_name", "测试商品");
document.addField("product_price", "100");
// 把文档添加到索引库
solrServer.add(document);
// 提交
solrServer.commit();
}
7.2. 修改文档
- 代码实现
/**
* solrj向索引库更新文档
*/
@Test
public void testUpdateDocument() throws Exception {
// 创建SolrServer对象,创建链接
// 参数:solr服务的地址 ,默认连接collection1
// http://localhost:8080/solr/collection2 连接2
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 创建一个文档对象
SolrInputDocument document = new SolrInputDocument();
// 向文档中添加域
// 每个文档必须有Id域,而且其它域必须在schema.xml
document.addField("id", "test001");
document.addField("product_name", "测试商品2");
document.addField("product_price", "1002");
// 把文档添加到索引库
solrServer.add(document);
// 提交
solrServer.commit();
}
7.3. 删除文档
- 根据id删除
/**
* solrj向索引库根据ID删除文档
*/
@Test
public void testDelDocumentById() throws Exception {
// 创建SolrServer对象,创建链接
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 根据id删除文档
solrServer.deleteById("test001");
// 提交
solrServer.commit();
}
- 根据查询删除
/**
* solrj向索引库根据查询内容删除文档
*/
@Test
public void testDelDocumentByQuery() throws Exception {
// 创建SolrServer对象,创建链接
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 根据id删除文档
solrServer.deleteByQuery("*:*");
//solrServer.deleteByQuery("id:test001");
// 提交
solrServer.commit();
}
7.4. 查询文档
- 简单查询
/**
* 查询索引库
*/
@Test
public void testQueryIndex() throws Exception {
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 常见一个SolrQuery对象
SolrQuery query = new SolrQuery();
// 设置查询条件
// query.set("q", "*:*");
query.setQuery("*:*");
// 执行查询
QueryResponse response = solrServer.query(query);
// 获取查询结果
SolrDocumentList results = response.getResults();
System.out.println("查询结果总记录数:" + results.getNumFound());
// 遍历查询结果
for (SolrDocument solrDocument : results) {
System.out.println(solrDocument.get("id"));
System.out.println(solrDocument.get("product_name"));
System.out.println(solrDocument.get("product_price"));
System.out.println(solrDocument.get("product_catelog_name"));
System.out.println(solrDocument.get("product_picture"));
}
}
- 复杂查询
/**
* 复杂查询索引库
*/
@Test
public void testQueryIndexFuZa() throws Exception {
SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
// 常见一个SolrQuery对象
SolrQuery query = new SolrQuery();
// 设置主查询条件
query.setQuery("厨房");
// 设置过滤条件
query.addFilterQuery("product_price:[0 TO 20]");
// 根据价格排序
// 参数1:要排序的域 , 参数2:排序方式
query.setSort("product_price", ORDER.asc);
// 设置分页
query.setStart(0);
query.setRows(6);
// 设置返回结果包含的域
query.setFields("id", "product_name", "product_price", "product_catelog_name", "product_picture");
// 设置默认搜索域
query.set("df", "product_keywords");
// 开启高亮
query.setHighlight(true);
// 高亮后显示的域
query.addHighlightField("product_name");
// 高亮前缀
query.setHighlightSimplePre("");
// 高亮后缀
query.setHighlightSimplePost("");
// 执行查询
QueryResponse response = solrServer.query(query);
// 获取查询结果
SolrDocumentList results = response.getResults();
System.out.println("查询结果总记录数:" + results.getNumFound());
// 遍历查询结果
for (SolrDocument solrDocument : results) {
System.out.println(solrDocument.get("id"));
//取高亮显示
Map>> highlighting = response.getHighlighting();
List list = highlighting.get(solrDocument.get("id")).get("product_name");
String productName = "";
if(list != null && list.size() > 0) {
productName = list.get(0);
}else {
productName = (String) solrDocument.get("product_name");
}
System.out.println(productName);
System.out.println(solrDocument.get("product_price"));
System.out.println(solrDocument.get("product_catelog_name"));
System.out.println(solrDocument.get("product_picture"));
}
}
8. 电商搜索案例实现
-
原型分析
-
系统架构
工程搭建
创建一个web工程导入jar包
1、springmvc的相关jar包
2、solrJ的jar包
3、Example\lib\ext下的jar包
springmvc.xml
web.xml
springmvc-first
index.html
index.htm
index.jsp
default.html
default.htm
default.jsp
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
springmvc
*.action
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
utf-8
CharacterEncodingFilter
/*
pojo
public class ProductModel implements Serializable {
private static final long serialVersionUID = 201671751716316277L;
private String pid; // 商品编号
private String name; // 商品名称
private String catalog_name; // 商品分类名称
private float price; // 价格
private String description; // 商品描述
private String picture; // 图片名称
}
public class ResultModel implements Serializable {
private static final long serialVersionUID = 8774196519712648201L;
private List productList; // 商品列表
private Long recordCount; // 商品总数
private int pageCount; // 总页数
private int curPage; // 当前页
}
Dao层
功能:接收service层传递过来的参数,根据参数查询索引库,返回查询结果。
参数:SolrQuery对象
返回值:一个商品列表List
返回:ResultModel
方法定义:ResultModel queryProduct(SolrQuery query) throws Exception;
/**
* 商品搜索DAO
* @author koax
*/
@Repository
public class ProductDao {
@Autowired
private SolrServer solrServer;
public ResultModel search(SolrQuery query) throws Exception {
// 执行查询
QueryResponse response = solrServer.query(query);
// 取查询结果
SolrDocumentList solrDocumentList = response.getResults();
// 取查询结果总记录数
ResultModel resultModel = new ResultModel();
resultModel.setRecordCount(solrDocumentList.getNumFound());
//取结果集,定义一个商品列表
List productList = new ArrayList<>();
for (SolrDocument solrDocument : solrDocumentList) {
//创建一个商品对象
ProductModel productModel = new ProductModel();
productModel.setPid((String)solrDocument.get("id"));
productModel.setCatalog_name((String)solrDocument.get("product_catalog_name"));
//取高亮显示
Map>> highlighting = response.getHighlighting();
List list2 = highlighting.get(solrDocument.get("id")).get("product_name");
String productName = "";
if(list2 != null && list2.size() >0) {
productName = list2.get(0);
}else {
productName = (String)solrDocument.get("product_name");
}
productModel.setName(productName);
productModel.setPicture((String)solrDocument.get("product_picture"));
productModel.setPrice((float)solrDocument.get("product_price"));
productList.add(productModel);
}
resultModel.setProductList(productList);
return resultModel;
}
}
Service
功能:接收action传递过来的参数,根据参数拼装一个查询条件,调用dao层方法,查询商品列表。接收返回的商品列表和商品的总数量,根据每页显示的商品数量计算总页数。
参数:
1、查询条件:字符串
2、商品分类的过滤条件:商品的分类名称,字符串
3、商品价格区间:传递一个字符串,满足格式:“0-100、101-200、201-*”
4、排序条件:页面传递过来一个升序或者降序就可以,默认是价格排序。0:升序1:降序
5、分页信息:每页显示的记录条数创建一个常量60条。传递一个当前页码就可以了。
业务逻辑
1、根据参数创建查询对象
2、调用dao执行查询。
3、根据总记录数计算总页数。
返回值:ResultModel
方法定义:ResultModel queryProduct(String queryString, String catalog_name, String price, String sort, Integer page) throws Exception;
/**
* 商品查询service
*
* @author koax
*/
@Service
public class ProductService {
private static final int PAGE_SIZE = 60;
@Autowired
private ProductDao productDao;
public ResultModel queryProduct(String queryString, String caltalog_name, String price, String sort, Integer page)
throws Exception {
// 1.根据参数创建查询对象
SolrQuery query = new SolrQuery();
// 设置查询条件
if (null != queryString && !"".equals(queryString)) {
query.setQuery(queryString);
} else {
query.setQuery("*:*");
}
// 商品分类过滤
if (null != caltalog_name && !"".equals(caltalog_name)) {
query.addFilterQuery("product_catalog_name:" + caltalog_name);
}
// 价格区间过滤
if (null != price && !"".equals(price)) {
String[] strings = price.split("-");
query.addFilterQuery("product_price:[" + strings[0] + " TO " + strings[1] + "]");
}
// 排序条件
if ("1".equals(sort)) {
query.setSort("product_price", ORDER.desc);
} else {
query.setSort("product_price", ORDER.asc);
}
// 分页处理
if (page == null) page = 1;
query.setStart((page - 1) * PAGE_SIZE);
query.setRows(PAGE_SIZE);
// 默认搜索域
query.set("df", "product_keywords");
//设置高亮
query.setHighlight(true);
query.addHighlightField("product_name");
query.setHighlightSimplePre("");
query.setHighlightSimplePost("");
// 2.调用dao执行查询
ResultModel resultModel = productDao.search(query);
// 3.根据总记录数计算总页数
Long recordCount = resultModel.getRecordCount();
long pageCount = recordCount / PAGE_SIZE;
if(recordCount % PAGE_SIZE > 0) {
pageCount++;
}
resultModel.setPageCount((int)pageCount);
resultModel.setCurPage(page);
return resultModel;
}
}
Controller
功能:接收页面传递过来的参数调用service查询商品列表。将查询结果返回给jsp页面,还需要查询参数的回显。
参数:
1、查询条件:字符串
2、商品分类的过滤条件:商品的分类名称,字符串
3、商品价格区间:传递一个字符串,满足格式:“0-100、101-200、201-*”
4、排序条件:页面传递过来一个升序或者降序就可以,默认是价格排序。0:升序1:降序
5、分页信息:每页显示的记录条数创建一个常量60条。传递一个当前页码就可以了。
6、Model:相当于request。
返回结果:String类型,就是一个jsp的名称。
String queryProduct(String queryString, String catalog_name, String price, String sort, Integer page, Model model) throws Exception;
/**
* 商品搜索controller
* @author koax
*/
@Controller
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/list")
public String productSearch(String queryString, String catalog_name, String price,
String sort, Integer page, Model model) throws Exception {
//调用服务查询商品列表
ResultModel resultModel = productService.queryProduct(queryString, catalog_name, price, sort, page);
//传递给页面
model.addAttribute("queryString",queryString);
model.addAttribute("catalog_name",catalog_name);
model.addAttribute("price",price);
model.addAttribute("sort",sort);
model.addAttribute("page",page);
model.addAttribute("result",resultModel);
//返回逻辑视图
return "product_list";
}
}
jsp页面