Spring的Hibernate Search全文检索功能



Spring的Hibernate Search全文检索功能

一个项目正好运用了Hibernate Search 的全文检索功能,所以就研究了一下。通过一小段的简单研究终于在项目俩面运用了起来。所以来简单记录一下。希望能对大家有所帮助。
首先来几个概念吧。:)

hibernate Search 是什么?
我的理解 hibernate search 就是hibernate里面一个基于lucence开发的全文检索功能模块。
Hibernate Search项目的主要特性包含以下几个方面:
1.Lucene集成——作为强大高效的检索引擎,Lucene的美名早已久经考验了;

2.数据的自动插入和更新——当一个对象通过Hibernate添加或更新时,索引也会相应进行透明的更新;

3.支持众多复杂的搜索方式——可快速的使用通配符进行搜索,以及多关键词全文检索(multi-word text searches)和近似或同义词搜索(approximation/synonym searches),或根据相关性排列搜索结果;

4.搜索集群(Search Clustering)——Hibernate Search提供了内建搜索集群解决方案,其中包括一个基于JMS的异步查询和索引系统;

5.对Lucene API接口的直接调用——如果用户打算处理某些特别复杂的问题,可以在查询中直接使用Lucene提供的API接口;

6.对Lucene的自动管理——Hibernate Search可以管理并优化Lucene的索引,并且非常高效地使用Lucene的API接口。

Hibernate Search相关的Annotation主要有三个:

@Indexed 标识需要进行索引的对象, 属性 : index 指定索引文件的路径
@DocumentId 用于标示实体类中的唯一的属性保存在索引文件中,是当进行全文检索时可以这个唯一的属性来区分索引中其他实体对象,一般使用实体类中的主键属性
@Field 标注在类的get属性上,标识一个索引的Field 属性 : index 指定是否索引,与Lucene相同
store 指定是否索引,与Lucene相同
name 指定Field的name,默认为类属性的名称
analyzer 指定分析器
另外@IndexedEmbedded 与 @ContainedIn 用于关联类之间的索引
@IndexedEmbedded有两个属性,一个prefix指定关联的前缀,一个depth指定关联的深度
在maven的pom.xml文件中加入以下几个依赖包

 

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.4.0.GA</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.5.2</version>
</dependency>
<!– hibernate framework end –>

<!– lucene start –>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>2.4.0</version>
</dependency>

 

在spring的配置文件中加入如下hibernate searhch 的配置

<property name=”hibernateProperties”>
<props>
<prop key=”hibernate.connection.autocommit”>true</prop>
<prop key=”hibernate.show_sql”>true</prop>
<prop key=”hibernate.dialect”>org.hibernate.dialect.OracleDialect
</prop>
<prop key=”hibernate.query.factory_class”> org.hibernate.hql.ast.ASTQueryTranslatorFactory
</prop>
<prop key=”hibernate.search.default.directory_provider”> org.hibernate.search.store.FSDirectoryProvider
</prop>
<prop key=”hibernate.search.worker.batch_size”> 1000</prop>
<prop key=”hibernate.search.default.indexBase”> d:/index</prop>
<prop key=”hibernate.search.analyzer”> org.apache.lucene.analysis.cjk.CJKAnalyzer</prop>
</props>
</property>
<property name=”eventListeners”>
<map>
<entry key=”post-update”>
<bean class=”org.hibernate.search.event.FullTextIndexEventListener” />
</entry>
<entry key=”post-insert”>
<bean class=”org.hibernate.search.event.FullTextIndexEventListener” />
</entry>
<entry key=”post-delete”>
<bean class=”org.hibernate.search.event.FullTextIndexEventListener” />
</entry>
</map>
</property>

以上配置包括了建立索引批量提交设置,索引文件生成的位置,还有建立索引的时候的词法分析器的制定。

还有就是事件监听的配置。上面的配置目的是针对所以对象的增删该查自动监听并建立索引文件。

下面简单建立一个pojo的 索引配置

