Lucene&Solr

全文检索

应用-->站内搜索

lucene是基础,solr是框架

全文检索的过程:①创建索引,②对索引进行搜索

要针对其进行检索的源文件

创建完的索引文件

检索的效果

图示

索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:

确定原始内容即要搜索的内容-->采集文档-->创建文档-->分析文档-->索引文档

搜索过程,从索引库中搜索内容,搜索过程包括:

用户通过搜索界面-->创建查询-->执行搜索,从索引库搜索-->渲染搜索结果

import static org.junit.Assert.*;
import java.io.File;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StoredField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

/**
 * Lucene 入门 创建索引 查询索引
 * 
 * @author lx
 *
 */
public class FirstLucene {

    // 创建索引
    @Test
    public void testIndex() throws Exception {
        // 第一步:创建一个java工程,并导入jar包。
        // 第二步:创建一个indexwriter对象。
        Directory directory = FSDirectory.open(new File("D:\temp\index"));
        // Directory directory = new RAMDirectory();//保存索引到内存中 (内存索引库)
//        Analyzer analyzer = new StandardAnalyzer();// 官方推荐
        Analyzer analyzer = new IKAnalyzer();// 官方推荐
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
        IndexWriter indexWriter = new IndexWriter(directory, config);
        // 1)指定索引库的存放位置Directory对象
        // 2)指定一个分析器,对文档内容进行分析。
        // 第三步:创建field对象,将field添加到document对象中。
        File f = new File("F:\北大青鸟培训\框架学习\Lucene&solr\01.参考资料\searchsource");
        File[] listFiles = f.listFiles();
        for (File file : listFiles) {
            // 第三步:创建document对象。
            Document document = new Document();
            // 文件名称
            String file_name = file.getName();
            Field fileNameField = new TextField("fileName", file_name, Store.YES);
            // 文件大小
            long file_size = FileUtils.sizeOf(file);
            Field fileSizeField = new LongField("fileSize", file_size, Store.YES);
            // 文件路径
            String file_path = file.getPath();
            Field filePathField = new StoredField("filePath", file_path);
            // 文件内容
            String file_content = FileUtils.readFileToString(file);
            Field fileContentField = new TextField("fileContent", file_content, Store.NO);

            document.add(fileNameField);
            document.add(fileSizeField);
            document.add(filePathField);
            document.add(fileContentField);
            // 第四步:使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。
            indexWriter.addDocument(document);
        }
        // 第五步:关闭IndexWriter对象。
        indexWriter.close();
    }

    // 搜索索引
    @Test
    public void testSearch() throws Exception {
        // 第一步:创建一个Directory对象,也就是索引库存放的位置。
        Directory directory = FSDirectory.open(new File("D:\temp\index"));// 磁盘
        // 第二步:创建一个indexReader对象,需要指定Directory对象。
        IndexReader indexReader = DirectoryReader.open(directory);
        // 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // 第四步:创建一个TermQuery对象,指定查询的域和查询的关键词。
        Query query = new TermQuery(new Term("fileName", "lucene"));
        // 第五步:执行查询。
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 第六步:返回查询结果。遍历查询结果并输出。
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            int doc = scoreDoc.doc;
            Document document = indexSearcher.doc(doc);
            // 文件名称
            String fileName = document.get("fileName");
            System.out.println(fileName);
            // 文件内容
            String fileContent = document.get("fileContent");
            System.out.println(fileContent);
            // 文件大小
            String fileSize = document.get("fileSize");
            System.out.println(fileSize);
            // 文件路径
            String filePath = document.get("filePath");
            System.out.println(filePath);
            System.out.println("------------");
        }
        // 第七步:关闭IndexReader对象
        indexReader.close();

    }

    // 查看标准分析器的分词效果
    @Test
    public void testTokenStream() throws Exception {
        // 创建一个标准分析器对象
//        Analyzer analyzer = new StandardAnalyzer();
//        Analyzer analyzer = new CJKAnalyzer();
//        Analyzer analyzer = new SmartChineseAnalyzer();
        Analyzer analyzer = new IKAnalyzer();
        // 获得tokenStream对象
        // 第一个参数:域名,可以随便给一个
        // 第二个参数:要分析的文本内容
//        TokenStream tokenStream = analyzer.tokenStream("test",
//                "The Spring Framework provides a comprehensive programming and configuration model.");
        TokenStream tokenStream = analyzer.tokenStream("test",
                "高富帅可以用二维表结构来逻辑表达实现的数据");
        // 添加一个引用,可以获得每个关键词
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        // 添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        // 将指针调整到列表的头部
        tokenStream.reset();
        // 遍历关键词列表,通过incrementToken方法判断列表是否结束
        while (tokenStream.incrementToken()) {
            // 关键词的起始位置
            System.out.println("start->" + offsetAttribute.startOffset());
            // 取关键词
            System.out.println(charTermAttribute);
            // 结束位置
            System.out.println("end->" + offsetAttribute.endOffset());
        }
        tokenStream.close();
    }

}
代码示例

