前言
之前在項目中需要用到全文檢索,根據搜索關鍵字來返回滿足條件的商品,同時需要滿足一定的商品類別和商城代碼,剛好學下lucene來初步簡單實現下這個需求。
連接Mysql數據庫和導出數據
首先我自己先封裝一個很簡單的數據庫操作工具類
public class DBUtil {
private String url;
private String user;
private String password;
private Connection conn;
public DBUtil(String url,String user,String password) throws ClassNotFoundException {
this.url=url;
this.user=user;
this.password=password;
Class.forName("com.mysql.jdbc.Driver");
}
/**
* 返回一個數據庫連接
* @return
*/
public Connection getConnection(){
try {
if(conn==null)
conn= DriverManager.getConnection(url,user,password);
return conn;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
/**
*關閉數據庫連接
*/
public void closeConnection(){
try {
if (conn != null) {
conn.close();
}
}catch (SQLException e){
e.printStackTrace();
} finally {
conn=null;
}
}
}
創建Docement
首先我們得先建立一個Docement,然後往Document加入Field,最後將Document寫入索引
下面我把創建索引抽象成一個接口
public interface DocumentCreator {
Document createDocument(ResultSet resultSet) throws Exception;
}
然後是一個具體的實現
public class SearchGoodDocumentCreator implements DocumentCreator {
@Override
public Document createDocument(ResultSet rs) throws Exception {
Document doc=new Document();
doc.add(new StoredField("id",rs.getString("id")));
doc.add(new TextField("title",rs.getString("title"), Field.Store.YES));
doc.add(new StringField("type",rs.getString("type"), Field.Store.YES));
doc.add(new StringField("mall",rs.getString("mall"), Field.Store.YES));
doc.add(new DoubleField("rank_score",rs.getDouble("rank_score"), Field.Store.YES));
doc.add(new DoubleDocValuesField("rank_score",rs.getDouble("rank_score")));
doc.add(new StoredField("urls",rs.getString("urls")));
doc.add(new StoredField("pic_urls",rs.getString("pic_urls")));
doc.add(new StoredField("comment_num",rs.getString("comment_num")));
doc.add(new SortedDocValuesField("id",new BytesRef(rs.getString("id"))));
return doc;
}
}
對於Field的選擇有以下幾種常用的情況
StringField
: 該Field不會被分詞,所以在建立完索引後想要搜索它則必須完全符合才行,因爲我在搜索商品的時候type
字段和mall
字段都是明確的,所以我使用StringField
,並且考慮到到時候搜索返回的Document結果也依然能夠拿到當時存儲的type
和mall
,所以我在創建該字段的時候還需聲明Field.Store.YES
。TextField
:它與StringField
的區別是它會先經過Analyzer進行分詞後再建立索引,因爲我是通過商品名字的關鍵字來搜索的,所以在創建title
的Field時用TextField。如果需要存儲原來還未經過分詞的原子段以便在搜索得到的結果中能夠獲取原子段的話,就加多Field.Store.YES
。DoubleField
,IntField
,LongField
等:只索引不存儲,在這裏。如果需要存儲原來還未經過分詞的原子段以便在搜索得到的結果中能夠獲取原子段的話,就加多Field.Store.YES
。DoubleDocValuesField
:用於對double
字段排序,在這裏我需要根據商品評分來對已經檢索得到的商品集合進行排序。並且我還需要在搜到的商品中仍然能保存着rankScore
字段,所以我還對rankScore
用了DoubleField
。StoredField
: 只存儲不建立索引。因爲我不需要通過搜索urls
,pic_urls
和comment_num
來獲得商品,所以只需要存儲就行了。
通過IndexWriter寫入Document
主要的步驟:
- 創建
Analyzer
- 創建
IndexWriterConfig
- 創建
Directory
- 創建
IndexWriter
- 用
IndexWriter
把創建好的Document
依次寫入Directory
因爲項目中商品的名字是中文,所以在建立索引和檢索中都需要中文分詞,所以使用了IKAnalyzer
或者SmartChineseAnalyzer
等支持中文分詞的Analyzer
。
public class IndexBuilder {
private String url;
private String user;
private String password;
private String sql;
private DBUtil dbUtil;
private Analyzer analyzer=new SmartChineseAnalyzer();
private String indexDirUrl ;
private DocumentCreator documentCreator;
public void setAnalyzer(Analyzer analyzer) {
this.analyzer = analyzer;
}
public void setDbUtil(DBUtil dbUtil) {
this.dbUtil = dbUtil;
}
public void setDocumentCreator(DocumentCreator documentCreator) {
this.documentCreator = documentCreator;
}
public void setIndexDirUrl(String indexDirUrl) {
this.indexDirUrl = indexDirUrl;
}
public void setPassword(String password) {
this.password = password;
}
public void setSql(String sql) {
this.sql = sql;
}
public void setUrl(String url) {
this.url = url;
}
public void setUser(String user) {
this.user = user;
}
public IndexBuilder(){}
/**
* 從數據庫查詢獲取結果集
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public ResultSet getResultSet() throws ClassNotFoundException, SQLException {
dbUtil=new DBUtil(url,user,password);
Connection conn=dbUtil.getConnection();
Statement statement=conn.createStatement();
return statement.executeQuery(sql);
}
/**
* 結束工作
*/
public void complete(){
if(dbUtil!=null)
dbUtil.closeConnection();
dbUtil=null;
}
/**
* 啓動建立索引
* @throws ClassNotFoundException
* @throws SQLException
* @throws IOException
*/
public void start() throws Exception {
System.out.println("luceneIndexBuilder start!");
long startTime=System.currentTimeMillis();
ResultSet rs=getResultSet();
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
Directory directory= FSDirectory.open(Paths.get(indexDirUrl));
IndexWriter indexWriter=new IndexWriter(directory,indexWriterConfig);
Document doc=null;
while(rs.next()){
doc=documentCreator.createDocument(rs);
//加入Document
indexWriter.addDocument(doc);
}
//記得調用close()才能確保索引真正寫入
indexWriter.close();
complete();
long endTime=System.currentTimeMillis();
System.out.println("luceneIndexBuilder complete");
}
}
測試
@Test
public void testStart() throws Exception {
System.out.println("---testStrat()----");
String url="jdbc:mysql://123.12.123.12:3306/example_db?useUnicode=true&characterEncoding=utf8";
String user="root";
String password="123456";
String sql="select id,title,type, mall,goods_rank.rank_score from goods;";
String indexDirUrl = new String("./indexDir/");
//SmartChineseAnalyzer和IKAnalyzer都支持中文分詞
Analyzer analyzer=new SmartChineseAnalyzer();
// Analyzer analyzer=new IKAnalyzer();
DocumentCreator documentCreator=new SearchGoodDocumentCreator();
//配置indexBuilder
IndexBuilder indexBuilder=new IndexBuilder();
indexBuilder.setUrl(url);
indexBuilder.setUser(user);
indexBuilder.setPassword(password);
indexBuilder.setSql(sql);
indexBuilder.setAnalyzer(analyzer);
indexBuilder.setIndexDirUrl(indexDirUrl);
indexBuilder.setDocumentCreator(documentCreator);
indexBuilder.start();
}