Compass是一个强大的,事务的,高性能的对象/搜索引擎映射(OSEM:object/search engine mapping)与一个Java持久层框架.Compass包括:
* 搜索引擎抽象层(使用Lucene搜索引荐),
* OSEM (Object/Search Engine Mapping) 支持,
* 事务管理,
* 类似于Google的简单关键字查询语言,
* 可扩展与模块化的框架,
* 简单的API.
如果你需要做站内搜索引擎,而且项目里用到了hibernate,那用compass是你的最佳选择。
本文用到compass2.14和IK中文分词包,另外将会使用注解来实现
废话不说,先给出关键的实现代码
假如现在有个需求,需要根据关键字搜索出文章,
- @Searchable (alias= "article" )
- public class Article {
- private Long ID; // 标识ID
- private String content; // 正文
- private String title; // 文章标题
- private Date createTime; // 创建时间
- @SearchableId
- public Long getID() {
- return ID;
- }
- public void setID(Long id) {
- ID = id;
- }
- @SearchableProperty (index = Index.TOKENIZED, store = Store.YES)
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this .content = content;
- }
- @SearchableProperty (index = Index.TOKENIZED, store = Store.YES)
- public String getTitle() {
- return title;
- }
- public void setTitle(String title) {
- this .title = title;
- }
- @SearchableProperty (index = Index.TOKENIZED, store = Store.YES)
- public Date getCreateTime() {
- return createTime;
- }
- public void setCreateTime(Date createTime) {
- this .createTime = createTime;
- }
- }
@Searchable(alias="article")
public class Article {
private Long ID; // 标识ID
private String content; // 正文
private String title; // 文章标题
private Date createTime; // 创建时间
@SearchableId
public Long getID() {
return ID;
}
public void setID(Long id) {
ID = id;
}
@SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
简单解释一下:
@Searchable(alias="article")表示这个是可以搜索实体,别人为article.
@SearchableId 这个是实体搜索的标识ID,和hibernate里的概念差不多,用来区分索引文件里的实体索引。
@SearchableProperty(index = Index.TOKENIZED, store = Store.YES) 表示这个属性存入索引文件,而且是在分词后在存入.
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import org.compass.core.Compass;
- import org.compass.core.CompassCallback;
- import org.compass.core.CompassException;
- import org.compass.core.CompassHighlighter;
- import org.compass.core.CompassHits;
- import org.compass.core.CompassQuery;
- import org.compass.core.CompassSession;
- import org.compass.core.CompassTemplate;
- import org.compass.core.CompassTransaction;
- import cn.rgcenter.entity.Article;
- public class SearchServiceBean {
- private Compass compass;
- /** 索引查询 * */
- public Map find( final String keywords, final String type, final int start,
- final int end) {
- CompassTemplate ct = new CompassTemplate(compass);
- return ct.execute( new CompassCallback<Map>() {
- public Map doInCompass(CompassSession session)
- throws CompassException {
- List result = new ArrayList();
- int totalSize = 0 ;
- Map container = new HashMap();
- CompassQuery query = session.queryBuilder().queryString(
- keywords).toQuery();
- CompassHits hits = query.setAliases(type).hits();
- totalSize = hits.length();
- container.put("size" , totalSize);
- int max = 0 ;
- if (end < hits.length()) {
- max = end;
- } else {
- max = hits.length();
- }
- if (type.equals( "article" )){
- for ( int i = start; i < max; i++) {
- Article article = (Article) hits.data(i);
- String title = hits.highlighter(i).fragment("title" );
- if (title != null ) {
- article.setTitle(title);
- }
- String content = hits.highlighter(i).setTextTokenizer(
- CompassHighlighter.TextTokenizer.AUTO)
- .fragment("content" );
- if (content != null ) {
- article.setContent(content);
- }
- result.add(article);
- }
- }
- container.put("result" , result);
- return container;
- }
- });
- }
- public Compass getCompass() {
- return compass;
- }
- public void setCompass(Compass compass) {
- this .compass = compass;
- }
- }
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.compass.core.Compass;
import org.compass.core.CompassCallback;
import org.compass.core.CompassException;
import org.compass.core.CompassHighlighter;
import org.compass.core.CompassHits;
import org.compass.core.CompassQuery;
import org.compass.core.CompassSession;
import org.compass.core.CompassTemplate;
import org.compass.core.CompassTransaction;
import cn.rgcenter.entity.Article;
public class SearchServiceBean {
private Compass compass;
/** 索引查询 * */
public Map find(final String keywords, final String type, final int start,
final int end) {
CompassTemplate ct = new CompassTemplate(compass);
return ct.execute(new CompassCallback<Map>() {
public Map doInCompass(CompassSession session)
throws CompassException {
List result = new ArrayList();
int totalSize = 0;
Map container = new HashMap();
CompassQuery query = session.queryBuilder().queryString(
keywords).toQuery();
CompassHits hits = query.setAliases(type).hits();
totalSize = hits.length();
container.put("size", totalSize);
int max = 0;
if (end < hits.length()) {
max = end;
} else {
max = hits.length();
}
if(type.equals("article")){
for (int i = start; i < max; i++) {
Article article = (Article) hits.data(i);
String title = hits.highlighter(i).fragment("title");
if (title != null) {
article.setTitle(title);
}
String content = hits.highlighter(i).setTextTokenizer(
CompassHighlighter.TextTokenizer.AUTO)
.fragment("content");
if (content != null) {
article.setContent(content);
}
result.add(article);
}
}
container.put("result", result);
return container;
}
});
}
public Compass getCompass() {
return compass;
}
public void setCompass(Compass compass) {
this.compass = compass;
}
}
索引的查询主要是根据传过来的参数,关键字keywords,是搜索的关键字,类型type,先判断是不是要搜索文章,因为一般来说,页面的搜索引擎不单单只搜索文章一个实体.
至于int 和end是为了分页取出部分结果的.
String title = hits.highlighter(i).fragment("title");这段是检索titile这个属性有没有出现搜索的关键字,有就将它高亮 (其实就是在关键字前后加个<font></font>的html标记设置颜色,等下可以看到在配置文件里可以自由设置高亮的颜 色).
String content = hits.highlighter(i).setTextTokenizer(
CompassHighlighter.TextTokenizer.AUTO)
.fragment("content");
这段代码和上面的title具有一样的一样的功能,另外还多了个很重要的功能,自动选择正文中最匹配关键字的内容中的一部分输出。因为很多时候一篇文章几千字,我们只想显示有关键字的那部分的摘要,这时候这个功能就很方便.
这之后还要写一个建立索引的服务类,让服务器启动的时候或者定时重建索引.
- import org.compass.gps.CompassGps;
- import org.springframework.beans.factory.InitializingBean;
- public class CompassIndexBuilder implements InitializingBean {
- // 是否需要建立索引,可被设置为false使本Builder失效.
- private boolean buildIndex = false ;
- // 索引操作线程延时启动的时间,单位为秒
- private int lazyTime = 10 ;
- // Compass封装
- private CompassGps compassGps;
- // 索引线程
- private Thread indexThread = new Thread() {
- @Override
- public void run() {
- try {
- Thread.sleep(lazyTime * 1000 );
- System.out.println("begin compass index..." );
- long beginTime = System.currentTimeMillis();
- // 重建索引.
- // 如果compass实体中定义的索引文件已存在,索引过程中会建立临时索引,
- // 索引完成后再进行覆盖.
- compassGps.index();
- long costTime = System.currentTimeMillis() - beginTime;
- System.out.println("compss index finished." );
- System.out.println("costed " + costTime + " milliseconds" );
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- /**
- * 实现<code>InitializingBean</code>接口,在完成注入后调用启动索引线程.
- */
- public void afterPropertiesSet() throws Exception {
- if (buildIndex) {
- indexThread.setDaemon(true );
- indexThread.setName("Compass Indexer" );
- indexThread.start();
- }
- }
- public void setBuildIndex( boolean buildIndex) {
- this .buildIndex = buildIndex;
- }
- public void setLazyTime( int lazyTime) {
- this .lazyTime = lazyTime;
- }
- public void setCompassGps(CompassGps compassGps) {
- this .compassGps = compassGps;
- }
- }
import org.compass.gps.CompassGps;
import org.springframework.beans.factory.InitializingBean;
public class CompassIndexBuilder implements InitializingBean {
// 是否需要建立索引,可被设置为false使本Builder失效.
private boolean buildIndex = false;
// 索引操作线程延时启动的时间,单位为秒
private int lazyTime = 10;
// Compass封装
private CompassGps compassGps;
// 索引线程
private Thread indexThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(lazyTime * 1000);
System.out.println("begin compass index...");
long beginTime = System.currentTimeMillis();
// 重建索引.
// 如果compass实体中定义的索引文件已存在,索引过程中会建立临时索引,
// 索引完成后再进行覆盖.
compassGps.index();
long costTime = System.currentTimeMillis() - beginTime;
System.out.println("compss index finished.");
System.out.println("costed " + costTime + " milliseconds");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
/**
* 实现<code>InitializingBean</code>接口,在完成注入后调用启动索引线程.
*/
public void afterPropertiesSet() throws Exception {
if (buildIndex) {
indexThread.setDaemon(true);
indexThread.setName("Compass Indexer");
indexThread.start();
}
}
public void setBuildIndex(boolean buildIndex) {
this.buildIndex = buildIndex;
}
public void setLazyTime(int lazyTime) {
this.lazyTime = lazyTime;
}
public void setCompassGps(CompassGps compassGps) {
this.compassGps = compassGps;
}
}
实现了spring的InitializingBean接口,让服务器启动,bean初始化的时候去建立索引
剩下的就是配置文件了
- <?xml version= "1.0" encoding= "UTF-8" ?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns:tx="http://www.springframework.org/schema/tx"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
- <bean id="annotationConfiguration"
- class = "org.compass.annotations.config.CompassAnnotationsConfiguration" >
- </bean>
- <!-- compass Bean -->
- <bean id="compass" class = "org.compass.spring.LocalCompassBean" >
- <property name="compassConfiguration"
- ref="annotationConfiguration" />
- <!-- 数据索引存储位置 -->
- <property name="connection" >
- <value>/compass/indexes</value>
- </property>
- <property name="transactionManager" ref= "transactionManager" />
- <property name="compassSettings" >
- <props>
- <prop key="compass.transaction.factory" >
- org.compass.spring.transaction.SpringSyncTransactionFactory
- </prop>
- <prop
- key="compass.engine.highlighter.default.formatter.simple.pre" >
- <![CDATA[<span style='background-color:yellow;color:red;' >]]>
- </prop>
- <prop
- key="compass.engine.highlighter.default.formatter.simple.post" >
- <![CDATA[</span>]]>
- </prop>
- <!--定义分词器-->
- <prop
- key="compass.engine.analyzer.default.type" >
- org.mira.lucene.analysis.IK_CAnalyzer
- </prop>
- </props>
- </property>
- <property name="classMappings" >
- <list>
- <value>cn.rgcenter.entity.Article</value>
- </list>
- </property>
- </bean>
- <!--hibernate驱动-->
- <bean id="hibernateGpsDevice"
- class = "org.compass.spring.device.hibernate.dep.SpringHibernate3GpsDevice" >
- <property name="name" >
- <value>hibernateDevice</value>
- </property>
- <property name="sessionFactory" ref= "sessionFactory" />
- <property name="mirrorDataChanges" >
- <value>true </value>
- </property>
- </bean>
- <!-- 数据库中的数据变化后同步更新索引 -->
- <bean id="hibernateGps"
- class = "org.compass.gps.impl.SingleCompassGps" init-method= "start"
- destroy-method="stop" >
- <property name="compass" >
- <ref bean="compass" />
- </property>
- <property name="gpsDevices" >
- <list>
- <bean
- class = "org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper" >
- <property name="gpsDevice" ref= "hibernateGpsDevice" />
- </bean>
- </list>
- </property>
- </bean>
- <!-- compass模版 -->
- <bean id="compassTemplate"
- class = "org.compass.core.CompassTemplate" >
- <property name="compass" ref= "compass" />
- </bean>
- <!-- 定时重建索引(利用quartz)或随Spring ApplicationContext启动而重建索引 -->
- <bean id="compassIndexBuilder"
- class = "cn.rgcenter.compass.service.CompassIndexBuilder"
- lazy-init="false" >
- <property name="compassGps" ref= "hibernateGps" />
- <property name="buildIndex" value= "true" />
- <property name="lazyTime" value= "5" />
- </bean>
- <!-- 搜索引擎服务类 -->
- <bean id="searchService"
- class = "cn.rgcenter.compass.service.SearchServiceBean" >
- <property name="compass" >
- <ref bean="compass" />
- </property>
- </bean>
- <!-- 搜索引擎Action -->
- <bean id="searchAction" class = "cn.rgcenter.action.SearchAction" >
- <property name="searchService" >
- <ref bean="searchService" />
- </property>
- </bean>
- </beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="annotationConfiguration"
class="org.compass.annotations.config.CompassAnnotationsConfiguration">
</bean>
<!-- compass Bean -->
<bean id="compass" class="org.compass.spring.LocalCompassBean">
<property name="compassConfiguration"
ref="annotationConfiguration" />
<!-- 数据索引存储位置 -->
<property name="connection">
<value>/compass/indexes</value>
</property>
<property name="transactionManager" ref="transactionManager" />
<property name="compassSettings">
<props>
<prop key="compass.transaction.factory">
org.compass.spring.transaction.SpringSyncTransactionFactory
</prop>
<prop
key="compass.engine.highlighter.default.formatter.simple.pre">
<![CDATA[<span style='background-color:yellow;color:red;'>]]>
</prop>
<prop
key="compass.engine.highlighter.default.formatter.simple.post">
<![CDATA[</span>]]>
</prop>
<!--定义分词器-->
<prop
key="compass.engine.analyzer.default.type">
org.mira.lucene.analysis.IK_CAnalyzer
</prop>
</props>
</property>
<property name="classMappings">
<list>
<value>cn.rgcenter.entity.Article</value>
</list>
</property>
</bean>
<!--hibernate驱动-->
<bean id="hibernateGpsDevice"
class="org.compass.spring.device.hibernate.dep.SpringHibernate3GpsDevice">
<property name="name">
<value>hibernateDevice</value>
</property>
<property name="sessionFactory" ref="sessionFactory" />
<property name="mirrorDataChanges">
<value>true</value>
</property>
</bean>
<!-- 数据库中的数据变化后同步更新索引 -->
<bean id="hibernateGps"
class="org.compass.gps.impl.SingleCompassGps" init-method="start"
destroy-method="stop">
<property name="compass">
<ref bean="compass" />
</property>
<property name="gpsDevices">
<list>
<bean
class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
<property name="gpsDevice" ref="hibernateGpsDevice" />
</bean>
</list>
</property>
</bean>
<!-- compass模版 -->
<bean id="compassTemplate"
class="org.compass.core.CompassTemplate">
<property name="compass" ref="compass" />
</bean>
<!-- 定时重建索引(利用quartz)或随Spring ApplicationContext启动而重建索引 -->
<bean id="compassIndexBuilder"
class="cn.rgcenter.compass.service.CompassIndexBuilder"
lazy-init="false">
<property name="compassGps" ref="hibernateGps" />
<property name="buildIndex" value="true" />
<property name="lazyTime" value="5" />
</bean>
<!-- 搜索引擎服务类 -->
<bean id="searchService"
class="cn.rgcenter.compass.service.SearchServiceBean">
<property name="compass">
<ref bean="compass" />
</property>
</bean>
<!-- 搜索引擎Action -->
<bean id="searchAction" class="cn.rgcenter.action.SearchAction">
<property name="searchService">
<ref bean="searchService" />
</property>
</bean>
</beans>
至于action就不列出代码了,很简单了,只需要传搜索方法的那几个参数过去就可以了.
最后看一下搜索结果示例图:
