Solr

一、Solr的简介

Solr 是Apache的顶级开源项目,采用Java开发,基于Lucene的全文搜索服务器。可独立运行在Jetty、Tomcat等Servlet容器中,用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr 根据XML 文档添加、删除、更新索引。Solr 搜索只需要发送 HTTP GET 请求,然后对 Solr返回XML 、JSON等格式的查询结果进行解析,组织页面布局。

Solr和Lucene的区别

(1)Lucene是全文检索引擎工具包,不能独立运行;Solr全文检索引擎,可独立运行

(2)Lucene开发工作量大(索引维护、索引性能优化、搜索性能优化);Solr可以快速的构建企业的搜索引擎

应用:站内搜索

 

二、Solr的安装和配置

1、目录结构

bin:solr的运行脚本

contrib:solr的一些贡献软件/插件,用于增强solr的功能

dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件

docs:solr的API文档

example:solr工程的例子目录

----solr:包含了默认配置信息的Solr的Core目录

----multicore:包含了在Solr的multicore中设置的多个Core目录

----webapps:包括一个solr.war,该war可作为solr的运行实例工程

licenses:solr相关的一些许可信息

 

2、运行环境

需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上。Solr默认提供Jetty(java)

 

3、Solr整合Tomcat

3.1 SolrHome和SolrCare

复制example/solr到磁盘根目录改名SolrHome(单独的文件夹,不能放到Tomcat下),SolrHome是Solr运行的主目录,包括了运行Solr实例所有的配置文件和数据文件,Solr实例就是

SolrCore,一个SolrHome可以包括多个SolrCore(Solr实例),每个SolrCore提供单独的搜索和索引服务

SolrHome目录结构:

collection1:SolrCore(Solr实例)目录,SolrCore名称不固定,一个solr运行实例对外单独提供索引和搜索接口。solrHome中可以创建多个solr运行实例SolrCore。一个solr的运行

实例对应一个索引目录

conf:SolrCore的配置文件目录

data:存放索引文件需要创建

 

3.2 整合

第一步:安装tomcat。D:\apache-tomcat-7.0.53

第二步:把solr的war包复制到tomcat 的webapp目录下

  把\solr-4.10.3\dist\solr-4.10.3.war复制到D:\apache-tomcat-7.0.53\webapps下。

改名为solr.war

第三步:solr.war解压。使用压缩工具解压或者启动tomcat自动解压。解压之后删除solr.war

第四步:把\solr-4.10.3\example\lib\ext目录下的所有的jar包添加到solr工程中

第五步:配置solrHome和solrCore

(1)创建一个solrhome(存放solr所有配置文件的一个文件夹)。solr-4.10.3\example\solr目录就是一个标准的solrhome

(2)把\solr-4.10.3\example\solr文件夹复制到D:\根目录下,改名为solrhome

(3)在solrhome下有一个文件夹叫做collection1这就是一个solrcore。就是一个solr的实例。一个solrcore相当于mysql中一个数据库。Solrcore之间是相互隔离

1.在solrcore中有一个文件夹叫做conf,包含了索引solr实例的配置信息

2.在conf文件夹下有一个solrconfig.xml。配置实例的相关信息。如果使用默认配置可以不做任何修改

Lib:solr服务依赖的扩展包,默认的路径是collection1\lib文件夹,如果没有就创建一个

dataDir:配置了索引库的存放路径。默认路径是collection1\data文件夹,如果没有data文件夹,会自动创建

requestHandler

第六步 solr服务器配置文件(solrHome)的位置

tomcat\webapps\solr\WEB-INF\web.xml

打开<env-entry>的注释,配置solrHome目录

第七步:启动tomcat  

E:\Program\apache-tomcat-7.0.52test\bin\startup.bat

第八步:访问http://localhost:8080/solr

 

4、Solr后台管理

(1)Analysis:测试索引分析器和搜索分析器的执行情况

(2)Document:创建索引、更新索引、删除索引

/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新

(3)Query:通过/select执行搜索索引,必须指定“q”查询条件才能搜索

 

5、配置中文分词器

schema.xml,在SolrCore的solrHome\collection1\conf目录下,数据表配置文件,定义了加入索引的数据的数据类型的。主要包括FieldTypes、Fields和其他的一些缺省设置

域(Filed)的分类

普通域(Filed):string long 等

动态域(DynamicFiled):起到模糊匹配的效果,可以模糊匹配没有定义过的域名

  例如:xxxx这个域名没有定义,但是xxxx_s这个域名模糊匹配了*_s这个域,所以相当于xxxx_s这个域定义了

主键域(uniqueKey):<uniqueKey>id</uniqueKey> 一般主键域就用默认的这个就可以不需要更改或者添加

复制域(copyField): 复制域用于查询的时候从多个域中进行查询,这样可以将多个域复制到某一个统一的域中,然后搜索的时候从这个统一的域中进行查询,就相当于从多个域中查询了

安装中文分词器

第一步:IKAnalyzer2012FF_u1.jar导入tomcat/webapps/solr/WEB-INF/lib

第二步:IKAnalyzer的配置文件IKAnalyzer.cfg.xml、停用字典stopword.dic、扩展字典ext.dic都复制到solr的classpath(WEB-INF)新建的classes目录下tomcat/webapps/solr/WEB-INF/classes

第三步:solrHome\collection1\conf下的schema.xml加入

自定义的fieldType,使用中文分析器

<!-- IKAnalyzer Filed Type-->
<fieldType name="text_ik" class="solr.TextField">
  <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>

定义field,指定field的type属性为text_ik

<!--IKAnalyzer Field-->
<field name="title_ik" type="text_ik" indexed="true" stored="true" />
<field name="content_ik" type="text_ik" indexed="true" stored="false" multiValued="true"/>

第四步:重启Tomcat

访问http://localhost:8080/solr,Analysis即可显示效果

 

是否存储和是否索引无关, 索引后就能查询,不索引就不能根据这个域搜索。存储后就能取出来里面的内容,不存储就取不出这个域内容

 

三、管理索引库

1、维护索引

1.1 单个添加

document中添加

1.2 批量导入

使用dataimport插件

从solr/dist中把solr-dataimporthandler-4.10.3.jar、solr-dataimporthandler-extras-4.10.3.jar、数据库驱动包mysql-connector-java-5.1.7-bin.jar 导入到solrHome\collection1\lib新建的

lib包下

在solrHome\collection1\conf\solrconfig.xml中添加requestHandler

<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
      <str name="config">data-config.xml</str>
    </lst>
</requestHandler> 

在相同目录下新建data-config.xml

<?xml version="1.0" encoding="UTF-8" ?>  
<dataConfig>   
<dataSource type="JdbcDataSource"   
          driver="com.mysql.jdbc.Driver"   
          url="jdbc:mysql://localhost:3306/solr"   
          user="root"   
          password="root"/>   
<document>   
    <entity name="product" query="SELECT pid,name,catalog_name,price,description,picture FROM products ">
         <field column="pid" name="id"/> 
         <field column="name" name="product_name"/> 
         <field column="catalog_name" name="product_catalog_name"/> 
         <field column="price" name="product_price"/> 
         <field column="description" name="product_description"/> 
         <field column="picture" name="product_picture"/> 
    </entity>   
</document>   

</dataConfig>

 Dataimport ----> Execute,勾选自动刷新,在query中就可以查询到批量导入的数据

 

2、删除文档

document界面

2.1 删除指定ID的索引

<delete>
    <id>8</id>
</delete>
<commit />

2.2 删除查询到的索引数据

<delete>
    <query>product_catalog_name:幽默杂货</query>
</delete>
<commit />

2.3 删除所有索引数据

<delete>
    <query>*:*</query>
</delete>
<commit />

 

3、查询索引

Query界面

1. q :查询字符串,必须的,如果查询所有使用*:*

2. fq:(filter query)过虑查询,作用:在q查询符合结果中同时是fq查询符合的

 过滤查询价格从1到20的记录

// 域名:条件
product_price : [1 TO 20]

可以在“q”查询条件中使用product_price:[1 TO 20]

可以使用“*”表示无限

3. sort : 排序

product_price desc/asc

4、start:分页显示使用,开始记录下标,从0开始

5、rows :指定返回结果最多有多少条记录,配合start来实现分页

6、fl :指定返回那些域内容,用逗号或空格分隔多个

7、df:指定一个搜索Field

product_keywords

也可以在SolrCore目录 中conf/solrconfig.xml文件中的SearchHandler指定默认搜索Field,指定后就可以直接在“q”查询条件中输入关键字。

8、wt - (writer type)指定输出格式,可以有 xml, json, php, php

9、hl 是否高亮 ,设置高亮域Field,设置格式前缀和后缀

 

 四、SolrJ