索引维护

import static org.junit.Assert.*;
import java.io.File;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.NumericRangeQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;

/**
 * 索引维护
 * 添加  入门程序
 * 删除
 * 修改
 * 查询  入门程序 精准查询 
 * @author lx
 *
 */
public class LuceneManager {

    //
    public IndexWriter getIndexWriter() throws Exception{
        // 第一步:创建一个java工程,并导入jar包。
        // 第二步:创建一个indexwriter对象。
        Directory directory = FSDirectory.open(new File("D:\temp\index"));
        // Directory directory = new RAMDirectory();//保存索引到内存中 (内存索引库)
        Analyzer analyzer = new StandardAnalyzer();// 官方推荐
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
        return new IndexWriter(directory, config);
    }
    //全删除
    @Test
    public void testAllDelete() throws Exception {
        IndexWriter indexWriter = getIndexWriter();
        indexWriter.deleteAll();
        indexWriter.close();
    }
    //根据条件删除
    @Test
    public void testDelete() throws Exception {
        IndexWriter indexWriter = getIndexWriter();
        Query query = new TermQuery(new Term("fileName","apache"));
        indexWriter.deleteDocuments(query);
        indexWriter.close();
    }
    //修改
    @Test
    public void testUpdate() throws Exception {
        IndexWriter indexWriter = getIndexWriter();
        Document doc = new Document();
        doc.add(new TextField("fileN", "测试文件名",Store.YES));
        doc.add(new TextField("fileC", "测试文件内容",Store.YES));
        indexWriter.updateDocument(new Term("fileName","lucene"), doc, new IKAnalyzer());
        indexWriter.close();
    }
    //IndexReader  IndexSearcher
    public IndexSearcher getIndexSearcher() throws Exception{
        // 第一步:创建一个Directory对象,也就是索引库存放的位置。
        Directory directory = FSDirectory.open(new File("D:\temp\index"));// 磁盘
        // 第二步:创建一个indexReader对象,需要指定Directory对象。
        IndexReader indexReader = DirectoryReader.open(directory);
        // 第三步:创建一个indexsearcher对象,需要指定IndexReader对象
        return new IndexSearcher(indexReader);
    }
    //执行查询的结果
    public void printResult(IndexSearcher indexSearcher,Query query)throws Exception{
        // 第五步:执行查询。
        TopDocs topDocs = indexSearcher.search(query, 10);
        // 第六步:返回查询结果。遍历查询结果并输出。
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            int doc = scoreDoc.doc;
            Document document = indexSearcher.doc(doc);
            // 文件名称
            String fileName = document.get("fileName");
            System.out.println(fileName);
            // 文件内容
            String fileContent = document.get("fileContent");
            System.out.println(fileContent);
            // 文件大小
            String fileSize = document.get("fileSize");
            System.out.println(fileSize);
            // 文件路径
            String filePath = document.get("filePath");
            System.out.println(filePath);
            System.out.println("------------");
        }
    }
    //查询所有
    @Test
    public void testMatchAllDocsQuery() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();
        Query query = new MatchAllDocsQuery();
        System.out.println(query);
        printResult(indexSearcher, query);
        //关闭资源
        indexSearcher.getIndexReader().close();
    }
    //根据数值范围查询
    @Test
    public void testNumericRangeQuery() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();
        
        Query query = NumericRangeQuery.newLongRange("fileSize", 47L, 200L, false, true);
        System.out.println(query);
        printResult(indexSearcher, query);
        //关闭资源
        indexSearcher.getIndexReader().close();
    }
    //可以组合查询条件
    @Test
    public void testBooleanQuery() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();
        
        BooleanQuery booleanQuery = new BooleanQuery();
        
        Query query1 = new TermQuery(new Term("fileName","apache"));
        Query query2 = new TermQuery(new Term("fileName","lucene"));
        //  select * from user where id =1 or name = 'safdsa'
        booleanQuery.add(query1, Occur.MUST);
        booleanQuery.add(query2, Occur.SHOULD);
        System.out.println(booleanQuery);
        printResult(indexSearcher, booleanQuery);
        //关闭资源
        indexSearcher.getIndexReader().close();
    }
    //条件解释的对象查询
    @Test
    public void testQueryParser() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();
        //参数1: 默认查询的域  
        //参数2:采用的分析器
        QueryParser queryParser = new QueryParser("fileName",new IKAnalyzer());
        // *:*   域:值
        Query query = queryParser.parse("fileName:lucene is apache OR fileContent:lucene is apache");
        
        printResult(indexSearcher, query);
        //关闭资源
        indexSearcher.getIndexReader().close();
    }
    //条件解析的对象查询   多个默念域
    @Test
    public void testMultiFieldQueryParser() throws Exception {
        IndexSearcher indexSearcher = getIndexSearcher();
        
        String[] fields = {"fileName","fileContent"};
        //参数1: 默认查询的域  
        //参数2:采用的分析器
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields,new IKAnalyzer());
        // *:*   域:值
        Query query = queryParser.parse("lucene is apache");
        
        printResult(indexSearcher, query);
        //关闭资源
        indexSearcher.getIndexReader().close();
    }
    
    
}
索引维护示例

单独使用Lucene实现站内搜索需要开发的工作量较大,主要表现在:索引维护、索引性能优化、搜索性能优化等,因此不建议采用。

基于Solr实现站内搜索扩展性较好,并且可以减少程序员的工作量,因为Solr提供了较为完备的搜索引擎解决方案,因此在门户、论坛等系统中常用此方案。

搭建solr

步骤一:创建solr文件夹,放入tomcat和solr

bin:solr的运行脚本
contrib:solr的一些贡献软件/插件,用于增强solr的功能。
dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。
docs:solr的API文档
example:solr工程的例子目录:
	example/solr:
	该目录是一个包含了默认配置信息的Solr的Core目录。
	example/multicore:
	该目录包含了在Solr的multicore中设置的多个Core目录。 
	example/webapps:
    该目录中包括一个solr.war,该war可作为solr的运行实例工程。
licenses:solr相关的一些许可信息

步骤二:拷贝,粘贴

(上面不行的话换solr-4.10.3distsolr-4.10.3.war)

改名为solr.war

解压,删掉war包

找到solr-4.10.3examplelibext下的jar包

放到D:solrapache-tomcat-7.0.92webappssolrWEB-INFlib下

步骤三

新建solrhome(放索引库)

粘贴到solrhome下

接下来,修改配置

<env-entry>
   <env-entry-name>solr/home</env-entry-name>
   <env-entry-value>D:solrsolrhome</env-entry-value>
   <env-entry-type>java.lang.String</env-entry-type>
</env-entry>

启动上文的tomcat

浏览器访问http://localhost:8080/solr

失败!

换了一顿官网的纯净版tomcat,重新解压了新的solr进行配置,还是不行!

浪费一个多小时,改了tomcat环境变量,成功……


安装中文分词器

①将IKAnalyzer2012FF_u1.jar拷到tomcat下的solr项目中

②在solr项目的WEB-INF文件夹中新建classes文件夹,拷入ext.dic,IKAnalyzer.cfg.xml,stopword.dic

③solr实例的conf文件夹中(D:solrsolrhomecollection1conf),修改schema.xml文件(新增)

<!-- IKAnalyzer-->
<fieldType name="text_ik" class="solr.TextField">
  <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>
</fieldType>
<!--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

左下角就是对域/数据的维护