@Entity
@Table(name=”B2C_COMMODITY”)
@Indexed(index=”newindexs/commodity”)
@SuppressWarnings(“serial”)
public class B2cCommodity implements Java.io.Serializable {

private String id;
private String commodityNo;
private String commodityName;
private String introduction;
private String keyword;
private BigDecimal sellPrice;

@Id
@Column(name=”id”,length=32)
@GeneratedValue (generator=”UUDI_KEY”)
@GenericGenerator(name=”UUDI_KEY”, strategy=”uuid.hex”)
public String getId() {
return this.id;
}

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


@Transient
@Field(index=Index.TOKENIZED)
public String getContent() {
return this.commodityName+” “+this.introduction+” “+this.keyword;
}

@Transient
@Field(index=Index.UN_TOKENIZED)
public String getIndexedSellPrice() {//用于索引的商品售价
String tmp = this.getSellPrice().toString();
return SearchUtil.makeIndexedPrice(tmp);
}

@Column(name=”COMMODITY_NAME”, nullable=false, length=300)
public String getCommodityName() {
return this.commodityName;
}

public void setCommodityName(String commodityName) {
this.commodityName = commodityName;
}

@Column(name = “INTRODUCTION”, length = 9000000)
public String getIntroduction() {
return this.introduction;
}

public void setIntroduction(String introduction) {
this.introduction = introduction;
}

@Column(name=”SELL_PRICE”, nullable=false, precision=18)
public BigDecimal getSellPrice() {
return this.sellPrice;
}

public void setSellPrice(BigDecimal sellPrice) {
this.sellPrice = sellPrice;
}
@Column(name=”KEYWORD”, length=200)
public String getKeyword() {
return this.keyword;
}

public void setKeyword(String keyword) {
this.keyword = keyword;
}

}

 

看到上面的配置大家可能有些疑问 ,我没有在原有的数据库列上面直接建立索引,而是将原有的多个列建立在了一个 content 列上。

这是为了解决多条件符合查询的时候的一个简便方法,例如我输入关键字 “男装” 我既想搜索 标题 又想所有 内容,还想搜索 介绍。那么我将这三个字段组合在一起建立一个content索引。在搜索的时候直接搜索 content 字段就可以了。而不用再麻烦 hibernate search 的 MultiFieldQueryParser 出入多个条件查询多个索引列了。只要查询一个就可以了。

上面的配置就设置基本完成了

 

 

/**
* 测试建立索引,配置了事件监听后根据增删改查自动更新索引文件
* @return
*/
@RequestMapping(“/view/testMakeIndex.htm”)
public String testMakeIndex(){
B2cCommodity obj = new B2cCommodity();
obj.setCommodityNo(“123456789″);
obj.setCommodityName(“测试”);
obj.setIntroduction(“测试lucence程序”);
obj.setStoreId(“00000121″);
obj.setSellPrice(new BigDecimal(“10″));
obj.setOnShelf(“1″);
obj.setCommodityNumber(100);
obj.setFreezeStatus(’1′);
obj.setDelFlag(’1′);
obj.setRegRegionId(“000001″);
obj.setCreateBy(“ylm”);
obj.setCreateTime(new Date());
commodityDAO.saveCommodity(obj);
return “/view/testIndexSearch.htm”;
}

只要跟平常运用hibernate pojo 操作增删该查数据库一样 就可以自动更新索引文件了

查询所以文件测试代码如下:

 

/**
* 测试查询索引文件
* @return
* @throws ParseException
*/
@RequestMapping(“/view/testIndexSearch.htm”)
public String testIndexSearch() throws ParseException{

FullTextSession fullTextSession = Search.createFullTextSession(commodityDAO.getSessionFactory().openSession());
MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {“content” }, new StandardAnalyzer());
org.apache.lucene.search.Query luceneQuery = parser.parse(“测试”);
FullTextQuery query = fullTextSession.createFullTextQuery(luceneQuery,B2cCommodity.class);
// 添加分页查询
query.setFirstResult(0);
query.setMaxResults(100);
// 对查询结果按name进行排序
List result = query.list();
return “/view/testIndexSearch.htm”;
}

以上是一个简单的配置和测试的例子