Solr用于服务器端,SolrJ作为客户端

导包solr-4.10.3\dist\solr-solrj-4.10.3.jar、solrj-lib\*、日志包solr-4.10.3\example\lib\ext\*

1、增删改

没有专门的改,增加的时候改

public class IndexManagerTest {

    @Test
    public void testIndexCreate() throws Exception{
        //创建和Solr服务端连接
        SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
        
        //创建solr文档对象
        SolrInputDocument doc = new SolrInputDocument();
        //域要先定义后使用,还有注意必须要有id主键域
        //solr中没有专用的修改方法, 会自动根据id进行查找,如果找到了则删除原来的将新的加入就是修改,如果没找到,将新的直接加入则就是新增
        doc.addField("id", "a001");
        doc.addField("product_name", "台灯1`111");
        doc.addField("product_price", "12.5");
        
        //将文档加入solrServer对象中
        solrServer.add(doc);
        
        //提交
        solrServer.commit();
    }
    
    @Test
    public void testIndexDel() throws Exception{
        //创建和Solr服务端连接
        SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
        
        //根据主键id进行删除
        //solrServer.deleteById("a001");
        
        //根据查询删除,这里是删除所有*:*
        solrServer.deleteByQuery("*:*");
        //提交
        solrServer.commit();
    }
}

2、查询

public class IndexSearchTest {

    @Test
    public void testIndexSearch1() throws Exception{
        //连接solr服务端
        SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
        
        //创建solr查询条件对象
        SolrQuery solrQuery = new SolrQuery();
        //查询所有
        solrQuery.setQuery("*:*");
        
        //查询并获取查询响应对象
        QueryResponse queryResponse = solrServer.query(solrQuery);
        //从查询响应中获取查询结果集对象
        SolrDocumentList results = queryResponse.getResults();
        //打印一共查询到多少条记录,也就是记录总数
        System.out.println("=====count====" + results.getNumFound());
        //遍历查询结果集
        for(SolrDocument doc : results){
            System.out.println("============="+doc.get("id"));
            System.out.println("============="+doc.get("product_name"));
            System.out.println("============="+doc.get("product_price"));
            System.out.println("====================================================");
        }
    }
    
    @Test
    public void testIndexSearch2() throws Exception{
        //连接solr服务端
        SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
        
        //创建solr查询条件对象
        SolrQuery solrQuery = new SolrQuery();
        //查询关键字输入
        solrQuery.setQuery("台灯");
        //设置默认搜索域
        solrQuery.set("df", "product_keywords");
        //设置过滤查询
        solrQuery.addFilterQuery("product_price:[1 TO 100]");
        //设置排序,这里是降序
        solrQuery.setSort("product_price", ORDER.desc);
        //=======设置分页========
        //设置起始条数
        solrQuery.setStart(0);
        //设置查询多少条
        solrQuery.setRows(50);
        
        //========设置高亮显示=======
        //高亮默认是关闭的,所以要手动开启
        solrQuery.setHighlight(true);
        //设置需要高亮显示的域
        solrQuery.addHighlightField("product_name");
        //设置高亮前缀
        solrQuery.setHighlightSimplePre("<span style=\"color:red\">");
        //设置高亮后缀
        solrQuery.setHighlightSimplePost("</span>");
        
        //===================查询并获取查询响应对象=====================================
        QueryResponse queryResponse = solrServer.query(solrQuery);
        //从查询响应中获取查询结果集对象
        SolrDocumentList results = queryResponse.getResults();
        //打印一共查询到多少条记录,也就是记录总数
        System.out.println("=====count====" + results.getNumFound());
        //遍历查询结果集
        for(SolrDocument doc : results){
            System.out.println("============="+doc.get("id"));
            //获取高亮
            Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
            List<String> list = highlighting.get(doc.get("id")).get("product_name");
            if(list != null && list.size() > 0){
                String hlName = list.get(0);
                System.out.println("=======high lighting=====" + hlName);
            }
            
            System.out.println("============="+doc.get("product_name"));
            System.out.println("============="+doc.get("product_price"));
            System.out.println("====================================================");
        }
    }
}

 

五、总结

1.solr是一个全文检索引擎系统,通过部署到tomcat下就可以独立运行,通过http协议对外提供全文检索服务,就是索引和文档的正删改查服务

