COMPASS+spring 构建自己的搜索引擎

news/2024/7/7 2:56:15
在新架构中打算选择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、测试用例

从测试用例讲起比较容易把关系理清楚,不然一堆术语和概念很让人晕乎。

import org.apache.log4j.Logger;
import java.util.Date;

import junit.framework.TestCase;

import org.compass.core.Compass;
import org.compass.core.CompassDetachedHits;
import org.compass.core.CompassHit;
import org.compass.core.CompassHits;
import org.compass.core.CompassSession;
import org.compass.core.CompassTemplate;
import org.compass.core.CompassTransaction;
import org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mobilesoft.esales.dao.hibernate.ArticleDAO;
import com.mobilesoft.esales.dao.hibernate.AuthorDAO;
import com.mobilesoft.esales.model.Article;
import com.mobilesoft.esales.model.Author;
import com.mobilesoft.framework.search.service.CompassSearchService;

/**
* Compass服务使用的测试用例
*
* @author liangchuan@mobile-soft.cn
*
*/

public class TestCompass extends TestCase {

private static final Logger logger = Logger.getLogger(TestCompass.class);

private static ClassPathXmlApplicationContext context = null;
private static CompassTemplate ct;

static {
context = new ClassPathXmlApplicationContext(new String[] {
"applicationContext.xml", "applicationContext-resources.xml",
"applicationContext-dao.xml", "applicationContext-service.xml",
"applicationContext-compass.xml" });
ct = (CompassTemplate) context.getBean("compassTemplate");
}

protected void setUp() throws Exception {

}

/**
* 插入测试数据
*/
public void testInsert() {

ArticleDAO articleDao = (ArticleDAO) context.getBean("articleDAO");
AuthorDAO authorDao = (AuthorDAO) context.getBean("authorDAO");
Article article = new Article();
Author author = new Author();
author.setAge((short) 27);
author.setUsername("liangchuan");
author.setPassword("liangchuan");
article.setTitle("Compass Test");
article.setPublishDate(new Date());
article.setAuthor(1);
authorDao.save(author);
articleDao.save(article);
ct.save(article);
ct.save(author);
}

/**
* 用于测试使用CompassTransaction事务方式
*/
public void testTransactionalFind() {

Compass compass = ct.getCompass();
CompassSession session = compass.openSession();
CompassTransaction tx = null;
try {
tx = session.beginTransaction();
CompassHits hits = session.find("Compass*");

logger.error("testTransactionalFind() - CompassHits hits="
+ hits.getLength());
for (int i = 0; i < hits.getLength(); i++) {
Object hit = hits.data(i);
if (hit instanceof Article) {
Article item = (Article) hit;
logger.error("testTransactionalFind() - article hits="
+ item.getTitle());
} else if (hit instanceof Author) {
Author item = (Author) hit;
logger.error("testTransactionalFind() - author hits="
+ item.getUsername());
} else {
logger.error("testTransactionalFind() - error hits=");
}
}
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}
}

/**
* 用于演示CompassDetachedHits的使用。
* 由于CompassTempalte得到的结果集必须在transactionalcontext中才能使用,
* 因此必须使用CompassDetachedHits方式测试CompassDetachedHits方式
*/
public void testDetachedFind() {

// 由于CompassTempalte得到的结果集必须在transactional
// context中才能使用,因此必须使用CompassDetachedHits方式
// 测试CompassDetachedHits方式
CompassDetachedHits hits = ct.findWithDetach("Compass*");

logger.error("testDetachedFind() - CompassHits hits="
+ hits.getLength());
for (int i = 0; i < hits.getLength(); i++) {
Object hit = hits.data(i);
if (hit instanceof Article) {
Article item = (Article) hit;
logger.error("testDetachedFind() - article hits="
+ item.getTitle());
} else if (hit instanceof Author) {
Author item = (Author) hit;
logger.error("testDetachedFind() - author hits="
+ item.getUsername());
} else {
logger.error("testDetachedFind() - error hits=");
}
}

}

/**
* 用于演示com.mobilesoft.framework.search.service.CompassSearchService的使用
*
*/
class CompassSearch extends CompassSearchService{
CompassSearch(){
Compass compass = ct.getCompass();
CompassSession session = compass.openSession();
CompassTransaction tx = null;

try {
tx = session.beginTransaction();
CompassSearchCommand command = new CompassSearchCommand();
command.setQuery("Compass");
CompassSearchResults results= performSearch(command,session);
logger.error("CompassSearch() - CompassHit TotalHits value=" +results.getTotalHits());

for (int i = 0; i < results.getHits().length; i++) {
CompassHit hits=results.getHits()[i];
Object hit=hits.getData();
logger.error("CompassSearch() - CompassHit hit=" + hit); //$NON-NLS-1$

if (hit instanceof Article) {
Article item = (Article) hit;
logger.error("testCompassSearchService() - article hits="
+ item.getTitle());
} else if (hit instanceof Author) {
Author item = (Author) hit;
logger.error("testCompassSearchService() - author hits="
+ item.getUsername());
} else {
logger.error("testCompassSearchService() - error hits=");
}

tx.commit();
}
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}

}

}
public void testCompassSearchService() {
new CompassSearch();
}

protected void tearDown() throws Exception {
}
}

