1、SolrCore的配置 a)schma.xml文件 b)配置中文分析器 2、配置业务域和批量索引导入 a)配置业务域 b)批量索引导入 c)Solrj复杂查询(用Query页面复杂查询、用程序实现) 3、京东案例(简单的站内搜索实现) 2.SolrCore的配置(重点) SolrCore的运行由两个重要的配置文件做指导,一个是solrconfig.xml,一个是schema.xml。 1)solrconfig.xml配置 依赖包、数据目录和请求处理器(/select,/update。。。)等,这是SolrCore的核心配置文件。之前已经介绍过了。 2)schema.xml配置: 主要是Field域的配置,Solr中Field的使用必须先配置然后使用,不能在代码中直接new。 2.1.schema.xml 域的配置包括:普通业务域(field)、动态域(dynamicField)、拷贝域(copyField)、唯一主键域(uniqueKey)、域类型 (fieldType)。 SolrCore本身自带了一些业务域的配置和域类型的定义,但就像MySQL数据库刚创建完就会有一个默认的mysql数据库一样,实际业务中这些自带的配置都没法使用,所以大概看一下就可以忽略掉。 在实际项目中都是我们自己根据业务需求定义自己的业务域。 2.1.1.field <field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> name:域的名称 type:域的类型 indexed:是否索引 stored:是否存储 required:是否必须 multiValued:是否是多值,存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id可以是多个,商品的图片url可以是多个,这些多个值可以存储在一个field域中。 注意:配置上面似乎缺少一个【是否分词】的配置属性,是因为【是否分词】是由fieldType的配置决定的,所以不在filed的配置中重复配置。 2.1.2.dynamicField(动态域) <dynamicField name="*_s" type="string" indexed="true" stored="true"/> name:动态域的名称,是一个通配的表达式,*匹配任意字符,只要域的名称和表达式的规则能够匹配就可以使用这个动态域作为普通业务域使用。 其他属性和正常field域配置一样,没有区别。 例如:添加索引id=zjl001,zhoujielun_s=zhoujielun001,其中zhoujielun_s在schema.xml中没有配置,但是这个符合动态域【*_s】,所以仍然可以添加: @Test public void testCreateIndex() throws Exception { // URL String baseUrl = "http://localhost:8081/solr/collection1"; // 创建HttpSolrServer HttpSolrServer solrServer = new HttpSolrServer(baseUrl); // 创建document对象 SolrInputDocument document = new SolrInputDocument(); document.addField("id", "zjl001"); document.addField("zhoujielun_s", "zhoujielun001"); solrServer.add(document); solrServer.commit(); } 2.1.3.uniqueKey <uniqueKey>id</uniqueKey> 声明一个主域,要求所有的Document中都必须包含这里声明的field域,相当于数据库表的主键。声明的域名必须存在。 2.1.4.copyField(复制域) <copyField source="cat1" dest="text"/> <copyField source="cat2" dest="text"/> 可以将多个field域合并到一个field域中,以便进行统一的检索。 例如:cat1、cat2和text是三个普通的field域,在创建索引时,solr会自动将cat1和cat2复制到text域中,那么查询text域就相当于查询cat1域和cat2域了。 source:源域 dest:目标域,搜索时,指定目标域为默认搜索域,可以提高查询效率。 无论是源域还是目标域都得是已经存在的,并且目标域的multiValued必须是true。 定义目标域: <field name="text" type="text_general" indexed="true" stored="false" multiValued="true"/> 2.1.5.fieldType(域类型) solr可以根据需要自己配置域的定义,域的定义包括:一个Solr底层的Field域类型和根据需要选定的分析器。 solr这样设计的好处是能获得更好的扩展性,同一种solr的Field域类型可以根据不同的分析器扩展出多个自定义域类型来,使用起来更加灵活。 <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> name:域类型的别名 class:指定solr的域类型。 analyzer:指定分词器。在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词器和过滤器。 还可以直接指定一个分析器的class类,这样就使用这个分析器中自带的分词器和过滤器,而不需要再在这里配置分词器和过滤器了。这样的配置更加简化。 type:值为index和query。index 是创建索引,query是查询索引。 tokenizer:指定分词器 filter:指定过滤器 2.2.配置中文分析器 SolrCore中配置fieldType的Solr底层域类型都是来自solr服务中的solr jar包的,如果想要引入IK中文分词器需要在solr服务的lib中加入IK的jar包。 第一步:添加IKAnalyzer2012FF_u1.jar到solr/WEB-INF/lib目录下。 第二步:配置IK分析器 solr/WEB-INF/下创建classes文件夹,添加IK的配置文件、自定义扩展词库、停用词词库。 第三步:配置日志输出 第三步:在schema.xml中添加一个自定义的fieldType,使用中文分析器。 <!-- fileType with IKAnalyzer --> <fieldType name="text_ik" class="solr.TextField"> <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer" /> </fieldType> 第四步:在schema.xml中添加field,指定field的type属性为text_ik <!-- IKAnalyzer Field --> <field name="content_ik" type="text_ik" indexed="true" stored="true" /> 第五步:重启tomcat 效果: 3.业务域的配置和批量索引(重点) 3.1.业务需求 一个web系统都会有一个站内搜索应用,要开发站内搜索第一个问题就是如何将数据库的数据批量导入索引库?数据导入索引库的确认事项: 1. 需要需要的表字段和检索数据 2. 根据表字段在schema.xml中配置业务域 3. 在schema.xml中配置一个导入数据的requestHandler 3.2.数据库表字段和数据 3.2.1.导入数据SQL脚本 在数据库中运行solr.sql脚本 导入数据:总共3803条数据 3.2.2.查询SQL SELECT pid,name,catalog_name,price,description,picture FROM products 3.3.配置业务Field域 先确定定义的商品document的Field域有哪些? 可以根据我们要查询的有哪些商品表的字段来确定: products商品表: 在SolrCore的schema.xml中配置业务域,就根据我们检索的字段来创建: <!-- products Field --> <field name="product_name" type="text_ik" indexed="true" stored="true" /> <field name="product_catalog_name" type="string" 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" /> <!-- products copyField --> <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"/> 注意:这里没有创建id的field,因为在schema.xml中默认自带id的field,而且必须包含这个id域,所以这里不用建。 3.4.配置数据导入requestHandler 在昨天也介绍solr的可视化管理工具中有一个Dataimport功能,就是用于批量导入数据创建索引的: 在上一节我们已经事先配置了业务域,下面需要利用批量导入插件将mysql中的products表中的数据批量创建索引: 3.4.1.第一步:导入插件依赖jar 先在一个SolrCore实例(collection1)下创建一个lib文件夹,然后把dataimport插件依赖的jar包添加到lib中, 同时还需要mysql的数据库驱动。 3.4.2.第二步:配置solrconfig.xml 配置solrconfig.xml文件,添加一个requestHandler。 <!-- A request handler that dataimport --> <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,与solrconfig.xml保存到同目录下 <?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="123"/> <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> 3.4.3.第三步:重启tomcat 重启tomcat,然后刷新页面,点击Dataimport就可以看到这个导入界面了: 点击“execute”按钮导入数据。 界面重要选项说明: ·Command: full-import表示全部导入 delta-import表示差分导入(把缺少的部分导入进来) 一般我们为了导入的数据完整,都采用full-import。 ·Clean:(默认选中)表示导入数据前会自动清空之前的索引文件 ·Commit:(默认选中)表示创建的索引会自动提交 ·Auto-Refresh Status:(默认不选中)它是控制到导入数据过程中界面是否自动刷新,默认不自动,要想自动选中即可。 导入成功界面: 3.5.测试导入的索引 3.5.1.solr的查询语法 1.q:查询关键字,必须的。 请求的q是字符串,如果查询所有使用*:* 2.fq: (filter query)过滤查询 作用:在q查询符合结果中同时是fq查询符合的 请求fq是一个数组(多个值) 过滤查询价格从1到20的记录。 也可以使用“*”表示无限,例如: 20以上:product_price:[20 TO *] 20以下:product_price:[* TO 20] 也可以在“q”查询条件中使用product_price:[1 TO 20], 如下效果和上面一样: 3.sort: 排序,desc代表降序,asc代表升序 按照价格升序排 4.start: 分页显示使用,开始记录下标,从0开始 rows: 指定返回结果最多有多少条记录,配合start来实现分页。 5.fl: (Field List)指定返回那些字段内容,用逗号或空格分隔多个。 显示商品id、商品名称、商品分类名称 6.df: 指定默认搜索Field 7.wt: (writer type)指定输出格式,可以有 xml, json, php, python等 8.hl: 是否高亮 ,设置高亮Field,设置格式前缀和后缀。 3.5.2.solrj的复杂查询 页面的查询条件,复杂查询条件和页面的查询条件一致 上面的查询条件也可以用代码实现: /** * solrj复杂查询 * * @throws Exception */ @Test public void complexSearchIndexTest() throws Exception { // 查询条件设置 SolrQuery solrQuery = setQueryConditions(); // 执行查询 QueryResponse response = solrServer.query(solrQuery); // 处理查询查询结果 dealSearchResult(response); } // 查询条件设置 private SolrQuery setQueryConditions() throws Exception { SolrQuery query = new SolrQuery(); // 设置查询条件 query.setQuery("台灯"); // 设置过滤条件 query.setFilterQueries("product_catalog_name:雅致灯饰", "product_price:[30 TO 40]"); // 或者分开设置 /*query.setFilterQueries("product_catalog_name:雅致灯饰"); query.setFilterQueries("product_price:[30 TO 40]");*/ // 设置排序条件 query.setSort("product_price", ORDER.desc); // 设置分页信息(开始行号和每页数据量) query.setStart(0); query.setRows(10); // 设置需要显示的域名(可选,不设置时全部显示) //query.setFields("id", "product_name", "product_price"); // 设置默认搜索域 query.set("df", "product_name"); // 设置高亮显示(是否开启高亮显示,高亮显示的域名,高亮的前缀和后缀) query.setHighlight(true); query.addHighlightField("product_name"); query.setHighlightSimplePre("<font color="red">"); query.setHighlightSimplePost("</font>"); return query; } // 处理查询结果 private void dealSearchResult(QueryResponse response) throws Exception { // 取得商品信息结果集 SolrDocumentList results = response.getResults(); // 取得高亮信息结果集 Map<String, Map<String, List<String>>> hlResults = response.getHighlighting(); // 打印结果件数 System.out.println("查询结果总件数:" + results.getNumFound()); // 遍历结果集打印结果 for (SolrDocument doc : results) { System.out.println("-=-=-=-=-=-=-=-=-="); // 处理高亮显示的内容 String hlValue = dealHighlighting(hlResults, doc, "product_name"); // 输出结果 System.out.println("id:" + doc.get("id")); System.out.println("product_name:" + hlValue); System.out.println("product_price:" + doc.get("product_price")); System.out.println("product_catalog_name:" + doc.get("product_catalog_name")); System.out.println("product_picture:" + doc.get("product_picture")); } } // 处理高亮显示内容 private String dealHighlighting(Map<String, Map<String, List<String>>> hlResults, SolrDocument doc, String hlField) throws Exception { // 先取得原版的域值 String orgFieldValue = (String)doc.get(hlField); // 判断高亮结果集中是否包含当前文档的高亮信息 if (hlResults != null) { List<String> list = hlResults.get(doc.get("id")).get(hlField); if (list != null && list.size() > 0) { return list.get(0); } } return orgFieldValue; } 4.案例(重点) 4.1.需求 使用Solr实现电商网站中商品信息搜索功能,可以根据关键字搜索商品信息,根据商品分类、价格过滤搜索结果,也可以根据价格进行排序,实现分页。 界面如下: 4.1.1.架构分析 架构分为: 1、solr服务器 2、自己的web服务器(需要开发) 3、数据库mysql 自己开发的应用 1、Controller 获取搜索条件,调用查询站内搜索service进行查询,并响应搜索结果到前台页面。 2、Service Service调用dao, 用前台传入的查询条件创建SolrQuery对象,然后传给DAO,进行搜索。 Service调用dao进行商品数据的维护时,要同步更新索引库(本案例不实现) 3、Dao 根据service传入的SolrQuery对象,对solr的索引库进行搜索,并返回查询结果。 对商品数据进行维护和查询(本案例不实现) 4.2.环境准备 Solr:4.10.3 Jdk环境:1.7 IDE环境:eclipse Mars2 服务器:Tomcat 7 4.3.工程搭建(UTF-8) 创建一个web工程导入jar包 1、spring的相关jar包 2、solrJ的jar包和依赖jar包 3、Solr服务的日志依赖包,solrexamplelibext下的jar包 创建后完整的工程: 1. 导入jar包后,先创建目录:dao、pojo、service、controller、config、jsp 2. 导入配置文件: 1) 表现层SpringMVC.xml:@controller注解扫描,注解驱动,视图解析器 2) 业务层ApplicationContext.xml:@Service、@Repository注解扫描、solrj连接Solr服务的bean (注意:这里面没有集成mybatis,所以我们用注解@Repository来注入,这样DAO需要有实现类我们自己编写。) 3) web.xml:SpringMVC的前端控制器和Spring监听 4) log4j.properties 3. 导入jsp页面:【资料jd案例product_list.jsp】 4. 导入静态资源:【资料jd案例】下的【images】【resource】拷贝到工程【WebContent】下。 5. 导入pojo:【资料jd案例pojo】下的【ProductModel.java】【ResultModel.java】 6. ApplicationContext.xml中配置SolrServer: <!-- 配置solr server bean --> <bean id="solrServer" class="org.apache.solr.client.solrj.impl.HttpSolrServer"> <!-- 配置Solr Server实例时需要的构造函数的参数 --> <constructor-arg value="http://127.0.0.1:8081/solr/collection1" /> </bean> 4.4.Dao 功能:接收service层传递过来的参数,根据参数查询索引库,返回查询结果。 参数:SolrQuery对象 返回值:一个商品列表List<ProductModel>,还需要返回查询结果的总数量。 返回:ResultModel 方法定义:ResultModel searchProductList(SolrQuery query) throws Exception; 商品对象模型: 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; } package cn.baidu.dao; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import cn.baidu.pojo.ProductModel; import cn.baidu.pojo.ResultModel; @Repository public class ProductDaoImpl implements ProductDao { @Autowired private SolrServer solrServer; @Override public ResultModel searchProductList(SolrQuery query) throws Exception { // 执行查询,并返回结果 QueryResponse queryResponse = solrServer.query(query); // 从结果对象中取得结果集 SolrDocumentList results = queryResponse.getResults(); // 创建返回结果对象 ResultModel resultModel = new ResultModel(); // 创建结果集list List<ProductModel> productList = new ArrayList<ProductModel>(); // 查询件数 resultModel.setRecordCount(results.getNumFound()); // 遍历结果集打印详细内容 for (SolrDocument doc : results) { // 创建一个ProductModel查询结果 ProductModel productModel = new ProductModel(); // 取得高亮显示的信息,然后根据高亮显示的信息进行结果信息打印 String productName = (String)doc.get("product_name"); Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting(); if (highlighting != null) { List<String> list = highlighting.get(doc.get("id")).get("product_name"); if (list != null && list.size() > 0) { productName = list.get(0); } } // 商品id productModel.setPid(String.valueOf(doc.get("id"))); // 商品名称 productModel.setName(productName); // 商品价格 String price = String.valueOf(doc.get("product_price")); if (price != null && !"".equals(price)) { productModel.setPrice(Float.parseFloat(price)); } // 商品图片 productModel.setPicture(String.valueOf(doc.get("product_picture"))); // 添加到商品列表中 productList.add(productModel); } if (productList.size() > 0) { resultModel.setProductList(productList); } return resultModel; } } 4.5.Service 功能:接收action传递过来的参数,根据参数拼装一个查询条件,调用dao层方法,查询商品列表。接收返回的商品列表和商品的总数量,根据每页显示的商品数量计算总页数。 参数: 1、查询条件:字符串 2、商品分类的过滤条件:商品的分类名称,字符串 3、商品价格区间:传递一个字符串,满足格式:“0-100、101-200、201-*” 4、排序条件:页面传递过来一个升序或者降序就可以,默认是价格排序。0:升序1:降序 5、分页信息:每页显示的记录条数创建一个常量60条。传递一个当前页码就可以了。 返回值:ResultModel 方法定义:ResultModel queryProduct(String queryString, String caltalog_name, String price, String sort, Integer page) throws Exception; package cn.baidu.service; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrQuery.ORDER; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.baidu.dao.ProductDao; import cn.baidu.pojo.ResultModel; @Service public class ProductServiceImpl implements ProductService { // 每页显示60条数据 private static final Integer PAGE_SIZE = 60; @Autowired private ProductDao prodDao; @Override public ResultModel search(String queryString, String catalog_name, String price, Integer page, String sort) 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); } // else { // // 注意这里要说一下为什么没有else,因为过滤条件不是必须的所以可以没有. // } // 价格过滤 if (price != null && !"".equals(price)) { String[] split = price.split("-"); if (split != null && split.length > 0) { solrQuery.addFilterQuery("product_price:[" + split[0] + " TO " + split[1] + "]"); } } // 排序 if ("1".equals(sort)) { solrQuery.setSort("product_price", ORDER.asc); } else { solrQuery.setSort("product_price", ORDER.desc); } // 分页 if (page == null || page <= 0) { page = 1; } // 从第几条开始 solrQuery.setStart((page - 1) * PAGE_SIZE); // 每页显示多少条 solrQuery.setRows(PAGE_SIZE); // 设置高亮 solrQuery.setHighlight(true); // 设置高亮显示的域 solrQuery.addHighlightField("product_name"); // 设置高亮前缀 solrQuery.setHighlightSimplePre("<span style="color:red">"); // 设置高亮后缀 solrQuery.setHighlightSimplePost("</span>"); // 根据查询条件取得结果 ResultModel resultModel = prodDao.search(solrQuery); // 设置当前页 resultModel.setCurPage(page); // 计算总页数 Long pageCount = resultModel.getRecordCount() / PAGE_SIZE; if (resultModel.getRecordCount() % PAGE_SIZE > 0) { pageCount++; } resultModel.setPageCount(pageCount); return resultModel; } } 4.1.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; package cn.baidu.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import cn.baidu.pojo.ResultModel; import cn.baidu.service.ProductService; @Controller public class ProductController { @Autowired private ProductService prodService; @RequestMapping("/list") public String list(String queryString, String catalog_name, String price, Integer page, String sort, Model model) throws Exception { ResultModel resultModel = prodService.search(queryString, catalog_name, price, page, sort); // 搜索结果返回 model.addAttribute("result", resultModel); // 回显查询条件 model.addAttribute("queryString", queryString); model.addAttribute("catalog_name", catalog_name); model.addAttribute("price", price); model.addAttribute("sort", sort); return "product_list"; } }