solr

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. 整合步骤

  1. 安装tomcat。D:\temp\apache-tomcat-7.0.53
  2. 把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
  3. solr.war解压。使用压缩工具解压或者启动tomcat自动解压。解压之后删除solr.war
  4. 把\solr-4.10.3\example\lib\ext目录下的所有的jar包添加到tomcat中solr工程中的lib目录下
  5. 配置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
  <requestHandler name="/select" class="solr.SearchHandler">
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <int name="rows">10</int>
       <str name="df">text</str>
     </lst>
    </requestHandler>

维护索引是使用的url
  <requestHandler name="/update" class="solr.UpdateRequestHandler">
  </requestHandler>
  1. 告诉solr服务器配置文件也就是solrHome的位置。修改web.xml使用jndi的方式告诉solr服务器。
    Solr/home名称必须是固定的。
  2. 启动tomcat
  3. 访问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和其他的一些缺省设置。

  1. FieldType 域类型定义
    下边“text_general”是Solr默认提供的FieldType,通过它说明FieldType定义的内容:
    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </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同义词过滤器。

  1. Field 域定义
    在fields结点内定义具体的Field,filed定义包括name,type(为之前定义过的各种FieldType),indexed(是否被索引),stored(是否被储存),multiValued(是否存储多个值)等属性。
    如下:
   <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 
        
   <field name="sku" type="text_en_splitting_tight" indexed="true" stored="true" omitNorms="true"/>

multiValued:该Field如果要存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),商品的图片(多个,大图和小图),通过使用solr查询要看出返回给客户端是数组:

uniqueKey
Solr中默认定义唯一主键key为id域,如下:

 <uniqueKey>id</uniqueKey>

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,使用中文分析器。

<!-- IKAnalyzer-->
    <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

5.3 设置业务系统Field

如果不使用Solr提供的Field可以针对具体的业务需要自定义一套Field,如下是商品信息Field:

<!--product-->
   <field name="product_name" type="text_ik" indexed="true" stored="true"/>
   <field name="product_price"  type="float" indexed="true" stored="true"/>
   <field name="product_description" type="text_ik" indexed="true" stored="false" />
   <field name="product_picture" type="string" indexed="false" stored="true" />
   <field name="product_catalog_name" type="string" indexed="true" stored="true" />

   <field name="product_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>
   <copyField source="product_name" dest="product_keywords"/>
   <copyField source="product_description" dest="product_keywords"/>

6. solr后台管理索引库

  6.1. 添加文档
  6.2. 删除文档
  6.3. 修改文档
  6.4. 查询文档

6.1 添加文档
  1. 添加单个文档
  2. 批量导入数据
    使用Dataimport插件批量导入数据

    1. 把dataimport插件依赖的jar包和mysql的数据库驱动jar包添加到solrcore(collection1\lib)中
  3. 配置solrconfig.mxl文件,添加一个requestHandler

<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">
    <lst name="defaults">
      <str name="config">data-config.xml</str>
    </lst>
</requestHandler> 
  1. 创建一个data-config.xml,保存到collection1\conf\目录下
<?xml version="1.0" encoding="UTF-8" ?>  
<dataConfig>   
<dataSource type="JdbcDataSource"   
          driver="com.mysql.jdbc.Driver"   
          url="jdbc:mysql://localhost:3306/lucene"   
          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>
  1. 重启tomcat


  2. 点击“execute”按钮导入数据
    导入数据前会先清空索引库,然后再导入。

6.2. 删除文档

删除索引格式如下

格式 说明
<delete>
 <id>8</id>
</delete>
<commit/>
删除指定ID的索引
<delete>
 <query>product_name:台灯</query>
</delete>
<commit/>
删除查询到的索引数据
<delete>
 <query>*:*</query>
</delete>
<commit/>
删除所有索引数据
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("<em>");
        // 高亮后缀
        query.setHighlightSimplePost("</em>");
        // 执行查询
        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<String, Map<String, List<String>>> highlighting = response.getHighlighting();
            List<String> 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. 电商搜索案例实现

  1. 原型分析


  2. 系统架构


  3. 工程搭建
    创建一个web工程导入jar包
    1、springmvc的相关jar包
    2、solrJ的jar包
    3、Example\lib\ext下的jar包

springmvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="com.shop.jd" />
    <!-- 配置注解驱动,如果配置此标签可以不用配置处理器映射器和适配器 -->
    <mvc:annotation-driven />
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!-- SolrServer的配置 -->
    <bean id="httpSolrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer">
        <constructor-arg index="0" value="http://localhost:8080/solr" />
    </bean>
</beans>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>springmvc-first</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    <!-- 配置前段控制器 -->
    <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>
    <!-- 解决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>

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<ProductModel> productList; // 商品列表
    private Long recordCount; // 商品总数
    private int pageCount; // 总页数
    private int curPage; // 当前页
}

Dao层
功能:接收service层传递过来的参数,根据参数查询索引库,返回查询结果。
参数:SolrQuery对象
返回值:一个商品列表List<ProductModel>,还需要返回查询结果的总数量。
返回: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<ProductModel> 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<String, Map<String, List<String>>> highlighting = response.getHighlighting();
            List<String> 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("<em style=\"color:read\">");
        query.setHighlightSimplePost("</em>");
        // 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页面

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容