2. solr直接操作索引库和文档库, 我们的业务系统中可以使用solrJ(solr的客户端,就是一堆jar包)来调用solr服务端,让solr服务端操作文档库和索引库,完成正删改查的任务,将结果返回

给solrJ客户端,我们在业务系统中就可以,获取到结果然后返回给客户在浏览器中显示

3. solrHome:solrhome就是solr最核心的目录, 一个solrhome中可以有多个solr实例

4. solrCore:一个solrCore就是一个solr实例,solr中实例与实例之间他们的索引库和文档库是相互隔离的。每个实例对外单独的提供索引和文档的增删改查服务,默认实例collection1

5. 文档和索引的增加和修改必须要有id, 主键域,没有会报错

6. 域名和类型必须先定义后使用,如果没有定义就使用会报错

7. 域的分类

  普通域:string long 等

  动态域:起到模糊匹配的效果,可以模糊匹配没有定义过的域名

    例如:xxxx这个域名没有定义,但是xxxx_s这个域名模糊匹配了*_s这个域,所以相当于xxxx_s这个域定义了

  主键域:<uniqueKey>id</uniqueKey> 一般主键域就用默认的这个就可以不需要更改或者添加

  复制域: 复制域用于查询的时候从多个域中进行查询,这样可以将多个域复制到某一个统一的域中,然后搜索的时候从这个统一的域中进行查询,就相当于从多个域中查询了

6.是否存储和是否索引无关, 索引后就能查询,不索引就不能根据这个域搜索,,存储后就能取出来里面的内容,不存储就取不出这个域内容

7. 一般企业中将数据全部放入数据库中, 由于查询的时候需要使用like模糊查询,模糊查询数据库中使用的是全表扫描算法,这样效率低级,所以需要使用全文检索,来优化查询速度.

 

六、搜索案例

1、系统架构

2、导包