5、配置文件
applicationContext-compass.xml
applicationContext-dao.xml、applicationContext-service.xml、applicationContext-resources.xml等略去。

<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans default-lazy-init="true">
<bean id="compassTemplate" class="org.compass.core.CompassTemplate">
<property name="compass" ref="compass"/>
</bean>
<bean id="annotationConfiguration"
class="org.compass.annotations.config.CompassAnnotationsConfiguration">
</bean>
<bean id="compass" class="org.compass.spring.LocalCompassBean">
<property name="classMappings">
<list>
<value>com.mobilesoft.esales.model.Article</value>
<value>com.mobilesoft.esales.model.Author</value>
</list>
</property>
<property name="compassConfiguration" ref="annotationConfiguration"/>

<property name="compassSettings">
<props>
<prop key="compass.engine.connection"> file://compass </prop>
<prop key="compass.transaction.factory">
org.compass.spring.transaction.SpringSyncTransactionFactory
</prop>
<prop
key="compass.engine.highlighter.default.formatter.simple.pre">
<![CDATA[<font color="red"><b>]]>
</prop>
<prop
key="compass.engine.highlighter.default.formatter.simple.post">
<![CDATA[</b></font>]]>
</prop>
</props>
</property>

<property name="transactionManager" ref="transactionManager"/>
</bean>

<bean id="hibernateGpsDevice"
class="org.compass.gps.device.hibernate.HibernateGpsDevice">
<property name="name">
<value>hibernateDevice</value>
</property>
<property name="sessionFactory" ref="sessionFactory"/>
<property name="mirrorDataChanges">
<value>true</value>
</property>
</bean>
<bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps"
init-method="start" destroy-method="stop">
<property name="compass" ref="compass"/>
<property name="gpsDevices">
<list>
<bean
class="org.compass.spring.device.SpringSyncTransactionGpsDeviceWrapper">
<property name="gpsDevice" ref="hibernateGpsDevice"/>
</bean>
</list>
</property>
</bean>
<bean id="compassSearchService" class="com.mobilesoft.framework.search.service.CompassSearchService">
<property name="compass" ref="compass"/>
<property name="pageSize" value="15"/>
</bean>

<!-- 定时重建索引(利用quartz)或随Spring ApplicationContext启动而重建索引 -->
<bean id="compassIndexBuilder" class="com.mobilesoft.framework.search.service.CompassIndexBuilder" lazy-init="false">
<property name="compassGps" ref="compassGps"/>
<property name="buildIndex" value="false"/>
<property name="lazyTime" value="10"/>
</bean>


