compass指南針搜索框架學習(一)

學習Compass是個很快速的過程,它不像其他框架需要花很多時間學習它的API和了解它的工作流程.似乎Compass就是兩個框架的組合版本.

爲什麼這樣說呢?看下它的工作流程就知道了:

Compass

這個結構和Hibernate很相像,無非就是Hibernate把JDBC封裝了一把.所以從結構上來說,只要我們瞭解了Hibernate,就已經對Compass有了瞭解.那麼Hibernate需要提供API和配置文件來對JDBC進行操作,那麼Compass呢?Compass不僅從結構上模仿了Hibernate,就連API風格也不盡相同.我們把它和Hibernate的API做個對比就知道了:

Hibernate API Compass API
Configration cfg = new Configuration().configue(); CompassConfiguration cfg = new CompassConfiguration().configure();
SessionFactory sf = cfg.buildSessionFactory(); Compass compass = cfg.buildCompass();
Session session = sf.openSession(); CompassSession session = compass.openSession();
Transaction tx = session.beginTransaction(); CompassTransaction tx = session.beginTransaction();
session.xxx(); // do something session.xxx(); // do something
tx.commit(); tx.commit();
session.close(); session.close();

再把兩個session的核心方法比較一下:

Hibernate session API CompassSession API  
save(Object) create(Object) 建立索引
saveOrUpdate(Object) save(Object) 保存或更新
delete(Object) delete(Class, ids...) 刪除索引
get() get() 獲取
createQuery(hql).list() find(String) 使用查詢字符串查詢

所以說,Compass與Hibernate極爲相似,Compass總結起來就兩句話:

Hibernate的風格Lucene的思想

Compass2.2的版本所用的Lucene2.4.1,Lucene2.4與目前的Lucene3.0相比:

API的差別很大,代碼不能通用3.0效率更高

但是Compass用起來舒服,學習成本不高,所以這裏用Compass2.2對索引庫進行操作.

所需jar包

image

Compass配置文件

先從Compass的文檔中複製配置模板:

 

再對其進行詳細配置:

<compass name="default">
 
 <!-- 1,連接信息 -->
 <connection>
 <file path="./indexDir/" />
 </connection>
 
 <!-- 2,聲明映射信息 -->
 <mappings>
 <class name="cn.domain.Article" />
 </mappings>
 
</compass>

在Lucene裏面,如果將對象的數據存儲在索引庫裏面,需要先把對象轉換爲Document,這個工作在Compass裏面會自動將其轉爲Document,在Lucene裏面還需要指定哪些Field是可以搜索的,並且是否存儲在緩存數據裏面,那麼Compass也要提供相應的配置纔行,Compass如何做到呢?它會在主配置文件(compass.cfg.xml)中通過反射得到Class,得到該類上所有的註解信息,這就是映射信息的作用.用Article作爲測試類,屬性如下:

public class Article {
 
 private Integer id;
 private String title;
 private String content;

再生成對應的getter、setter方法,我們要指明這個類的數據是可以被搜索到的,需要在類上添加註解:

@Searchable
public class Article {

這就表明這是一個可搜索對象,Compass會通過反射創造出該類的實例,所以一定要有無參的構造函數,並且有刪除索引的需求,還有Field的配置等,對可搜索對象的所有要求爲:

要有默認的構造方法

在類上要有:@Searchable要有一個唯一標識符的屬性:@SearchableId其他的屬性用:@SearchableProperty

第二個條件主要是針對刪除和更新的,因爲之前對Compass的核心方法和Hibernate的核心方法進行了對比,刪除索引是用delete()方法,delete()方法不是接收一個對象,因爲你把對象給它,它也不知道對象與與對象之間是以什麼爲區別的,所以給它一個Class,它通過在主配置文件中的映射信息,反射出該類, 再用你提供的第二個參數,也就是唯一標識符,來找到索引,從而進行刪除和更新的操作,這就是@SearchableId的作用.

在Lucene裏面將對象轉爲Document對象的時候,需要保存的數據用Field對象來封裝,封裝的時候,要提供是否存儲數據以及是否爲數據建立索引等配置,這就是@SearchableProperty的作用.完整的對類的配置爲下,以id作爲唯一標識符:

@Searchable
public class Article {
 
 // 對於唯一標識符,默認不可以進行搜索,除非明確的指定name屬性。
 // Compass2.2對於數字是使用toString()策略,對於數字屬性應指定format屬性。
 // 如format="00000",表示數字存爲5個字符,如果不足5個,前面用若干個0補齊。
 @SearchableId(name = "id", format = "00000000")
 private Integer id;
 
 // name屬性可以不寫,默認當前屬性(成員變量)的名稱。
 @SearchableProperty(name = "title", store = Store.YES, index = Index.ANALYZED)
 private String title;
 
 @SearchableProperty(name = "content", store = Store.YES, index = Index.ANALYZED)
 private String content;

注意導包不要導錯了,全是org.compass.annotations包中的.屬性配置基本和Lucene裏面一樣.唯一需要注意的就是對數字的處理.如果在Compass的使用過程中出來問題,或者找不到索引之類的,一定要記得看你的數據類型.

創建索引的方式基本與Lucene相同,畢竟Compass是對Lucene的封裝,思想是屬於Lucene的.就像Hibernate使用Session需要先得到SessionFactory一樣,Compass也需要獲得SessionFactory,並且也是由Configuration對象來取得的:

public class ArticleDao {
 
 private Compass compassSessionFactory;
 
 public ArticleDao() {
 CompassConfiguration cfg = new CompassConfiguration().configure();
 compassSessionFactory = cfg.configure().buildCompass();
 }

在Hibernate中,對於Session,SessionFactory只需要一個就夠了,對於一個索引庫,全局只有一個Compass對象就可以了:

@Test
public void createIndex(){
 //準備好測試數據
 Article article = new Article();
 article.setId(1);
 article.setTitle("baby");
 article.setContent("love you");
 
 //獲得Session
 CompassSession session = compassSessionFactory.openSession();
 //這個事務是針對索引庫的事務
 CompassTransaction tx = session.beginTransaction();
 session.save(article);
 tx.commit();
 session.close(); //一定要記得關閉
}

簡單的查詢,就像Lucene的查詢字符串一樣.

@Test
public void searchIndex() {
 //獲得Session
 CompassSession session = compassSessionFactory.openSession();
 //這個事務是針對索引庫的事務
 CompassTransaction tx = session.beginTransaction();
 //找到一條結果數
 // Article article = session.load(Article.class, 1);
 // System.out.println(article.getId());
 // System.out.println(article.getTitle());
 // System.out.println(article.getContent());
 String queryString = "love";
 CompassHits hits = session.find(queryString);
 int count = hits.length();
 for (int i = 0; i < count; i++) {
 Article article = (Article) hits.data(i);
 System.out.println(article.getId());
 System.out.println(article.getTitle());
 System.out.println(article.getContent());
 }
 tx.commit();
 session.close(); //一定要記得關閉
}

這只是一個簡單增加索引和查詢索引的例子,權當做Hello World吧.

每次使用Session都要從SessionFactory中獲取,無疑是很麻煩的,不如把SessionFactory維護在工具類裏面,全局只有一個,在用一個靜態方法得到一個Session.

@Test
public void deleteIndex(){
 CompassSession session = CompassUtils.getSession();
 CompassTransaction tx = session.beginTransaction();
 session.delete(Article.class,1);
 tx.commit();
 session.close();
}
 
@Test
public void updateIndex(){
 CompassSession session = CompassUtils.getSession();
 CompassTransaction tx = session.beginTransaction();
 Article article = session.get(Article.class, 1);
 article.setContent("love love");
 session.save(article);
 tx.commit();
 session.close();
}

注意,如果唯一標識符是數字,並且沒有在實體類裏面對唯一標識符使用format屬性,則以上代碼獲取不到!

 


默認的分詞是標準分詞器,這個分詞器對中文采取的是單字分詞,根本沒辦法使用.一般做法是用第三方的分詞器,常用的有極易分詞、庖丁分詞、IKAnalyzer等.要使用分詞器,需要在Compass的主配置文件裏面進行配置,這裏採用極易分詞:

<!-- 3,其他配置 -->
<settings>
 <!-- 配置分詞器類型 -->
 <setting name="compass.engine.analyzer.default.type" value="jeasy.analysis.MMAnalyzer"></setting>
</settings>

這個分詞器對中文支持比較好,採用的是詞庫分詞,準確度較高.

 


接下來還要配置高亮器,配置高亮器主要有三點:前綴、後綴、高亮顯示的摘要.同樣是在主配置文件裏面進行配置:

<!-- 3,其他配置 -->
<settings>
 <!-- 高亮配置:摘要的長度(字符數量,默認爲100) -->
 <setting name="compass.engine.highlighter.default.fragmenter.simple.size" value="20" />
 <!-- 高亮配置:顯示效果的前綴 -->
 <setting name="compass.engine.highlighter.default.formatter.simple.pre" value="&lt;span class='keyword'&gt;" />
 <!-- 高亮配置:顯示效果的後綴 -->
 <setting name="compass.engine.highlighter.default.formatter.simple.post" value="&lt;/span&gt;" />
 
 <!-- 配置分詞器類型 -->
 <setting name="compass.engine.analyzer.default.type" value="jeasy.analysis.MMAnalyzer"></setting>
</settings>

注意,前綴和後綴中的標籤,要轉義,不然會和配置文件中的符號產生衝突!

@Test
public void highlighterIndex() {
 CompassSession session = CompassUtils.getSession();
 CompassTransaction tx = session.beginTransaction();
 // 查詢條件
 String queryString = "love";
 CompassHits hits = session.find(queryString);
 int count = hits.length();
 for (int i = 0; i < count; i++) {
 Article article = (Article) hits.data(i);
 // -------------------------
 // 進行高亮操作,一次高亮一個屬性值
  // 返回高亮後的一段文本摘要,原屬性值不會改變
   // 如果當前高亮的屬性值中沒有出現關鍵字,則返回null.
 String text = hits.highlighter(i).fragment("content");
 if(text != null){
 // 使用的高亮後的文本替原文本
 article.setContent(text);
 }
 System.out.println(article.getId());
 System.out.println(article.getTitle());
 System.out.println(article.getContent());
 }
 tx.commit();
 session.close();
}

一般來說搜索結果的排序和Lucene裏面是一樣的,按照相關度得分進行排序,在實際應用中,往往需要人工操縱得分,比如給了推廣費之類的,既然有這種需求,自然就有這種做法,和Lucene的不同之處在於,Lucene在添加索引時控制相關度得分的比重,Compass也是在添加索引時控制,不過需要在實體類裏面增加一個屬性和它的getter、setter方法,這個屬性決定了所有這個類的數據的相關度得分的比重,也就是說可以讓只要是這個實體類的數據,就能比別的類的數據得分要高,並且只要是設置了這個屬性,就能在添加索引時控制相關度得分的比重:

@SearchableBoostProperty
private float boostValue = 1F;

在實體裏面增加這個屬性,屬性名無所謂,類型是float,最重要的是要有getter、setter方法和@SearchableBoostProperty,必須要有這處註解,Lucene才能在你調用相關方法的時候,通過反射獲得有這個註解的屬性名,在調用它的getter方法,得到值,那麼在程序中就要增加一句代碼:

//準備好測試數據
Article article = new Article();
article.setId(3);
article.setTitle("baby");
article.setContent("love you");
//設定比重,默認爲1L
article.setBoostValue(2L);

其他地方都是一樣的.除了對相關度得分的排序以外,還可能在搜索時使用某個屬性的值進行排序.這也是可以的:

@Test
public void sortIndex() {
 CompassSession session = CompassUtils.getSession();
 CompassTransaction tx = session.beginTransaction();
 // 查詢條件
 String queryString = "love";
 //CompassHits hits = session.find(queryString);
 CompassQuery query = session.queryBuilder().queryString(queryString).toQuery();
 query.addSort("id");
 CompassHits hits = query.hits();
 int count = hits.length();
 for (int i = 0; i < count; i++) {
 Article article = (Article) hits.data(i);
 System.out.println(article.getId());
 System.out.println(article.getTitle());
 System.out.println(article.getContent());
 }
 tx.commit();
 session.close();
}

只需要在得到搜索結果之前做一點改動.先得到CompassQuery對象,調用這個對象的addSort()方法增加排序規則.像上述一樣,則是按升序進行排序,也可以替換成下面這句使用降序排列:

query.addSort("id",SortDirection.REVERSE);

注意,指定參與排序的字段時,參與排序的字段必須要是Index.NOT_ANALYZED,否則分詞後無法找到!

有時候也會使用到過濾器對搜索結果進行過濾.

@Test
public void filterIndex() {
 CompassSession session = CompassUtils.getSession();
 CompassTransaction tx = session.beginTransaction();
 //查詢條件
 String queryString = "love";
 //得到CompassQuery對象,準備查詢
 CompassQuery query = session.queryBuilder().queryString(queryString).toQuery();
 /**
 * 增加過濾條件,between第三、四個參數分別代表是否包含下邊界和上邊界
 */
 CompassQueryFilter filter = session.queryFilterBuilder().between("id", 2, 3, true, true);
 //添加過濾器
 query.setFilter(filter);
 CompassHits hits = query.hits();
 int count = hits.length();
 for (int i = 0; i < count; i++) {
 Article article = (Article) hits.data(i);
 System.out.println(article.getId());
 System.out.println(article.getTitle());
 System.out.println(article.getContent());
 }
 tx.commit();
 session.close();
}

但是使用過濾器是效率很低的做法,使用查詢字符串可以獲得更高的效率,而且可以達到同樣的效果.下面就對各種查詢進行簡要說明:

@Test
public void search() throws Exception {
 CompassSession session = CompassUtils.openSession();
 CompassTransaction tx = session.beginTransaction();
 
 // 查詢方式一:使用查詢字符串(可以有查詢語法)
 // CompassHits hits = session.find(queryString);
 // -----------------------------------------------------------------
 // 查詢方式二:構建CompassQuery對象
 // 1,查詢所有
 CompassQuery query1 = session.queryBuilder().matchAll();
 
 // 2,關鍵詞查詢
 CompassQuery query2 = session.queryBuilder().term("title", "lucene");
 
 // 3,範圍查詢
 CompassQuery query3 = session.queryBuilder().between("id", 5, 15, true);
 
 // 4,通配符查詢
 CompassQuery query4 = session.queryBuilder().wildcard("title", "lu*n?");
 
 // 5,短語查詢
 // 設定精確間隔的匹配方式,寫法1
 CompassMultiPhraseQueryBuilder qb = session.queryBuilder().multiPhrase("title");
 qb.add("lucene", 0); // 第一個詞位置從0開始
 qb.add("工作", 2); // 第二個詞位置從2開始
 CompassQuery query5 = qb.toQuery();
 // 設定精確間隔的匹配方式,寫法2
 CompassQuery query6 = session.queryBuilder().multiPhrase("title")//
 .add("lucene", 0) // 第一個詞位置從0開始
 .add("工作", 2) // 第二個詞位置從2開始
 .toQuery();
 // 設定最多間隔的匹配方式
 CompassQuery query7 = session.queryBuilder().multiPhrase("title")//
 .add("lucene") //
 .add("工作") //
 .setSlop(5) // 指定的詞之間的最長間隔不超過5個詞
 .toQuery();
 // 6,布爾查詢
 CompassQuery query = session.queryBuilder().bool()//
 // .addMust(query)
 // .addMustNot(query)
 // .addShould(query)
 .addMust(query1)//
 .addMustNot(query3)//
 .toQuery();
 
 // -------------------------------
 CompassHits hits = query.hits();
 
 // 處理結果
 List<Article> list = new ArrayList<Article>();
 int count = hits.length(); // 總結果數
 
 for (int i = 0; i < hits.length(); i++) {
 Article article = (Article) hits.data(i);
 list.add(article);
 }
 
 tx.commit();
 session.close();
 // -----------------------------------------------------------------
 // 顯示結果
 System.out.println("====== 符合條件的總記錄數爲:" + count + " ======");
 for (Article a : list) {
 System.out.println("--------------> id = " + a.getId());
 System.out.println("title = " + a.getTitle());
 System.out.println("content= " + a.getContent());
 }
}

基本上還是Lucene的那一套,只是簡單的封裝一下而已.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章