SpringMVC包、solrJ的包、日志包example/lib/*

2.1 web.xml

前端控制器、POST乱码

 <!-- 1.配置前端控制器 -->
  <servlet>
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
          <!-- 指定springmvc配置文件的路径 
              如果不指定默认为:/WEB-INF/${servlet-name}-servlet.xml
          -->
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc.xml</param-value>
      </init-param>
  </servlet>
  <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>*.action</url-pattern>
  </servlet-mapping>
  <!-- 2.解决post乱码问题 -->
  <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

2.2 SpringMVC.xml

包扫描、注解驱动、视图解析器、SolrServer

<!-- 1.包扫描,controller、service、dao全部扫描-->
<context:component-scan base-package="com.guojie"/>
<!-- 2.配置注解驱动,如果配置此标签可以不用配置处理器映射器和适配器 -->
<mvc:annotation-driven/>
<!-- 3.配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!-- 4.SolrServer的配置 -->
<bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
  <!-- index=0代表调用有一个构造参数的solrServer的构造方法 -->
    <constructor-arg index="0" value="http://localhost:8080/solr"/>
</bean>

 

3、POJO

// 商品对象模型
public class ProductModel {
    // 商品编号
    private String pid;
    // 商品名称
    private String name;
    // 商品分类名称
    private String catalog_name;
    // 价格
    private float price;
    // 商品描述
    private String description;
    // 图片名称
    private String picture;
}
// 返回值对象模型
public class ResultModel {
    // 商品列表
    private List<ProductModel> productList;
    // 商品总数
    private Long recordCount;
    // 总页数
    private int pageCount;
    // 当前页
    private int curPage;
}

 

4、DAO

功能:接收Service层传过来的参数,根据参数查询索引库,返回查询结果

参数:SolrQuery对象

返回值:一个商品列表List<ProductModel>,还需要返回查询结果的总数量

返回:ResultModel

方法:ResultModel queryProduct(SolrQuery query) throws Exception

@Repository
public class ProductDaoImpl implements ProductDao {
    
    @Autowired
    private SolrServer solrServer;

    @Override
    public ResultModel queryProducts(SolrQuery solrQuery) throws Exception {
        //查询并获取查询响应
        QueryResponse queryResponse = solrServer.query(solrQuery);
        //从响应中获取查询结果集
        SolrDocumentList docList = queryResponse.getResults();
        
        //创建返回结果对象
        ResultModel resultModel = new ResultModel();
        List<ProductModel> productList = new ArrayList<ProductModel>();
        
        //遍历结果集
        if(docList != null){
            //获取总记录数
            resultModel.setRecordCount(docList.getNumFound());
            for(SolrDocument doc : docList){
                ProductModel product = new ProductModel();
                product.setPid(String.valueOf(doc.get("id")));
                
                //获取高亮
                Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
                if(highlighting != null){
                    List<String> list = highlighting.get(doc.get("id")).get("product_name");
            // 判断list不为空,且list的大小不为0
                    if(list != null && list.size() > 0){
                        product.setName(list.get(0));
                    } else {
                        product.setName(String.valueOf(doc.get("product_name")));
                    }
                } else {
                    product.setName(String.valueOf(doc.get("product_name")));
                }
                // String不为NULL且不为"",Java中没有sizeof
                if(doc.get("product_price") != null && !"".equals(doc.get("product_price"))){
                    product.setPrice(Float.valueOf(doc.get("product_price").toString()));
                }
                product.setCatalog_name(String.valueOf(doc.get("product_catalog_name")));
                product.setPicture(String.valueOf(doc.get("product_picture")));
                productList.add(product);
            }
            resultModel.setProductList(productList);
        }
        return resultModel;
    }

}

 

5、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 caltalog_name, String price,String sort, Integer page) throws Exception;

@Service
public class ProductServiceImpl implements ProductService {
    private static final Integer PAGE_SIZE = 60;

    @Autowired
    private ProductDao productDao;

    @Override
    public ResultModel query(String queryString, String catalog_name, String price, String sort, Integer page)
            throws Exception {
        //创建查询条件对象
        SolrQuery solrQuery = new SolrQuery();
        //设置默认搜索域
        solrQuery.set("df", "product_keywords");
        //设置查询关键字
        if(queryString != null && !"".equals(queryString)){
            solrQuery.setQuery(queryString);
        } else {
            solrQuery.setQuery("*:*");
        }
        
        //设置过滤条件按照分类名称进行过滤
        if(catalog_name != null && !"".equals(catalog_name)){
            solrQuery.addFilterQuery("product_catalog_name:" + catalog_name);
        }
        //设置过滤条件按照价格进行过滤
        if(price != null && !"".equals(price)){
            String[] split = price.split("-");
            if(split != null && split.length > 1){
                solrQuery.addFilterQuery("product_price:["+split[0]+" TO "+split[1]+"]");
            }
        }
        //设置排序
        if("1".equals(sort)){
            solrQuery.addSort("product_price", ORDER.asc);
        } else {
            solrQuery.addSort("product_price", ORDER.desc);
        }
        
        //设置分页
        if(page == null){
            page = 1;
        }
        Integer start = (page - 1) * PAGE_SIZE;
        //从第几天记录开始查
        solrQuery.setStart(start);
        //每页显示多少条
        solrQuery.setRows(PAGE_SIZE);
        
        //设置高亮显示
        solrQuery.setHighlight(true);
        //设置高亮显示的域
        solrQuery.addHighlightField("product_name");
        //设置高亮前缀
        solrQuery.setHighlightSimplePre("<span style=\"color:red\">");
        //设置高亮后缀
        solrQuery.setHighlightSimplePost("</span>");
        
        //查询返回结果
        ResultModel resultModel = productDao.queryProducts(solrQuery);
        
        resultModel.setCurPage(Long.parseLong(page.toString()));
        
        //计算总页数
        Long pageCount = resultModel.getRecordCount() / PAGE_SIZE;
        if(resultModel.getRecordCount() % PAGE_SIZE > 0){
            pageCount ++;
        }
        resultModel.setPageCount(pageCount);
        return resultModel;
    }
}

6、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 caltalog_name, String price, String sort, Integer page, Model model) throws Exception;

@Controller
public class ProductsController {
    
    @Autowired
    private ProductService productService;

    @RequestMapping("/list")
    public String list(String queryString, String catalog_name, String price, 
            String sort, Integer page, Model model) throws Exception{
        
        ResultModel result = productService.query(queryString, catalog_name, price, sort, page);
        
        //返回查询结果
        model.addAttribute("result", result);
        
        model.addAttribute("queryString", queryString);
        model.addAttribute("catalog_name", catalog_name);
        model.addAttribute("price", price);
        model.addAttribute("sort", sort);
        return "product_list";
    }
}

 

Java Project编译后的文件在bin下

Web项目编译后的文件在classes下

 

posted @ 2017-10-23 10:39  十年英雄梦  阅读(166)  评论(0编辑  收藏  举报