</beans>

6、Service层(参考了SpringSide实现)
AdvancedSearchCommand.java
package com.mobilesoft.framework.search.service;

import java.util.HashSet;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.compass.core.CompassQuery.SortDirection;
import org.compass.core.CompassQuery.SortPropertyType;
import org.compass.core.support.search.CompassSearchCommand;

import org.springframework.util.Assert;

public class AdvancedSearchCommand extends CompassSearchCommand {

/**
* 封装基于Compass 的排序参数.
*/
class CompassSort {

private String name;

private SortPropertyType type;

private SortDirection direction;

public CompassSort() {
}

public CompassSort(String sortParamName, String paramType,
boolean isAscend) {
Assert.isTrue(StringUtils.isNotBlank(sortParamName));
setName(sortParamName);

if ("int".equalsIgnoreCase(paramType)) {
setType(SortPropertyType.INT);
} else if ("float".equalsIgnoreCase(paramType)) {
setType(SortPropertyType.FLOAT);
} else if ("string".equalsIgnoreCase(paramType)) {
setType(SortPropertyType.STRING);
} else {
setType(SortPropertyType.AUTO);
}

if (isAscend) {
setDirection(SortDirection.AUTO);
} else {
setDirection(SortDirection.REVERSE);
}
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public SortPropertyType getType() {
return type;
}

public void setType(SortPropertyType type) {
this.type = type;
}

public SortDirection getDirection() {
return direction;
}

public void setDirection(SortDirection direction) {
this.direction = direction;
}
}

/**
* 搜索结果排序表.
*/
private Set<CompassSort> sortMap = new HashSet<CompassSort>();

private String[] highlightFields;

/**
* @param paramType 现定义了三种类型: int string 以及 float。<br>
* 除去这三种外,其他会被自动定义为SortPropertyType.AUTO 具体的可见{@link org.compass.core.CompassQuery.SortPropertyType}
* @param isAscend 顺序还是倒序排序
* @see org.compass.core.CompassQuery.SortPropertyType#AUTO
* @see org.compass.core.CompassQuery.SortPropertyType#INT
* @see org.compass.core.CompassQuery.SortPropertyType#STRING
* @see org.compass.core.CompassQuery.SortPropertyType#FLOAT
* @see org.compass.core.CompassQuery.SortDirection#AUTO
* @see org.compass.core.CompassQuery.SortDirection#REVERSE
*/
public void addSort(String sortParamName, String paramType, boolean isAscend) {
this.sortMap.add(new CompassSort(sortParamName, paramType, isAscend));
}

public Set<CompassSort> getSortMap() {
return sortMap;
}

public void setSortMap(Set<CompassSort> sortMap) {
this.sortMap = sortMap;
}

public String[] getHighlightFields() {
return highlightFields;
}

public void setHighlightFields(String[] highlightFields) {
this.highlightFields = highlightFields;
}
}

CompassIndexBuilder.java

package com.mobilesoft.framework.search.service;

import org.apache.log4j.Logger;
import org.compass.gps.CompassGps;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

/**
* 通过quartz定时调度定时重建索引或自动随Spring ApplicationContext启动而重建索引的Builder.
* 会启动后延时数秒新开线程调用compassGps.index()函数.
* 默认会在Web应用每次启动时重建索引,可以设置buildIndex属性为false来禁止此功能.
* 也可以不用本Builder, 编写手动调用compassGps.index()的代码.
*
*/
public class CompassIndexBuilder implements InitializingBean {

private static final Logger log = Logger.getLogger(CompassIndexBuilder.class);

// 是否需要建立索引,可被设置为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);

log.info("begin compass index...");
long beginTime = System.currentTimeMillis();
// 重建索引.
// 如果compass实体中定义的索引文件已存在,索引过程中会建立临时索引,
// 索引完成后再进行覆盖.
compassGps.index();
long costTime = System.currentTimeMillis() - beginTime;
log.info("compss index finished.");
log.info("costed " + costTime + " milliseconds");
} catch (InterruptedException e) {
// simply proceed
}
}
};

