在新架構中打算選擇Compass或Hibernate Search作為搜索引擎框架,比較后,感覺Hibernate Search上還是沒有Compass成熟,另外考慮到后期對網頁的爬取及搜索需求,決定還是基于Compass來作為架構缺省的搜索引擎。網上關于 Compass的文檔很多,但說得相對完整其詳細的入門文檔基本上沒有,Compass的官方文檔倒是說得很詳細,但是例子一塌糊涂,存在很大問題。記錄一下搭建的過程,作為入門的指南。

    Compass 通過OSEM(Object/Search Engine Mapping)允許把應用對象的領域模型映射到搜索引擎,最終通過訪問common meta data來達到訪問對象的目的。
1、幾個核心概念

1.1、annotation vs. xml配置文件

   Compass的配置文件主要分成三類:

    第一類:*.cmd.xml文件*

      .cmd.xml文件是對common meta data進行定義,定義了最終搜索的結果中的最基本的元數據。

    第二類:*.cpm.xml文件

      *.cpm.xml是Object/Search Engine Mapping,提供了POJO到common meta data的映射。

    第三類:*.cfg.xml文件

      Compass的*.cfg.xml定義了Compass的Index存放路徑、搜索引擎分詞等相關信息。

    與采用xml配置文件相比較,采用Annonation方式還是相對簡單,尤其是采用Spring時候,不用寫*.cmd.xml文件、*.cpm.xml、*.cfg.xml,相對很方便,而且不像Hibernate的Annonation很多,Compass的 Annonation的核心標注只有@Searchable、@SearchableId、@SearchableProperty、 @SearchableComponent個,很容易記憶。因此推薦使用Annonation方式
1.2、Compass核心API

Compass的核心API借鑒了Hibernate的術語,因此在操作上基本上與Hibernate類似,以下為Compass的幾個核心接口:

    CompassConfiguration(類似Hibernate Configuration):用來在一些設置參數、配置文件和映射定義上配置Compass。通常用來創建Compass接口。
    Compass(類似Hibernate SessionFactory):為單線程使用,創建線程安全的實例來打開Compass Seesion。同樣還提供了一些搜索引擎索引級別的操作。
    CompassSesssion(類似Hibernate Session):用來執行像保存、刪除、查找、裝載這樣的搜索操作。很輕量但是并不是線程安全的。
    CompassTransaction(類似Hibernate Transaction):管理Compass事務的接口。使用它并不需要事務管理環境(像Spring、JTA)。

1.3、Compass與Spring集成

Compass 已經對對spring集成做了很好的封裝,同時與Spring對Hibernate的支持類似,Compass也提供了CompassTemplate來簡化諸如對Session、Transaction、Exception等操作,盡量充分使用此工具,可以有效提高效率。例如:

CompassTemplate ct = (CompassTemplate) context.getBean("compassTemplate");

Article article = new Article();
article.setTitle("Compass Test");
article.setPublishDate(new Date());
article.setAuthor(1);

ct.save(article); //存儲對象需要索引的數據到Compass的索引中。


2、軟件環境

Spring :2.5

Compas:1.2.1

Hibernate:3.2.5

Mysql :5.0.5
3、數據庫腳本

CREATE TABLE `article` (

`Id` int(11) NOT NULL auto_increment,

`title` varchar(40) NOT NULL default '',

`author` int(11) default '0',

`publish_date` date NOT NULL default '0000-00-00',

PRIMARY KEY (`Id`) ) TYPE=MyISAM;

CREATE TABLE `author` (

`Id` int(11) NOT NULL auto_increment,

`username` varchar(20) NOT NULL default '',

`password` varchar(20) NOT NULL default '',

`age` smallint(6) default '0',

PRIMARY KEY (`Id`) ) TYPE=MyISAM;

4、測試用例

從測試用例講起比較容易把關系理清楚,不然一堆術語和概念很讓人暈乎。
Java代碼 復制代碼
  1. import org.apache.log4j.Logger;   
  2. import java.util.Date;   
  3.   
  4. import junit.framework.TestCase;   
  5.   
  6. import org.compass.core.Compass;   
  7. import org.compass.core.CompassDetachedHits;   
  8. import org.compass.core.CompassHit;   
  9. import org.compass.core.CompassHits;   
  10. import org.compass.core.CompassSession;   
  11. import org.compass.core.CompassTemplate;   
  12. import org.compass.core.CompassTransaction;   
  13. import org.compass.core.support.search.CompassSearchCommand;   
  14. import org.compass.core.support.search.CompassSearchResults;   
  15. import org.springframework.context.support.ClassPathXmlApplicationContext;   
  16.   
  17. import com.mobilesoft.esales.dao.hibernate.ArticleDAO;   
  18. import com.mobilesoft.esales.dao.hibernate.AuthorDAO;   
  19. import com.mobilesoft.esales.model.Article;   
  20. import com.mobilesoft.esales.model.Author;   
  21. import com.mobilesoft.framework.search.service.CompassSearchService;   
  22.   
  23. /**  
  24.  * Compass服務使用的測試用例  
  25.  *   
  26.  * @author liangchuan@mobile-soft.cn  
  27.  *   
  28.  */  
  29.   
  30. public class TestCompass extends TestCase {   
  31.   
  32.     private static final Logger logger = Logger.getLogger(TestCompass.class);   
  33.   
  34.     private static ClassPathXmlApplicationContext context = null;   
  35.     private static CompassTemplate ct;   
  36.   
  37.     static {   
  38.         context = new ClassPathXmlApplicationContext(new String[] {   
  39.                 "applicationContext.xml""applicationContext-resources.xml",   
  40.                 "applicationContext-dao.xml""applicationContext-service.xml",   
  41.                 "applicationContext-compass.xml" });   
  42.         ct = (CompassTemplate) context.getBean("compassTemplate");   
  43.     }   
  44.   
  45.     protected void setUp() throws Exception {   
  46.   
  47.     }   
  48.   
  49.     /**  
  50.      * 插入測試數據  
  51.      */  
  52.     public void testInsert() {   
  53.   
  54.         ArticleDAO articleDao = (ArticleDAO) context.getBean("articleDAO");   
  55.         AuthorDAO authorDao = (AuthorDAO) context.getBean("authorDAO");   
  56.         Article article = new Article();   
  57.         Author author = new Author();   
  58.         author.setAge((short27);   
  59.         author.setUsername("liangchuan");   
  60.         author.setPassword("liangchuan");   
  61.         article.setTitle("Compass Test");   
  62.         article.setPublishDate(new Date());   
  63.         article.setAuthor(1);   
  64.         authorDao.save(author);   
  65.         articleDao.save(article);   
  66.         ct.save(article);   
  67.         ct.save(author);   
  68.     }   
  69.   
  70.     /**  
  71.      * 用于測試使用CompassTransaction事務方式  
  72.      */  
  73.     public void testTransactionalFind() {   
  74.   
  75.         Compass compass = ct.getCompass();   
  76.         CompassSession session = compass.openSession();   
  77.         CompassTransaction tx = null;   
  78.         try {   
  79.             tx = session.beginTransaction();   
  80.             CompassHits hits = session.find("Compass*");   
  81.   
  82.             logger.error("testTransactionalFind() - CompassHits hits="  
  83.                     + hits.getLength());   
  84.             for (int i = 0; i < hits.getLength(); i++) {   
  85.                 Object hit = hits.data(i);   
  86.                 if (hit instanceof Article) {   
  87.                     Article item = (Article) hit;   
  88.                     logger.error("testTransactionalFind() - article     hits="  
  89.                             + item.getTitle());   
  90.                 } else if (hit instanceof Author) {   
  91.                     Author item = (Author) hit;   
  92.                     logger.error("testTransactionalFind() - author hits="  
  93.                             + item.getUsername());   
  94.                 } else {   
  95.                     logger.error("testTransactionalFind() - error hits=");   
  96.                 }   
  97.             }   
  98.             tx.commit();   
  99.         } catch (Exception e) {   
  100.             if (tx != null) {   
  101.                 tx.rollback();   
  102.             }   
  103.         } finally {   
  104.             session.close();   
  105.         }   
  106.     }   
  107.   
  108.     /**  
  109.      * 用于演示CompassDetachedHits的使用。  
  110.      * 由于CompassTempalte得到的結果集必須在transactionalcontext中才能使用,  
  111.      * 因此必須使用CompassDetachedHits方式測試CompassDetachedHits方式  
  112.      */  
  113.     public void testDetachedFind() {   
  114.   
  115.         // 由于CompassTempalte得到的結果集必須在transactional   
  116.         // context中才能使用,因此必須使用CompassDetachedHits方式   
  117.         // 測試CompassDetachedHits方式   
  118.         CompassDetachedHits hits = ct.findWithDetach("Compass*");   
  119.   
  120.         logger.error("testDetachedFind() - CompassHits hits="  
  121.                 + hits.getLength());   
  122.         for (int i = 0; i < hits.getLength(); i++) {   
  123.             Object hit = hits.data(i);   
  124.             if (hit instanceof Article) {   
  125.                 Article item = (Article) hit;   
  126.                 logger.error("testDetachedFind() - article     hits="  
  127.                         + item.getTitle());   
  128.             } else if (hit instanceof Author) {   
  129.                 Author item = (Author) hit;   
  130.                 logger.error("testDetachedFind() - author hits="  
  131.                         + item.getUsername());   
  132.             } else {   
  133.                 logger.error("testDetachedFind() - error hits=");   
  134.             }   
  135.         }   
  136.   
  137.     }   
  138.   
  139.     /**  
  140.      * 用于演示com.mobilesoft.framework.search.service.CompassSearchService的使用  
  141.      *   
  142.      */  
  143.     class CompassSearch extends CompassSearchService{   
  144.         CompassSearch(){   
  145.             Compass compass = ct.getCompass();   
  146.             CompassSession session = compass.openSession();   
  147.             CompassTransaction tx = null;   
  148.   
  149.             try {   
  150.                 tx = session.beginTransaction();   
  151.                 CompassSearchCommand command = new CompassSearchCommand();   
  152.                 command.setQuery("Compass");   
  153.                 CompassSearchResults results= performSearch(command,session);   
  154.                 logger.error("CompassSearch() - CompassHit TotalHits value=" +results.getTotalHits());   
  155.   
  156.                 for (int i = 0; i < results.getHits().length; i++) {   
  157.                     CompassHit hits=results.getHits()[i];   
  158.                     Object hit=hits.getData();   
  159.                     logger.error("CompassSearch() - CompassHit hit=" + hit); //$NON-NLS-1$   
  160.   
  161.                     if (hit instanceof Article) {   
  162.                         Article item = (Article) hit;   
  163.                         logger.error("testCompassSearchService() - article     hits="  
  164.                                 + item.getTitle());   
  165.                     } else if (hit instanceof Author) {   
  166.                         Author item = (Author) hit;   
  167.                         logger.error("testCompassSearchService() - author hits="  
  168.                                 + item.getUsername());   
  169.                     } else {   
  170.                         logger.error("testCompassSearchService() - error hits=");   
  171.                     }   
  172.                        
  173.                     tx.commit();   
  174.                 }   
  175.             } catch (Exception e) {   
  176.                 if (tx != null) {   
  177.                     tx.rollback();   
  178.                 }   
  179.             } finally {   
  180.                 session.close();   
  181.             }           
  182.                        
  183.         }   
  184.            
  185.     }   
  186.     public void testCompassSearchService() {   
  187.         new CompassSearch();   
  188.         }   
  189.   
  190.     protected void tearDown() throws Exception {   
  191.     }   
  192. }  


5、配置文件
applicationContext-compass.xml
Java代碼 復制代碼
  1. <?xml version="1.0"?>   
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"  
  3.     "http://www.springframework.org/dtd/spring-beans-2.0.dtd">   
  4. <beans default-lazy-init="true">   
  5.     <bean id="compassTemplate" class="org.compass.core.CompassTemplate">   
  6.         <property name="compass" ref="compass"/>   
  7.     </bean>   
  8.     <bean id="annotationConfiguration"  
  9.         class="org.compass.annotations.config.CompassAnnotationsConfiguration">   
  10.     </bean>   
  11.     <bean id="compass" class="org.compass.spring.LocalCompassBean">   
  12.         <property name="classMappings">   
  13.             <list>   
  14.                 <value>com.mobilesoft.esales.model.Article</value>   
  15.                 <value>com.mobilesoft.esales.model.Author</value>   
  16.             </list>   
  17.         </property>   
  18.         <property name="compassConfiguration" ref="annotationConfiguration"/>   
  19.            
  20.         <property name="compassSettings">   
  21.             <props>   
  22.                 <prop key="compass.engine.connection"> file://compass </prop>   
  23.                 <prop key="compass.transaction.factory">   
  24.                     org.compass.spring.transaction.SpringSyncTransactionFactory   
  25.                     </prop>   
  26.                 <prop   
  27.                     key="compass.engine.highlighter.default.formatter.simple.pre">   
  28.                     <![CDATA[<font color="red"><b>]]>   
  29.                 </prop>   
  30.                 <prop   
  31.                     key="compass.engine.highlighter.default.formatter.simple.post">   
  32.                     <![CDATA[</b></font>]]>   
  33.                 </prop>   
  34.             </props>   
  35.         </property>   
  36.            
  37.         <property name="transactionManager" ref="transactionManager"/>   
  38.     </bean>   
  39.        
  40.     <bean id="hibernateGpsDevice"  
  41.         class="org.compass.gps.device.hibernate.HibernateGpsDevice">   
  42.         <property name="name">   
  43.             <value>hibernateDevice</value>   
  44.         </property>   
  45.         <property name="sessionFactory" ref="sessionFactory"/>   
  46.         <property name="mirrorDataChanges">   
  47.             <value>true</value>   
  48.         </property>   
  49.     </bean>   
  50.     <bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"  
  51.         init-method="start" destroy-method="stop">   
  52.         <property name="compass" ref="compass"/>   
  53.         <property name="gpsDevices">   
  54.             <list>   
  55.                 <bean   
  56.                     class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">   
  57.                     <property name="gpsDevice" ref="hibernateGpsDevice"/>   
  58.                 </bean>   
  59.             </list>   
  60.         </property>   
  61.     </bean>   
  62.     <bean id="compassSearchService" class="com.mobilesoft.framework.search.service.CompassSearchService">   
  63.         <property name="compass" ref="compass"/>   
  64.         <property name="pageSize" value="15"/>   
  65.     </bean>   
  66.   
  67.     <!-- 定時重建索引(利用quartz)或隨Spring ApplicationContext啟動而重建索引 -->   
  68.     <bean id="compassIndexBuilder" class="com.mobilesoft.framework.search.service.CompassIndexBuilder" lazy-init="false">   
  69.         <property name="compassGps" ref="compassGps"/>   
  70.         <property name="buildIndex" value="false"/>   
  71.         <property name="lazyTime" value="10"/>   
  72.     </bean>   
  73.        
  74.        
  75. </beans>  


applicationContext-dao.xml、applicationContext-service.xml、applicationContext-resources.xml等略去。


6、Service層(參考了SpringSide實現)
Java代碼 復制代碼
  1. AdvancedSearchCommand.java   
  2. package com.mobilesoft.framework.search.service;   
  3.   
  4. import java.util.HashSet;   
  5. import java.util.Set;   
  6.   
  7. import org.apache.commons.lang.StringUtils;   
  8. import org.compass.core.CompassQuery.SortDirection;   
  9. import org.compass.core.CompassQuery.SortPropertyType;   
  10. import org.compass.core.support.search.CompassSearchCommand;   
  11.   
  12. import org.springframework.util.Assert;   
  13.   
  14. public class AdvancedSearchCommand extends CompassSearchCommand {   
  15.   
  16.     /**  
  17.      * 封裝基于Compass 的排序參數.  
  18.      */  
  19.     class CompassSort {   
  20.   
  21.         private String name;   
  22.   
  23.         private SortPropertyType type;   
  24.   
  25.         private SortDirection direction;   
  26.   
  27.         public CompassSort() {   
  28.         }   
  29.   
  30.         public CompassSort(String sortParamName, String paramType,   
  31.                            boolean isAscend) {   
  32.             Assert.isTrue(StringUtils.isNotBlank(sortParamName));   
  33.             setName(sortParamName);   
  34.   
  35.             if ("int".equalsIgnoreCase(paramType)) {   
  36.                 setType(SortPropertyType.INT);   
  37.             } else if ("float".equalsIgnoreCase(paramType)) {   
  38.                 setType(SortPropertyType.FLOAT);   
  39.             } else if ("string".equalsIgnoreCase(paramType)) {   
  40.                 setType(SortPropertyType.STRING);   
  41.             } else {   
  42.                 setType(SortPropertyType.AUTO);   
  43.             }   
  44.   
  45.             if (isAscend) {   
  46.                 setDirection(SortDirection.AUTO);   
  47.             } else {   
  48.                 setDirection(SortDirection.REVERSE);   
  49.             }   
  50.         }   
  51.   
  52.         public String getName() {   
  53.             return name;   
  54.         }   
  55.   
  56.         public void setName(String name) {   
  57.             this.name = name;   
  58.         }   
  59.   
  60.         public SortPropertyType getType() {   
  61.             return type;   
  62.         }   
  63.   
  64.         public void setType(SortPropertyType type) {   
  65.             this.type = type;   
  66.         }   
  67.   
  68.         public SortDirection getDirection() {   
  69.             return direction;   
  70.         }   
  71.   
  72.         public void setDirection(SortDirection direction) {   
  73.             this.direction = direction;   
  74.         }   
  75.     }   
  76.   
  77.     /**  
  78.      * 搜索結果排序表.  
  79.      */  
  80.     private Set<CompassSort> sortMap = new HashSet<CompassSort>();   
  81.   
  82.     private String[] highlightFields;   
  83.   
  84.     /**  
  85.      * @param paramType 現定義了三種類型: int string 以及 float。<br>  
  86.      *                  除去這三種外,其他會被自動定義為SortPropertyType.AUTO 具體的可見{@link org.compass.core.CompassQuery.SortPropertyType}  
  87.      * @param isAscend  順序還是倒序排序  
  88.      * @see org.compass.core.CompassQuery.SortPropertyType#AUTO  
  89.      * @see org.compass.core.CompassQuery.SortPropertyType#INT  
  90.      * @see org.compass.core.CompassQuery.SortPropertyType#STRING  
  91.      * @see org.compass.core.CompassQuery.SortPropertyType#FLOAT  
  92.      * @see org.compass.core.CompassQuery.SortDirection#AUTO  
  93.      * @see org.compass.core.CompassQuery.SortDirection#REVERSE  
  94.      */  
  95.     public void addSort(String sortParamName, String paramType, boolean isAscend) {   
  96.         this.sortMap.add(new CompassSort(sortParamName, paramType, isAscend));   
  97.     }   
  98.   
  99.     public Set<CompassSort> getSortMap() {   
  100.         return sortMap;   
  101.     }   
  102.   
  103.     public void setSortMap(Set<CompassSort> sortMap) {   
  104.         this.sortMap = sortMap;   
  105.     }   
  106.   
  107.     public String[] getHighlightFields() {   
  108.         return highlightFields;   
  109.     }   
  110.   
  111.     public void setHighlightFields(String[] highlightFields) {   
  112.         this.highlightFields = highlightFields;   
  113.     }   
  114. }   
  115.   
  116. CompassIndexBuilder.java   
  117.   
  118. package com.mobilesoft.framework.search.service;   
  119.   
  120. import org.apache.log4j.Logger;   
  121. import org.compass.gps.CompassGps;   
  122. import org.springframework.beans.factory.InitializingBean;   
  123. import org.springframework.util.Assert;   
  124.   
  125. /**  
  126.  * 通過quartz定時調度定時重建索引或自動隨Spring ApplicationContext啟動而重建索引的Builder.  
  127.  * 會啟動后延時數秒新開線程調用compassGps.index()函數.  
  128.  * 默認會在Web應用每次啟動時重建索引,可以設置buildIndex屬性為false來禁止此功能.  
  129.  * 也可以不用本Builder, 編寫手動調用compassGps.index()的代碼.  
  130.  *  
  131.  */  
  132. public class CompassIndexBuilder implements InitializingBean {   
  133.   
  134.     private static final Logger log = Logger.getLogger(CompassIndexBuilder.class);   
  135.   
  136.     // 是否需要建立索引,可被設置為false使本Builder失效.   
  137.     private boolean buildIndex = false;   
  138.   
  139.     // 索引操作線程延時啟動的時間,單位為秒   
  140.     private int lazyTime = 10;   
  141.   
  142.     // Compass封裝   
  143.     private CompassGps compassGps;   
  144.   
  145.     // 索引線程   
  146.     private Thread indexThread = new Thread() {   
  147.   
  148.         @Override  
  149.         public void run() {   
  150.             try {   
  151.                 Thread.sleep(lazyTime * 1000);   
  152.   
  153.                 log.info("begin compass index...");   
  154.                 long beginTime = System.currentTimeMillis();   
  155.                 // 重建索引.   
  156.                 // 如果compass實體中定義的索引文件已存在,索引過程中會建立臨時索引,   
  157.                 // 索引完成后再進行覆蓋.   
  158.                 compassGps.index();   
  159.                 long costTime = System.currentTimeMillis() - beginTime;   
  160.                 log.info("compss index finished.");   
  161.                 log.info("costed " + costTime + " milliseconds");   
  162.             } catch (InterruptedException e) {   
  163.                 // simply proceed   
  164.             }   
  165.         }   
  166.     };   
  167.   
  168.     /**  
  169.      * 實現<code>InitializingBean</code>接口,在完成注入后調用啟動索引線程.  
  170.      *  
  171.      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()  
  172.      */  
  173.     public void afterPropertiesSet() throws Exception {   
  174.         if (buildIndex) {   
  175.             Assert.notNull(compassGps, "CompassIndexBuilder not set CompassGps yet.");   
  176.             indexThread.setDaemon(true);   
  177.             indexThread.setName("Compass Indexer");   
  178.             indexThread.start();   
  179.         }   
  180.     }   
  181.   
  182.     public void setBuildIndex(boolean buildIndex) {   
  183.         this.buildIndex = buildIndex;   
  184.     }   
  185.   
  186.     public void setLazyTime(int lazyTime) {   
  187.         this.lazyTime = lazyTime;   
  188.     }   
  189.   
  190.     public void setCompassGps(CompassGps compassGps) {   
  191.         this.compassGps = compassGps;   
  192.     }   
  193. }   
  194.   
  195. CompassSearchService.java   
  196.   
  197. package com.mobilesoft.framework.search.service;   
  198.   
  199. import org.compass.core.Compass;   
  200. import org.compass.core.CompassCallback;   
  201. import org.compass.core.CompassDetachedHits;   
  202. import org.compass.core.CompassHits;   
  203. import org.compass.core.CompassQuery;   
  204. import org.compass.core.CompassSession;   
  205. import org.compass.core.CompassTemplate;   
  206. import org.compass.core.CompassTransaction;   
  207. import org.compass.core.support.search.CompassSearchCommand;   
  208. import org.compass.core.support.search.CompassSearchResults;   
  209.   
  210. import org.springframework.beans.factory.InitializingBean;   
  211. import org.springframework.util.Assert;   
  212.   
  213. import com.mobilesoft.framework.search.service.AdvancedSearchCommand.CompassSort;   
  214. /**  
  215.  * 仿照 {@link org.compass.spring.web.mvc.CompassSearchController}  
  216.  * 中的代碼,構建了一個Service,方便不使用Spring MVC   
  217.  *  
  218.  * @see org.compass.spring.web.mvc.CompassSearchController  
  219.  * @see org.compass.spring.web.mvc.AbstractCompassCommandController  
  220.  */  
  221. public class CompassSearchService implements InitializingBean {   
  222.   
  223.     //每頁顯示的條目數量   
  224.     private Integer pageSize = 15;   
  225.   
  226.     private Compass compass;   
  227.   
  228.     private CompassTemplate compassTemplate;   
  229.   
  230.     /**  
  231.      * 公開的搜索接口,返回匹配的搜索結果,與  
  232.      * {@link org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,  
  233.      *javax.servlet.http.HttpServletResponse,Object,org.springframework.validation.BindException) 處理相似  
  234.      *  
  235.      * @see org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,  
  236.      *javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.validation.BindException)  
  237.      */  
  238.     public CompassSearchResults search(final CompassSearchCommand command) {   
  239.         return (CompassSearchResults) getCompassTemplate().execute(   
  240.                 CompassTransaction.TransactionIsolation.READ_ONLY_READ_COMMITTED, new CompassCallback() {   
  241.             public Object doInCompass(CompassSession session) {   
  242.                 return performSearch(command, session);   
  243.             }   
  244.         });   
  245.     }   
  246.   
  247.     /**  
  248.      * 通過此方法調用搜索引擎,進行結果匹配搜索.  
  249.      *  
  250.      * @see org.compass.spring.web.mvc.CompassSearchController#performSearch(  
  251.      *org.compass.spring.web.mvc.CompassSearchCommand,org.compass.core.CompassSession)  
  252.      */  
  253.     protected CompassSearchResults performSearch(CompassSearchCommand searchCommand, CompassSession session) {   
  254.         long time = System.currentTimeMillis();   
  255.         CompassQuery query = buildQuery(searchCommand, session);   
  256.         CompassHits hits = query.hits();   
  257.         CompassDetachedHits detachedHits;   
  258.         CompassSearchResults.Page[] pages = null;   
  259.         if (pageSize == null) {   
  260.             doProcessBeforeDetach(searchCommand, session, hits, -1, -1);   
  261.             detachedHits = hits.detach();   
  262.         } else {   
  263.             int iPageSize = pageSize;   
  264.             int page = 0;   
  265.             int hitsLength = hits.getLength();   
  266.             if (searchCommand.getPage() != null) {   
  267.                 page = searchCommand.getPage();   
  268.             }   
  269.             int from = page * iPageSize;   
  270.   
  271.             if (from > hits.getLength()) {   
  272.   
  273.                 // 如果起始的條目大于搜索到的條目   
  274.                 from = hits.getLength() - iPageSize;   
  275.                 doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);   
  276.                 detachedHits = hits.detach(from, hitsLength);   
  277.             } else if ((from + iPageSize) > hitsLength) {   
  278.   
  279.                 // 結束的條目大于搜索到的結果   
  280.                 doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);   
  281.                 detachedHits = hits.detach(from, hitsLength);   
  282.             } else {   
  283.   
  284.                 // 中間的頁碼,直接取出相應的條目   
  285.                 doProcessBeforeDetach(searchCommand, session, hits, from, iPageSize);   
  286.                 detachedHits = hits.detach(from, iPageSize);   
  287.             }   
  288.             doProcessAfterDetach(searchCommand, session, detachedHits);   
  289.             int numberOfPages = (int) Math.ceil((float) hitsLength / iPageSize);   
  290.             pages = new CompassSearchResults.Page[numberOfPages];   
  291.             for (int i = 0; i < pages.length; i++) {   
  292.                 pages[i] = new CompassSearchResults.Page();   
  293.                 pages[i].setFrom(i * iPageSize + 1);   
  294.                 pages[i].setSize(iPageSize);   
  295.                 pages[i].setTo((i + 1) * iPageSize);   
  296.                 if (from >= (pages[i].getFrom() - 1) && from < pages[i].getTo()) {   
  297.                     pages[i].setSelected(true);   
  298.                 } else {   
  299.                     pages[i].setSelected(false);   
  300.                 }   
  301.             }   
  302.             if (numberOfPages > 0) {   
  303.                 CompassSearchResults.Page lastPage = pages[numberOfPages - 1];   
  304.                 if (lastPage.getTo() > hitsLength) {   
  305.                     lastPage.setSize(hitsLength - lastPage.getFrom());   
  306.                     lastPage.setTo(hitsLength);   
  307.                 }   
  308.             }   
  309.         }   
  310.         time = System.currentTimeMillis() - time;   
  311.         CompassSearchResults searchResults = new CompassSearchResults(detachedHits.getHits(), time, pageSize);   
  312.         searchResults.setPages(pages);   
  313.         return searchResults;   
  314.     }   
  315.   
  316.     /**  
  317.      * 構建Lucene搜索器.  
  318.      */  
  319.     protected CompassQuery buildQuery(CompassSearchCommand searchCommand, CompassSession session) {   
  320.         CompassQuery query = session.queryBuilder().queryString(searchCommand.getQuery().trim()).toQuery();   
  321.   
  322.         if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {   
  323.             AdvancedSearchCommand advancedSearchCommand = (AdvancedSearchCommand) searchCommand;   
  324.   
  325.             for (CompassSort sort : advancedSearchCommand.getSortMap()) {   
  326.                 query.addSort(sort.getName(), sort.getType(), sort.getDirection());   
  327.             }   
  328.         }   
  329.         return query;   
  330.     }   
  331.   
  332.     /**  
  333.      * 在detach 之前,可以做一些操作。比如highlighting...  
  334.      *  
  335.      * @param from 需要注意的是,如果pageSize 沒有指定,那么這里傳入的參數為-1  
  336.      */  
  337.     protected void doProcessBeforeDetach(CompassSearchCommand searchCommand, CompassSession session, CompassHits hits,   
  338.                                          int from, int size) {   
  339.         if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {   
  340.             if (from < 0) {   
  341.                 from = 0;   
  342.                 size = hits.getLength();   
  343.             }   
  344.             String[] highlightFields = ((AdvancedSearchCommand) searchCommand).getHighlightFields();   
  345.   
  346.             if (highlightFields == null) {   
  347.                 return;   
  348.             }   
  349.   
  350.             // highlight fields   
  351.             for (int i = from; i < size; i++) {   
  352.                 for (String highlightField : highlightFields) {   
  353.                     hits.highlighter(i).fragment(highlightField);   
  354.                 }   
  355.             }   
  356.         }   
  357.     }   
  358.   
  359.     /**  
  360.      * An option to perform any type of processing before the hits are detached.  
  361.      */  
  362.     protected void doProcessAfterDetach(CompassSearchCommand searchCommand, CompassSession session,   
  363.                                         CompassDetachedHits hits) {   
  364.   
  365.     }   
  366.   
  367.     public void afterPropertiesSet() throws Exception {   
  368.         Assert.notNull(compass, "Must set compass property");   
  369.         this.compassTemplate = new CompassTemplate(compass);   
  370.     }   
  371.   
  372.     public Integer getPageSize() {   
  373.         return pageSize;   
  374.     }   
  375.   
  376.     public void setPageSize(Integer pageSize) {   
  377.         this.pageSize = pageSize;   
  378.     }   
  379.   
  380.     public void setCompass(Compass compass) {   
  381.         this.compass = compass;   
  382.     }   
  383.   
  384.     protected CompassTemplate getCompassTemplate() {   
  385.         return this.compassTemplate;   
  386.     }  

8、Model層

@SearchableId 聲明Document的id列;

@SearchableProperty 聲明要索引的field;

@SearchableComponent 聲明要索引的其他關聯對象。
Java代碼 復制代碼
  1. Article.java   
  2.   
  3. package com.mobilesoft.esales.model;   
  4.   
  5. import java.util.Date;   
  6.   
  7. import org.compass.annotations.Searchable;   
  8. import org.compass.annotations.SearchableId;   
  9. import org.compass.annotations.SearchableProperty;   
  10. import org.compass.core.CompassTemplate;   
  11.   
  12.   
  13. @Searchable  
  14. public class Article  implements java.io.Serializable {   
  15.   
  16.     @SearchableId  
  17.     private Integer id;   
  18.     @SearchableProperty(name="title")   
  19.     private String title;   
  20.     @SearchableProperty(name="author")   
  21.     private Integer author;   
  22.     @SearchableProperty(name="publishDate")   
  23.     private Date publishDate;   
  24.   
  25.     /** default constructor */  
  26.     public Article() {   
  27.     }   
  28.   
  29.     /** minimal constructor */  
  30.     public Article(String title, Date publishDate) {   
  31.         this.title = title;   
  32.         this.publishDate = publishDate;   
  33.     }   
  34.   
  35.     /** full constructor */  
  36.     public Article(String title, Integer author, Date publishDate) {   
  37.         this.title = title;   
  38.         this.author = author;   
  39.         this.publishDate = publishDate;   
  40.     }   
  41.   
  42.     public Integer getId() {   
  43.         return this.id;   
  44.     }   
  45.   
  46.     public void setId(Integer id) {   
  47.         this.id = id;   
  48.     }   
  49.   
  50.     public String getTitle() {   
  51.         return this.title;   
  52.     }   
  53.   
  54.     public void setTitle(String title) {   
  55.         this.title = title;   
  56.     }   
  57.   
  58.     public Integer getAuthor() {   
  59.         return this.author;   
  60.     }   
  61.   
  62.     public void setAuthor(Integer author) {   
  63.         this.author = author;   
  64.     }   
  65.   
  66.     public Date getPublishDate() {   
  67.         return this.publishDate;   
  68.     }   
  69.   
  70.     public void setPublishDate(Date publishDate) {   
  71.         this.publishDate = publishDate;   
  72.     }   
  73.   
  74. }   
  75.   
  76. Author.java   
  77.   
  78. package com.mobilesoft.esales.model;   
  79.   
  80. import org.compass.annotations.Searchable;   
  81. import org.compass.annotations.SearchableId;   
  82. import org.compass.annotations.SearchableProperty;   
  83. import org.compass.core.CompassTemplate;   
  84.   
  85.   
  86. @Searchable  
  87. public class Author  implements java.io.Serializable {   
  88.   
  89.   
  90.     @SearchableId  
  91.     private Integer id;   
  92.     @SearchableProperty(name="username")   
  93.     private String username;   
  94.     private String password;   
  95.     @SearchableProperty(name="age")   
  96.     private Short age;   
  97.   
  98.     public Author() {   
  99.     }   
  100.   
  101.     /** minimal constructor */  
  102.     public Author(String username, String password) {   
  103.         this.username = username;   
  104.         this.password = password;   
  105.     }   
  106.   
  107.     /** full constructor */  
  108.     public Author(String username, String password, Short age) {   
  109.         this.username = username;   
  110.         this.password = password;   
  111.         this.age = age;   
  112.     }   
  113.   
  114.     // Property accessors   
  115.   
  116.     public Integer getId() {   
  117.         return this.id;   
  118.     }   
  119.   
  120.     public void setId(Integer id) {   
  121.         this.id = id;   
  122.     }   
  123.   
  124.     public String getUsername() {   
  125.         return this.username;   
  126.     }   
  127.   
  128.     public void setUsername(String username) {   
  129.         this.username = username;   
  130.     }   
  131.   
  132.     public String getPassword() {   
  133.         return this.password;   
  134.     }   
  135.   
  136.     public void setPassword(String password) {   
  137.         this.password = password;   
  138.     }   
  139.   
  140.     public Short getAge() {   
  141.         return this.age;   
  142.     }   
  143.   
  144.     public void setAge(Short age) {   
  145.         this.age = age;   
  146.     }   
  147.   
  148.   
  149. }  

9、DAO層

ArticleDAO.java和AuthorDAO.java省略

直接用MyEclipse生成的,沒有什么特別的。
10、參考文檔

http://www.compass-project.org/docs/1.2.1/reference/html/

The Compass Framework Search made easy.pdf

Compass TSSJS Europe 06.pdf

Hello World Tutorial

InfoQ:  Compass: Integrate Search into your apps

InfoQ: Compass: Simplifying and Extending Lucene to Provide Google-like Search

InfoQ: Compass: 在你的應用中集成搜索功能

Compass 指南

http://www.kimchy.org/



本文摘自:http://chuanliang2007.spaces.live.com/blog/cns!E5B7AB2851A4C9D2!389.entry