Lucene连接数据库进行全文搜索初探(一)

Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。 已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎,例如:Web论坛Jive,邮件列表HTML归档/浏览/查询系统Eyebrows,包括我们熟悉的Eclipse的全文帮助搜索功能。在实际开发中,因为数据库不是专为全文搜索设计的,所以对于全文搜索,特别是模糊查询类的全文搜索,用Lucene就比数据库的效率有优势的多。对于变化很少,但查询访问量大的数据,将数据库的数据以document形式存在本地,访问的时候不经过数据库,可以减少数据库压力。对于第一个Lucene程序,编写主要分以下几个步骤:

1.初始化Lucene的检索工具IndexSearcher

IndexSearcher是Lucene中最基本的检索工具,所有的检索都会用到IndexSearcher检索工具,但是在使用IndexSearcher之前,还要做一些准备工作,即对检索工具IndexSearcher进行初始化。

初始化IndexSearcher,需要设置索引存放的路径,这样才能让查询器定位索引,用于后面进行搜索。如以下为一个初始化IndexSearcher的过程:(4种方式)

public IndexSearcher(String path) throws IOException {
this(IndexReader.open(path), true);
}

public IndexSearcher(Directory directory) throws IOException {
this(IndexReader.open(directory), true);
}

public IndexSearcher(IndexReader r) {
this(r, false);
}

private IndexSearcher(IndexReader r, boolean closeReader) {
reader = r;
this.closeReader = closeReader;
}

如:Searcher searcher = new IndexSearcher(indexDir);

返回的结果是IndexSearcher类的一个实例,indexDir表示索引文件的存放路径。

2.构建索引器和索引函数

实例化一个构造器:

IndexWriter writer = new IndexWriter("D:/index/", new PanGuAnalyzer(), true);   //索引的存储位置
这个函数有三个参数,分别是:path——索引文件存放路径,a——分词工具,create——true表示建立索引

3.建立索引

将要建立索引的文件构造成一个Document对象,并添加一个域,如:

Document doc = new Document();
doc.Add(new Field("id", item.id.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));
这里先解释下三个概念:

1)Field:可以理解成索引文件中一个个的字段块,占用空间按字段长度分配。
2)Store:一个内部类,它是static的,主要为了设置Field的存储属性.

主要有以下几种:

public static final Store COMPRESS = new Store("COMPRESS"); // 在索引中压缩存储Field的值
public static final Store YES = new Store("YES");//在索引中存储Field的值
public static final Store NO = new Store("NO"); // 在索引中不存储Field的值

3.)Index: 通过Index设置索引方式,有以下几种:

public static final Index TOKENIZED = new Index("TOKENIZED"); // 对Field进行索引,同时还要对其进行分词(由Analyzer来管理如何分词)
public static final Index UN_TOKENIZED = new Index("UN_TOKENIZED"); // 对Field进行索引,但不对其进行分词
public static final Index NO_NORMS = new Index("NO_NORMS"); // 对Field进行索引,但是不使用Analyzer

4.优化检索,关闭写入

writer.Optimize(); //添加完所有document,我们对索引进行优化,优化主要是将多个索引文件合并到一个,有利于提高索引速度。 

writer.Close();//随后将writer关闭,这点很重要。
5.开始检索

1)首先创建一个容器来存放你从索引文件中读取到的数据,这里我们使用Table

private DataTable dt()
        {
            DataTable mytab = new DataTable();
            mytab.Columns.Add("ID");
            mytab.Columns.Add("TRADENAME");
            mytab.Columns.Add("AREANAME");
            mytab.Columns.Add("COMPANYNAME");
            mytab.Columns.Add("FHDES");
            mytab.Clear();
            return mytab;
        }
2)读取索引文件中的数据

private IndexSearcher LuceneSource()
        {
            string INDEX_STORE_PATH = "D:/index/";  //INDEX_STORE_PATH 为索引存储目录   
            return new IndexSearcher(INDEX_STORE_PATH);
        }
3)得到过滤后数据,即查询条件,你可以理解成SQL里的where条件
lucene的搜索相当强大,它提供了很多辅助查询类,每个类都继承自Query类,各自完成一种特殊的查询,你可以像搭积木一样将它们任意组合使用,完成一些复杂操作;另外lucene还提供了Sort类对结果进行排序,提供了Filter类对查询条件进行限制。你或许会不自觉地拿它跟SQL语句进行比较:“lucene能执行and、or、order by、where、like‘%xx%’操作吗?”回答是:“当然没问题!”

a.  TermQuery
首先介绍最基本的查询,如果你想执行一个这样的查询:“在content域中包含‘lucene’的document”,那么你可以用TermQuery:

<span style="white-space:pre">	</span>Term t = new Term("content", " lucene");Query query = new TermQuery(t);
b.BooleanQuery

如果你想让产品名称或者产品发货说明匹配关键字,那么你可以用:

<span style="white-space:pre">	</span>strkeyword = Common.ProductAbout.GetKeyWordsSplitBySpace(strkeyword, new PanGuTokenizer());
                QueryParser companynameparser = new QueryParser("companyname", new PanGuAnalyzer(true));
                Query companynamequery = companynameparser.Parse(strkeyword);
                QueryParser productnameparser = new QueryParser("productname", new PanGuAnalyzer(true));
                Query productdesquery = productnameparser.Parse(strkeyword);
                bq.Add(productdesquery, BooleanClause.Occur.SHOULD);<span style="font-family: Arial, Helvetica, sans-serif;">//</span><span style="font-family: Arial, Helvetica, sans-serif;">此处的BooleanClause.Occur,此类有2个重要的属性,SHOULD和MUST,</span>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre">				</span>//SHOULD你就理解成SQL里’OR’,MUST理解成SQL里的’AND’,此处表示要同时满足productdesquery和idquery</span>
                bq.Add(companynamequery, BooleanClause.Occur.SHOULD);
c.WildcardQuery

可以使用通配符*,?等进行查询:

<span style="white-space:pre">	</span>Query query = new WildcardQuery(new Term("content", "use*");
d.PrefixQuery

如果你想搜以‘’开头的词语,你可以用PrefixQuery:

<span style="white-space:pre">	</span>PrefixQuery query = new PrefixQuery(new Term("content ", "淘");
e.FuzzyQuery

用于搜索相似的词,使用Levenshtein算法。如搜索"happy"类似的词:

<span style="white-space:pre">	</span>Query query = new FuzzyQuery(new Term("content", "wuzza");
f. RangeQuery

允许搜索时间域从20150302到20150324之间的document

<span style="white-space:pre">	</span>RangeQuery query = new RangeQuery(new Term(“time”, “20150302”), new Term(“time”, “20150324”), true);//true表示含边界

6.构建Query

在使用Query之前,需要首先生成一个Query对象。Lucene既允许直接生成一个Query类型的对象,也允许使用QueryParser类的parse()方法来返回一个Query类型的对象。这两种方法在功能上是完全一样的,只是后者在使用时更方便一些,而前者则更为灵活。在API中的格式:

public static Query parse(String query, String field,Analyzer analyzer)

7.搜索并处理返回结果

在构建完Query对象后,就可以使用前面已经初始化好的IndexSearcher工具来进行检索了。IndexSearcher提供了良好的检索接口,用户只需简单地将Query对象传入,就可以得到一个返回结果。当然,这个过程看似简单,其中也有许多值得思考的问题,如检索结果的排序、过滤等。在Lucene中搜索结果的集合是用Hits类的实例来进行表示的。Hits对象中主要有以下几个经常使用的方法:
• length():返回搜索到结果的总数量。
• doc(int n):返回第n个文档。
• id(int n):返回第n个文档的内部ID号。
• score(n):返回第n个文档的得分。

在开发Web相关应用时,简便的方法是当某个用户检索完毕后,可直接将返回的Hits对象存入该用户的session中,然后根据用户的需要进行相关查询。不过这里读者要注意的一点,由于Hits对象被放入session中,并不适合存入大量文本。因为若是这样,对用户来说,可能导致浏览器的响应速度极慢,对服务器方来说,可能导致服务器的内容被大量Hits所占用,最终造成服务器的崩溃。比较好的一种方式,是将Lucene与数据库相结合,在索引中存入一些关键性的ID字段、路径字段或是简单的文本,而真正的数据提取则从数据库中得到。这样一来既可以发挥Lucene优势,也可以使服务器端的压力减轻。例子:

private Hits LuceneFilteridSource(BooleanQuery bq)
        {

            IndexSearcher mysearch = LuceneSource();
            Sort sort = new Sort(new SortField("ID", SortField.DOC, false));//排序  
            return mysearch.Search(bq, sort);

        }
这里的SortField("ID", SortField.DOC, false)指的是对id这个字段进行倒序。SortField主要有以下属性:

FIELD_DOC:按文档数字排序

FIELD_SCORE:按文档分值排序

MissingValue看了API但是没懂,就不提了。

最后的true表示反序,true为正序。

为了减少一次Hits所返回的数据量,可以用将数据放在多个Document里面,如:

Document doc1 = new Document();
doc1.add(Field.Text("contents", "word1 word"));
doc1.add(Field.Keyword("path", "path\\document1.txt"));

Document doc2 = new Document();
doc2.add(Field.Text("contents", "word2 word"));
doc2.add(Field.Keyword("path", "path\\document2.txt"));

Document doc3 = new Document();
doc3.add(Field.Text("contents", "word3 word"));
doc3.add(Field.Keyword("path", "path\\document3.txt"));
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.addDocument(doc3);

要想进一步改进可以研究下Hits的缓存机制。

注意,LUCENE不支持关键词为空的情况,所以如果你想把索引文件中所有的数据都调用出来,那可以用如下方法

for (int i = 0; i < mysearch.MaxDoc(); i++)
                {
                    Document doc = mysearch.Doc(i);
                    FillingTable(mytab, doc);
                }
下面是我写的一个用Lucene连接MySQL获取数据的入门例子:

环境:MyEclipse+MySQL

导入的包:

数据库表:Student


实现功能:输入名字,返回学生记录的id和地址:

package test;


import java.awt.List;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.sql.*;

import org.apache.lucene.search.Hits;
import org.apache.lucene.search.Query;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.*;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.TermPositionVector; 
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.RAMDirectory;


import jeasy.analysis.MMAnalyzer; 


public class TestLucene {
	
	
	
    private static ResultSet recrd;
    static MMAnalyzer analyzer=new MMAnalyzer();
    /**
     * @param args
     */
    public static void main(String[] args) {
//        MMAnalyzer analyzer=new MMAnalyzer();
        try {
            
            File indexpath= new File("F://index");
            IndexWriter writer=new IndexWriter(indexpath,
                    analyzer,true);
             recrd=getConn();                        //获取数据库的记录集
//            while (recrd.next()) {
//                System.out.println(recrd.getString(2));
//                
//           }

            IndexBuilder(writer);                     //建立索引
            BufferedReader bReader= new  BufferedReader(new InputStreamReader(System.in)); 
            String query=bReader.readLine().toString(); 
            System.out.println(query);
            Hits hits=    search(query);                   //输入查询内容后,查询
           
            for(int i=0;i<hits.length();i++){           //返回查询后结果
                Document document=hits.doc(i);
                System.out.println(document.get("user_id"));
                System.out.println(document.get("username"));
                System.out.println(document.get("address"));
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
        

    }
    
    public static Hits search(String quString){                   //搜索用户输入的字符
        Hits hits=null;
        try {
            IndexSearcher iSearcher = new IndexSearcher(IndexReader
					.open("F://index"));
            QueryParser parser=new QueryParser("username",analyzer);
            Query query=parser.parse(quString);
            System.out.println(query.toString());
            
            hits=iSearcher.search(query);
            return hits;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
        
        
    } 
    
    public static void IndexBuilder(IndexWriter fWriter) throws Exception{             //建立索引
        while(recrd.next()){
            
        Directory ramDirectory=new RAMDirectory();
        IndexWriter ramWriter=new IndexWriter(ramDirectory,
                new StandardAnalyzer(),true);
        Document document=new Document();
        Field id= new Field("user_id",recrd.getString("user_id"),Field.Store.YES,
                Field.Index.TOKENIZED);
        Field name=new Field("username",recrd.getString("username"),Field.Store.YES,
                Field.Index.TOKENIZED);
        Field age=new Field("address",recrd.getString("address"),Field.Store.YES,
        		Field.Index.TOKENIZED
);
        
    
        document.add(id);
        document.add(name);
        document.add(age);
        
        ramWriter.addDocument(document);
        ramWriter.close();
        fWriter.addIndexes(new Directory[]{ramDirectory});
        
        }
    }

    public static ResultSet getConn() {                                       //建立数据库连接,并返回结果
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url="jdbc:mysql://localhost:3306/ss";
            
            Connection conn = DriverManager.getConnection(url, "root", "123");
            Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
            String sql = "select * from Student";
            ResultSet rs = stmt.executeQuery(sql);
            return rs;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

}
结果:

再次声明下,好像Lucene的版本更新变化很大,现在最新是Lucene5,据说已经不支持Lucene3以前的版本,Fields的Store和Index属性貌似被废除了,我在Lucene4的Api里面见到还有,但是用高版本的Lucene好像记得有报错,说没有这个方法。还有就是本来是连接SQLServer的,但是没连成,估计是编码问题。因为之前用Lucene来为本地文件建立检索和搜索时,文件源用记事本新建(编码默认为ANSI),就搜索不到结果,要另存为UTF-8才行,而从SQLServer搜索出来的query是没问题的。但是好像SQLServer不能改编码为UTF-8,只能GBK最多。这点有待探究。如果有幸你能看到我的博文,并有什么发现,欢迎大家交流赐教。微笑

本文参考了一些百度上的资料,但是大部分系自己实践探索成果。转载请注明:http://blog.csdn.net/wws199304/


展开阅读全文

没有更多推荐了,返回首页