/**
* 实现<code>InitializingBean</code>接口,在完成注入后调用启动索引线程.
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
if (buildIndex) {
Assert.notNull(compassGps, "CompassIndexBuilder not set CompassGps yet.");
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;
}
}

CompassSearchService.java

package com.mobilesoft.framework.search.service;

import org.compass.core.Compass;
import org.compass.core.CompassCallback;
import org.compass.core.CompassDetachedHits;
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 org.compass.core.support.search.CompassSearchCommand;
import org.compass.core.support.search.CompassSearchResults;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import com.mobilesoft.framework.search.service.AdvancedSearchCommand.CompassSort;
/**
* 仿照 {@link org.compass.spring.web.mvc.CompassSearchController}
* 中的代码,构建了一个Service,方便不使用Spring MVC
*
* @see org.compass.spring.web.mvc.CompassSearchController
* @see org.compass.spring.web.mvc.AbstractCompassCommandController
*/
public class CompassSearchService implements InitializingBean {

//每页显示的条目数量
private Integer pageSize = 15;

private Compass compass;

private CompassTemplate compassTemplate;

/**
* 公开的搜索接口,返回匹配的搜索结果,与
* {@link org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,
*javax.servlet.http.HttpServletResponse,Object,org.springframework.validation.BindException) 处理相似
*
* @see org.compass.spring.web.mvc.CompassSearchController#handle(javax.servlet.http.HttpServletRequest,
*javax.servlet.http.HttpServletResponse,java.lang.Object,org.springframework.validation.BindException)
*/
public CompassSearchResults search(final CompassSearchCommand command) {
return (CompassSearchResults) getCompassTemplate().execute(
CompassTransaction.TransactionIsolation.READ_ONLY_READ_COMMITTED, new CompassCallback() {
public Object doInCompass(CompassSession session) {
return performSearch(command, session);
}
});
}

/**
* 通过此方法调用搜索引擎,进行结果匹配搜索.
*
* @see org.compass.spring.web.mvc.CompassSearchController#performSearch(
*org.compass.spring.web.mvc.CompassSearchCommand,org.compass.core.CompassSession)
*/
protected CompassSearchResults performSearch(CompassSearchCommand searchCommand, CompassSession session) {
long time = System.currentTimeMillis();
CompassQuery query = buildQuery(searchCommand, session);
CompassHits hits = query.hits();
CompassDetachedHits detachedHits;
CompassSearchResults.Page[] pages = null;
if (pageSize == null) {
doProcessBeforeDetach(searchCommand, session, hits, -1, -1);
detachedHits = hits.detach();
} else {
int iPageSize = pageSize;
int page = 0;
int hitsLength = hits.getLength();
if (searchCommand.getPage() != null) {
page = searchCommand.getPage();
}
int from = page * iPageSize;

if (from > hits.getLength()) {

// 如果起始的条目大于搜索到的条目
from = hits.getLength() - iPageSize;
doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);
detachedHits = hits.detach(from, hitsLength);
} else if ((from + iPageSize) > hitsLength) {

// 结束的条目大于搜索到的结果
doProcessBeforeDetach(searchCommand, session, hits, from, hitsLength);
detachedHits = hits.detach(from, hitsLength);
} else {

// 中间的页码,直接取出相应的条目
doProcessBeforeDetach(searchCommand, session, hits, from, iPageSize);
detachedHits = hits.detach(from, iPageSize);
}
doProcessAfterDetach(searchCommand, session, detachedHits);
int numberOfPages = (int) Math.ceil((float) hitsLength / iPageSize);
pages = new CompassSearchResults.Page[numberOfPages];
for (int i = 0; i < pages.length; i++) {
pages[i] = new CompassSearchResults.Page();
pages[i].setFrom(i * iPageSize + 1);
pages[i].setSize(iPageSize);
pages[i].setTo((i + 1) * iPageSize);
if (from >= (pages[i].getFrom() - 1) && from < pages[i].getTo()) {
pages[i].setSelected(true);
} else {
pages[i].setSelected(false);
}
}
if (numberOfPages > 0) {
CompassSearchResults.Page lastPage = pages[numberOfPages - 1];
if (lastPage.getTo() > hitsLength) {
lastPage.setSize(hitsLength - lastPage.getFrom());
lastPage.setTo(hitsLength);
}
}
}
time = System.currentTimeMillis() - time;
CompassSearchResults searchResults = new CompassSearchResults(detachedHits.getHits(), time, pageSize);
searchResults.setPages(pages);
return searchResults;
}

/**
* 构建Lucene搜索器.
*/
protected CompassQuery buildQuery(CompassSearchCommand searchCommand, CompassSession session) {
CompassQuery query = session.queryBuilder().queryString(searchCommand.getQuery().trim()).toQuery();

if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {
AdvancedSearchCommand advancedSearchCommand = (AdvancedSearchCommand) searchCommand;

for (CompassSort sort : advancedSearchCommand.getSortMap()) {
query.addSort(sort.getName(), sort.getType(), sort.getDirection());
}
}
return query;
}

/**
* 在detach 之前,可以做一些操作。比如highlighting...
*
* @param from 需要注意的是,如果pageSize 没有指定,那么这里传入的参数为-1
*/
protected void doProcessBeforeDetach(CompassSearchCommand searchCommand, CompassSession session, CompassHits hits,
int from, int size) {
if (AdvancedSearchCommand.class.isAssignableFrom(searchCommand.getClass())) {
if (from < 0) {
from = 0;
size = hits.getLength();
}
String[] highlightFields = ((AdvancedSearchCommand) searchCommand).getHighlightFields();

if (highlightFields == null) {
return;
}

// highlight fields
for (int i = from; i < size; i++) {
for (String highlightField : highlightFields) {
hits.highlighter(i).fragment(highlightField);
}
}
}
}

/**
* An option to perform any type of processing before the hits are detached.
*/
protected void doProcessAfterDetach(CompassSearchCommand searchCommand, CompassSession session,
CompassDetachedHits hits) {

}

public void afterPropertiesSet() throws Exception {
Assert.notNull(compass, "Must set compass property");
this.compassTemplate = new CompassTemplate(compass);
}

public Integer getPageSize() {
return pageSize;
}

public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}

public void setCompass(Compass compass) {
this.compass = compass;
}

protected CompassTemplate getCompassTemplate() {
return this.compassTemplate;
}


8、Model层

@SearchableId 声明Document的id列;

@SearchableProperty 声明要索引的field;

@SearchableComponent 声明要索引的其他关联对象。

Article.java

package com.mobilesoft.esales.model;

import java.util.Date;

import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.compass.core.CompassTemplate;


@Searchable
public class Article implements java.io.Serializable {

@SearchableId
private Integer id;
@SearchableProperty(name="title")
private String title;
@SearchableProperty(name="author")
private Integer author;
@SearchableProperty(name="publishDate")
private Date publishDate;

/** default constructor */
public Article() {
}

/** minimal constructor */
public Article(String title, Date publishDate) {
this.title = title;
this.publishDate = publishDate;
}

/** full constructor */
public Article(String title, Integer author, Date publishDate) {
this.title = title;
this.author = author;
this.publishDate = publishDate;
}

public Integer getId() {
return this.id;
}

public void setId(Integer id) {
this.id = id;
}

public String getTitle() {
return this.title;
}

public void setTitle(String title) {
this.title = title;
}

public Integer getAuthor() {
return this.author;
}

public void setAuthor(Integer author) {
this.author = author;
}

public Date getPublishDate() {
return this.publishDate;
}

public void setPublishDate(Date publishDate) {
this.publishDate = publishDate;
}

}