导数据

拷入jar包

solr-dataimporthandler-4.10.3.jar

solr-dataimporthandler-extras-4.10.3.jar

mysql-connector-java-5.1.7-bin.jar

拷贝到D:solrsolrhomecollection1lib

修改配置D:solrsolrhomecollection1confsolrconfig.xml

<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/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>

创建lucene数据库,导入数据

下一步,将这些数据导入到索引库当中

修改D:solrsolrhomecollection1confschema.xml

<!--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"/>

重启服务

筛选


使用SolrJ管理索引

代码

//向索引库中添加索引
@Test
public void addDocument() throws Exception {
    //和solr服务器创建连接
    //参数:solr服务器的地址
    SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
    //创建一个文档对象
    SolrInputDocument document = new SolrInputDocument();
    //向文档中添加域
    //第一个参数:域的名称,域的名称必须是在schema.xml中定义的
    //第二个参数:域的值
    document.addField("id", "c0001");
    document.addField("title_ik", "使用solrJ添加的文档");
    document.addField("content_ik", "文档的内容");
    document.addField("product_name", "商品名称");
    //把document对象添加到索引库中
    solrServer.add(document);
    //提交修改
    solrServer.commit();
    
}

根据id删除

//删除文档,根据id删除
@Test
public void deleteDocumentByid() throws Exception {
    //创建连接
    SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
    //根据id删除文档
    solrServer.deleteById("c0001");
    //提交修改
    solrServer.commit();
}

根据查询删除

//根据查询条件删除文档
@Test
public void deleteDocumentByQuery() throws Exception {
    //创建连接
    SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
    //根据查询条件删除文档
    solrServer.deleteByQuery("*:*");
    //提交修改
    solrServer.commit();
}

查询

//查询索引
@Test
public void queryIndex() throws Exception {
    //创建连接
    SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
    //创建一个query对象
    SolrQuery query = new SolrQuery();
    //设置查询条件
    query.setQuery("*:*");
    //执行查询
    QueryResponse queryResponse = solrServer.query(query);
    //取查询结果
    SolrDocumentList solrDocumentList = queryResponse.getResults();
    //共查询到商品数量
    System.out.println("共查询到商品数量:" + solrDocumentList.getNumFound());
    //遍历查询的结果
    for (SolrDocument solrDocument : solrDocumentList) {
        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_catalog_name"));
        System.out.println(solrDocument.get("product_picture"));
        
    }
}

复杂查询

//复杂查询索引
@Test
public void queryIndex2() throws Exception {
    //创建连接
    SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr");
    //创建一个query对象
    SolrQuery query = new SolrQuery();
    //设置查询条件
    query.setQuery("钻石");
    //过滤条件
    query.setFilterQueries("product_catalog_name:幽默杂货");
    //排序条件
    query.setSort("product_price", ORDER.asc);
    //分页处理
    query.setStart(0);
    query.setRows(10);
    //结果中域的列表
    query.setFields("id","product_name","product_price","product_catalog_name","product_picture");
    //设置默认搜索域
    query.set("df", "product_keywords");
    //高亮显示
    query.setHighlight(true);
    //高亮显示的域
    query.addHighlightField("product_name");
    //高亮显示的前缀
    query.setHighlightSimplePre("<em>");
    //高亮显示的后缀
    query.setHighlightSimplePost("</em>");
    //执行查询
    QueryResponse queryResponse = solrServer.query(query);
    //取查询结果
    SolrDocumentList solrDocumentList = queryResponse.getResults();
    //共查询到商品数量
    System.out.println("共查询到商品数量:" + solrDocumentList.getNumFound());
    //遍历查询的结果
    for (SolrDocument solrDocument : solrDocumentList) {
        System.out.println(solrDocument.get("id"));
        //取高亮显示
        String productName = "";
        Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
        List<String> list = highlighting.get(solrDocument.get("id")).get("product_name");
        //判断是否有高亮内容
        if (null != list) {
            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_catalog_name"));
        System.out.println(solrDocument.get("product_picture"));
        
    }
}

案例实现


击石乃有火,不击元无烟!!
原文地址:https://www.cnblogs.com/rain2020/p/12950390.html