全文分两部分:
一:Lucene简介
Lucene版本:3.0.2
全文检索大体分两个部分:索引创建(Indexing)和搜索索引(Search)
1. 索引过程:
1) 有一系列被索引文件(此处所指即数据库数据)
2) 被索引文件经过语法分析和语言处理形成一系列词(Term)。
3) 经过索引创建形成词典和反向索引表。
4) 通过索引存储将索引写入硬盘。
2. 搜索过程:
a) 用户输入查询语句。
b) 对查询语句经过语法分析和语言分析得到一系列词(Term)。
c) 通过语法分析得到一个查询树。
d) 通过索引存储将索引读入到内存。
e) 利用查询树搜索索引,从而得到每个词(Term)的文档链表,对文档链表进行交,差,并得到结果文档。
f) 将搜索到的结果文档对查询的相关性进行排序。
g) 返回查询结果给用户。
• 索引过程如下:
◦ 创建一个IndexWriter用来写索引文件,它有几个参数,INDEX_DIR就是索引文件所存放的位置,Analyzer便是用来对文档进行词法分析和语言处理的。
◦ 创建一个Document代表我们要索引的文档。
◦ 将不同的Field加入到文档中。我们知道,一篇文档有多种信息,如题目,作者,修改时间,内容等。不同类型的信息用不同的Field来表示,在本例子中,一共有两类信息进行了索引,一个是文件路径,一个是文件内容。其中FileReader的SRC_FILE就表示要索引的源文件。
◦ IndexWriter调用函数addDocument将索引写到索引文件夹中。
• 搜索过程如下:
◦ IndexReader将磁盘上的索引信息读入到内存,INDEX_DIR就是索引文件存放的位置。
◦ 创建IndexSearcher准备进行搜索。
◦ 创建Analyer用来对查询语句进行词法分析和语言处理。
◦ 创建QueryParser用来对查询语句进行语法分析。
◦ QueryParser调用parser进行语法分析,形成查询语法树,放到Query中。
◦ IndexSearcher调用search对查询语法树Query进行搜索,得到结果TopScoreDocCollector。
二:代码示例(本文重点部分)
1) 首先是连接数据库的jdbc配置文件信息以及存放索引文件的路径配置信息test.properties:
jdbc.driverClassName = oracle.jdbc.driver.OracleDriver
jdbc.url = jdbc:oracle:thin:@10.20.151.4:1521:ptdev
jdbc.username = pt
jdbc.password = pt
jdbc.maxIdle = 2
jdbc.maxActive = 4
jdbc.maxWait = 5000
jdbc.validationQuery = select 0
res.index.indexPath = D\:\\webapps\\test\\testHome\\search\\res\\index1
res.index.mainDirectory = D\:\\webapps\\test\\testHome\\search\\res
2) 读取资源文件的工具类PropertiesUtil.java:
package com.lucene;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* PropertiesUtil.java
* @version 1.0
* @createTime 读取配置文件信息类
*/
public class PropertiesUtil {
private static String defaultPropertyFilePath = "test.properties";
private static Map<String,Properties> ppsMap = new HashMap<String,Properties>();
/**
* 读取默认文件的配置信息,读key返回value
* @param key
* @return value
*/
public static final String getPropertyValue(String key) {
Properties pps = getPropertyFile(defaultPropertyFilePath);
return pps == null ? null : pps.getProperty(key);
}
/**
* 传入filePath读取指定property文件,读key返回value
* @param propertyFilePath
* @param key
* @return value
*/
public static String getPropertyValue(String propertyFilePath,String key) {
if(propertyFilePath == null) {
propertyFilePath = defaultPropertyFilePath;
}
Properties pps = getPropertyFile(propertyFilePath);
return pps == null ? null : pps.getProperty(key);
}
/**
* 根据path返回property文件,并保存到HashMap中,提高效率
* @param propertyFilePath
* @return
*/
public static Properties getPropertyFile(String propertyFilePath) {
if(propertyFilePath == null) {
return null;
}
Properties pps = ppsMap.get(propertyFilePath);
if(pps == null) {
InputStream in = PropertiesUtil.class.getResourceAsStream(propertyFilePath);
pps = new Properties();
try {
pps.load(in);
} catch (IOException e) {
e.printStackTrace();
}
ppsMap.put(propertyFilePath, pps);
}
return pps;
}
public static void main(String args[])
{
System.out.println(PropertiesUtil.getPropertyValue("jdbc.url"));
}
}
3) Jdbc连接数据库获取Connection工具类JdbcUtil.java:
package com.lucene;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* JdbcUtil.java
* @version 1.0
* @createTime JDBC获取Connection工具类
*/
public class JdbcUtil {
private static Connection conn = null;
private static final String URL;
private static final String JDBC_DRIVER;
private static final String USER_NAME;
private static final String PASSWORD;
static {
URL = PropertiesUtil.getPropertyValue("jdbc.url");
JDBC_DRIVER = PropertiesUtil.getPropertyValue("jdbc.driverClassName");
USER_NAME = PropertiesUtil.getPropertyValue("jdbc.username");
PASSWORD = PropertiesUtil.getPropertyValue("jdbc.password");
}
public static Connection getConnection() {
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}
4) .测试lucene, SearchLogic.java:
package com.lucene;
import java.io.File;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.TermVector;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Searcher;
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.wltea.analyzer.lucene.IKAnalyzer;
import org.wltea.analyzer.lucene.IKSimilarity;
/**
* SearchLogic.java
* @version 1.0
* @createTime Lucene数据库检索
*/
public class SearchLogic {
private static Connection conn = null;
private static Statement stmt = null;
private static ResultSet rs = null;
private String searchDir = PropertiesUtil.getPropertyValue("res.index.indexPath");
private static File indexFile = null;
private static Searcher searcher = null;
private static Analyzer analyzer = null;
/** 索引页面缓冲 */
private int maxBufferedDocs = 500;
/**
* 获取数据库数据
* @return ResultSet
* @throws Exception
*/
public List<SearchBean> getResult(String queryStr) throws Exception {
List<SearchBean> result = null;
conn = JdbcUtil.getConnection();
if(conn == null) {
throw new Exception("数据库连接失败!");
}
String sql = "select articleid,title_cn,abstract_cn from p2p_jour_article";
try {
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
this.createIndex(rs); //给数据库创建索引,此处执行一次,不要每次运行都创建索引,以后数据有更新可以后台调用更新索引
TopDocs topDocs = this.search(queryStr);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
result = this.addHits2List(scoreDocs);
} catch(Exception e) {
e.printStackTrace();
throw new Exception("数据库查询sql出错! sql : " + sql);
} finally {
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
return result;
}
/**
* 为数据库检索数据创建索引
* @param rs
* @throws Exception
*/
private void createIndex(ResultSet rs) throws Exception {
Directory directory = null;
IndexWriter indexWriter = null;
try {
indexFile = new File(searchDir);
if(!indexFile.exists()) {
indexFile.mkdir();
}
directory = FSDirectory.open(indexFile);
analyzer = new IKAnalyzer();
indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
indexWriter.setMaxBufferedDocs(maxBufferedDocs);
Document doc = null;
while(rs.next()) {
doc = new Document();
Field articleid = new Field("articleid", String.valueOf(rs
.getInt("articleid")), Field.Store.YES,
Field.Index.NOT_ANALYZED, TermVector.NO);
Field abstract_cn = new Field("abstract_cn", rs
.getString("abstract_cn") == null ? "" : rs
.getString("abstract_cn"), Field.Store.YES,
Field.Index.ANALYZED, TermVector.NO);
doc.add(articleid);
doc.add(abstract_cn);
indexWriter.addDocument(doc);
}
indexWriter.optimize();
indexWriter.close();
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* 搜索索引
* @param queryStr
* @return
* @throws Exception
*/
private TopDocs search(String queryStr) throws Exception {
if(searcher == null) {
indexFile = new File(searchDir);
searcher = new IndexSearcher(FSDirectory.open(indexFile));
}
searcher.setSimilarity(new IKSimilarity());
QueryParser parser = new QueryParser(Version.LUCENE_30,"abstract_cn",new IKAnalyzer());
Query query = parser.parse(queryStr);
TopDocs topDocs = searcher.search(query, searcher.maxDoc());
return topDocs;
}
/**
* 返回结果并添加到List中
* @param scoreDocs
* @return
* @throws Exception
*/
private List<SearchBean> addHits2List(ScoreDoc[] scoreDocs ) throws Exception {
List<SearchBean> listBean = new ArrayList<SearchBean>();
SearchBean bean = null;
for(int i=0 ; i<scoreDocs.length; i++) {
int docId = scoreDocs[i].doc;
Document doc = searcher.doc(docId);
bean = new SearchBean();
bean.setArticleid(doc.get("articleid"));
bean.setAbstract_cn(doc.get("abstract_cn"));
listBean.add(bean);
}
return listBean;
}
public static void main(String[] args) {
SearchLogic logic = new SearchLogic();
try {
Long startTime = System.currentTimeMillis();
List<SearchBean> result = logic.getResult("222");
int i = 0;
for(SearchBean bean : result) {
if(i == 10) break;
System.out.println("bean.name " + bean.getClass().getName()
+ " : bean.articleid " + bean.getArticleid()
+ " : bean.abstract_cn " + bean.getAbstract_cn());
i++;
}
System.out.println("searchBean.result.size : " + result.size());
Long endTime = System.currentTimeMillis();
System.out.println("查询所花费的时间为:" + (endTime-startTime)/1000);
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
SearchBean.java:
package com.lucene;
public class SearchBean {
private String articleid;
private String abstract_cn;
public String getArticleid() {
return articleid;
}
public void setArticleid(String articleid) {
this.articleid = articleid;
}
public String getAbstract_cn() {
return abstract_cn;
}
public void setAbstract_cn(String abstract_cn) {
this.abstract_cn = abstract_cn;
}
}
表p2p_jour_article结构如下:
SQL> desc p2p_jour_article
Name Type Nullable Default Comments
----------- ------------ -------- ------- --------
ARTICLEID VARCHAR2(10) Y
ABSTRACT_CN VARCHAR2(20) Y
TITLE_CN VARCHAR2(30) Y
数据如下:
运行SearchLogic类main方法(需引入附件中的2个jar包),结果如下:
由此查询出了ABSTRACT_CN=‘222’的记录
相关代码结构如下: