- 開始之前
- 什麼是Compass
- 與Spring、iBatis的整合
- 與Lucene的比較
- 經驗總結
- 相關資源
開始之前
本文是Compass的入門指引,通過實例介紹了Compass與iBatis、Spring的整合,適合不瞭解Compass的讀者,但要求讀者瞭解Lucene、Spring和iBatis,寫過一些簡單的應用。
文中使用的軟件包:
什麼是Compass
Compass是一個Java搜索框架。它封裝了Lucene,增加了一些Lucene不支持的特性(例如實時更新索引),支持各種數據(Java對象、xml、json)到索引的映射,支持各種數據源(JDBC, Hibernate, iBatis)。
圖解(看得煩的直接跳過看下面的例子吧):
- Compass - 一般在程序啓動時建立並被整個程序共享,主要用於建立CompassSession並通過其管理索引數據。
- CompassSession - 用於處理數據的session。
- CompassTransaction - 手動進行事務管理,如果不使用,Compass會自動管理事務。
- CompassTemplate - 將session和transaction透明化。
- 數據到索引的各種映射 - OSEM, XSEM, JSEM, RSEM。支持通過程序、XML、JSON進行配置。
- CompassGps - Gps的核心模塊,管理GpsDevice,有兩種實現:SingleCompassGps和DualCompassGps。
- CompassGpsDevice - 處理各種數據源到索引的操作:JDBC, Hibernate, iBatis等。不能獨立使用而必須融合到CompassGps中。
與Spring、iBatis的整合
建索引
1、假設Spring + iBatis的框架已經搭建好。
2、配置Domain的OSEM
- @Searchable (alias= "user" )
- public class User {
- @SearchableId
- private int id;
- @SearchableProperty (index=Index.ANALYZED, store=Store.YES)
- private String name; // 姓名
- @SearchableProperty (index=Index.NOT_ANALYZED, store=Store.YES)
- private String gender; // 性別
- @SearchableProperty (index=Index.NOT_ANALYZED, store=Store.YES)
- private int age; // 年齡
- public User() {
- }
- public User(String name, String gender, int age) {
- setName(name);
- setGender(gender);
- setAge(age);
- }
- public int getId() {
- return id;
- }
- public void setId( int id) {
- this .id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this .name = name;
- }
- public String getGender() {
- return gender;
- }
- public void setGender(String gender) {
- this .gender = gender;
- }
- public int getAge() {
- return age;
- }
- public void setAge( int age) {
- this .age = age;
- }
- }
其實就是加幾個Annotation而已。看到Index.ANALYZED、Store.YES這些東西,用過Lucene的應該大概都明白了吧。
- @Searchable - 指明該類可被映射至索引,alias參數是這一類索引對象的別名。
- @SearchableId - 索引對象的id,在同一類索引對象(同一個alias)中唯一標識一個對象。
- @SearchableProperty - 指示一個類屬性如何被索引,index和store參數類似Lucene。
3、建立LocalCompassBean,配置索引文件存放路徑和進行映射的domain。
- < bean id = "compass" class = "org.compass.spring.LocalCompassBean" >
- < property name = "compassSettings" >
- < props >
- <!-- 索引文件保存路徑 -->
- < prop key = "compass.engine.connection" > /home/index/compasstest </ prop >
- </ props >
- </ property >
- < property name = "classMappings" > <!-- 進行映射的domain -->
- < list >
- < value > ren.domain.User </ value >
- < value > ren.domain.Book </ value >
- </ list >
- </ property >
- </ bean >
4、建立SqlMapClientGpsDevice,配置iBatis的sqlMapClient和獲取數據進行索引的SQL語句id。
- < bean id = "ibatisGpsDevice" class = "org.compass.gps.device.ibatis.SqlMapClientGpsDevice" >
- < property name = "name" value = "ibatis" />
- < property name = "sqlMapClient" >
- < ref bean = "sqlMapClient" /> <!-- 引用項目中已經定義的ibatis的sqlMapClient -->
- </ property >
- < property name = "selectStatementsIds" > <!-- 對這些SQL查詢的結果進行索引 -->
- < list >
- < value > user.getAllUsers </ value >
- < value > book.getAllBooks </ value >
- </ list >
- </ property >
- </ bean >
5、建立CompassGps(SingleCompassGps或DualCompassGps),引用前面的compass和device。
- < bean id = "compassGps" class = "org.compass.gps.impl.SingleCompassGps"
- init-method = "start" destroy-method = "stop" >
- < property name = "compass" ref = "compass" />
- < property name = "gpsDevices" >
- < list >
- < ref local = "ibatisGpsDevice" />
- </ list >
- </ property >
- </ bean >
6、最後,直接調用CompassGps.index()方法建立索引。
- @Component
- @Qualifier ( "indexBuilder" )
- public class IndexBuilder {
- @Autowired
- @Qualifier ( "compassGps" )
- private CompassGps compassGps;
- public void buildIndex() {
- compassGps.index(); // 一行代碼搞定
- }
- }
查索引
1、建立CompassTemplate,引用LocalCompassBean。
- < bean id = "compassTemplate" class = "org.compass.core.CompassTemplate" >
- < property name = "compass" >
- < ref bean = "compass" />
- </ property >
- </ bean >
2、使用CompassTemplate.execute(CompassCallback<T>)進行查詢。
- @Component
- @Qualifier ( "indexSearcher" )
- public class IndexSearcher {
- @Autowired
- @Qualifier ( "compassTemplate" )
- private CompassTemplate compassTemplate;
- /**
- * 搜索用戶
- */
- public List<User> searchUser( final String name, final String gender, final int age) {
- return compassTemplate.execute( new CompassCallback<List<User>>() {
- public List<User> doInCompass(CompassSession session) throws CompassException {
- CompassQueryBuilder builder = session.queryBuilder();
- String queryString = "" ;
- if (!StringUtils.isBlank(name)) {
- queryString += "and user.name:" + name;
- }
- if (!StringUtils.isBlank(gender)) {
- queryString += "and user.gender:" + gender;
- }
- if (age > 0 ) {
- queryString += "and user.age:" + age;
- }
- CompassQuery query = builder.queryString(queryString).toQuery();
- query.addSort("user.age" , SortPropertyType.INT, SortDirection.REVERSE);
- CompassHits hits = query.hits();
- List<User> list = new ArrayList<User>();
- for (CompassHit hit : hits) {
- list.add((User)hit.data());
- }
- return list;
- }
- });
- }
- }
拼查詢字符串這裏寫得比較累贅,小朋友不要學~
與Lucene的比較
1、Compass有比Lucene更易用的API(廢話,封裝了Lucene嘛),例如支持直接更新記錄(因爲resource類似數據庫記錄,含有主鍵)。像上面的建索引過程,如果用Lucene,肯定得寫很多Java代碼。
2、支持整合各種ORM框架和Spring,減少了代碼量。例如上面例子中整合iBatis,直接幾行配置就搞定了。
3、效率問題?感覺Lucene的API用起來老是不順手,Compass這樣封裝雖然方便了,但有些擔心會不會降低了性能,於是做了個簡單的測試,分別索引4萬條記錄,結果是
Compass: 12203 ms.
Lucene: 9797 ms.
Compass比Lucene慢了大約25%,當然這個測試十分粗略,結果僅供參考。
經驗總結
1、對多個表建索引後進行搜索,在添加排序條件時,如果不指定SortPropertyType,那麼在沒有指定converter的字段上排序時會拋Exception:
java.lang.RuntimeException: field "gender" does not appear to be indexed
但如果只對單個表建索引,不會有這個問題。應該是Compass的一個bug,不知道新版本有沒有解決。
2、最好自己封裝排序字段和分頁。
3、總結,Compass比較適用於邏輯不太複雜的應用,會比Lucene少寫很多代碼。但如果需要一些較爲特殊的需求,或者對效率要求比較高,還是用Lucene吧。
相關資源
Compass入門指南:http://www.yeeach.com/2008/03/23/compass-%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97/
全文檢索的基本原理:http://blog.csdn.net/forfuture1978/archive/2009/10/22/4711308.aspx
大型網站的Lucene應用:http://www.luanxiang.org/blog/archives/605.html