Author.java

package com.mobilesoft.esales.model;

import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.compass.core.CompassTemplate;


@Searchable
public class Author implements java.io.Serializable {


@SearchableId
private Integer id;
@SearchableProperty(name="username")
private String username;
private String password;
@SearchableProperty(name="age")
private Short age;

public Author() {
}

/** minimal constructor */
public Author(String username, String password) {
this.username = username;
this.password = password;
}

/** full constructor */
public Author(String username, String password, Short age) {
this.username = username;
this.password = password;
this.age = age;
}

// Property accessors

public Integer getId() {
return this.id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return this.username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return this.password;
}

public void setPassword(String password) {
this.password = password;
}

public Short getAge() {
return this.age;
}

public void setAge(Short age) {
this.age = age;
}


}
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://www.niftyadmin.cn/n/3484857.html

相关文章

java ssm数据接口签名加密_SSM 登录 JS和Java互通加解密(AES)

最近新项目上线&#xff0c;被客户扫描出【已解密的登录请求】&#xff0c;原因是我们在登录请求时没有对用户名或密码进行加密请求(悲催&#xff0c;以前的项目没有进行加密也没出啥问题)&#xff0c;本着客户至上的原则&#xff0c;我们在下次投产时把这个功能给加上。在网上…

java 锁机制有什么用_java的锁机制

1.什么是死锁线程死锁描述的是这样一种情况&#xff1a;多个线程同时被阻塞&#xff0c;它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞&#xff0c;因此程序不可能正常终止。public class DeadLockDemo {private static Object resource1 new Object()…

XFire WebService开发快速起步

环境&#xff1a;XFire-1.2.6JDK1.5MyEclipse 6.5Tomcat-5.5.27Windows XP Professional简体中文版软件下载地址&#xff1a;[url]http://repository.codehaus.org/org/codehaus/xfire/xfire-distribution/1.2.6/xfire-distribution-1.2.6.zip[/url][url]http://apache.mirror.…

python如何求一个列表里所有数字的和_在Python中汇总一个数字列表

我有一个数字列表&#xff0c;如[1,2,3,4,5...] &#xff0c;我想计算(12)/2和第二&#xff0c; (23)/2和第三&#xff0c; (34)/2 &#xff0c;依此类推。 我怎样才能做到这一点&#xff1f;我想将第一个数字与第二个数字相加并除以2&#xff0c;然后将第二个数字与第三个相加…

vue知识掌握(一)

vue项目的搭建 本文章主要讲述vue项目的搭建&#xff0c;在搭建vue项目前需要准备一些材料。 1.node安装 npm&#xff1a;在你安装node的时候一般node已经自带了npm&#xff0c;所以忽略。 webpack&#xff1a;npm install webpack -g 检验安装是否成功的办法&#xff1a; 检验…

java 包装类方法总结_Java基础教程:Java包装类概念及应用

概述Java提供了两个类型系统&#xff0c;基本类型与引用类型&#xff0c;使用基本类型在于效率&#xff0c;然而很多情况&#xff0c;会创建对象使用&#xff0c;因为对象可以做更多的功能&#xff0c;如果想要我们的基本类型像对象一样操作&#xff0c;就可以使用基本类型对应…

Spring Bean 初始化过程

Life类 Java代码 package com.open.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory…

java处理分析经济学_什么?程序员还要了解经济学?!

【编者按】本文作者为常年混迹于 SitePoint JavaScript 论坛的 Web 开发者 James Hibbard&#xff0c;主要介绍针对开发新手提出了一些学习建议。文章系国内 ITOM 管理平台 OneAPM 编译呈现。在近期的一次采访中&#xff0c; StackOverflow联合创始人Joel Spolsky建议&#xff…