详解Java的Hibernate框架中的搜索工具的运用
hibernate提供了全文索引功能,非常棒,这里简要介绍下它的用法,
1.在pom.xml引入包依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search-orm</artifactId>
<version>${hibernate-search.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-phonetic</artifactId>
<version>${lucene.version}</version>
</dependency>
hibernate配置searchindex保存路径
<beanid="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
destroy-method="destroy">
<propertyname="dataSource"ref="poolingDataSource"/>
<propertyname="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<propertyname="hibernateProperties">
<props>
<propkey="hibernate.dialect">${hibernate.dialect}</prop>
<!--Booleanscanbeeasilyusedinexpressionsby
declaringHQLquerysubstitutionsinHibernateconfiguration-->
<propkey="hibernate.query.substitutions">true'Y',false'N'</prop>
<!--http://ehcache.org/documentation/integrations/hibernate-->
<!--http://www.tutorialspoint.com/hibernate/hibernate_caching.htm-->
<propkey="hibernate.cache.use_second_level_cache">true</prop>
<!--org.hibernate.cache.ehcache.EhCacheRegionFactory-->
<propkey="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<!--hibernate只会缓存使用load()方法获得的单个持久化对象,如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()
等方法获得的数据结果集的话,就需要设置hibernate.cache.use_query_cachetrue-->
<propkey="hibernate.cache.use_query_cache">true</prop>
<propkey="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
<!--HibernateSearchindexdirectory-->
***<propkey="hibernate.search.default.indexBase">indexes/</prop>***
</props>
</property>
</bean>
对需要搜索的类加上IndexedAnnotation,然后对类中可以被搜索的字段加上@FieldAnnotation,通常Enum字段不需要Analyzer进行词法分析,其他字段则需要,对于不需要Projection(返回部分字段)的情况下,不需要在index中存储实际数据。可以通过AnalyzerDef来定义不同的词法分析器以及对于的特殊词过滤器
@Indexed
@AnalyzerDef(
name="enTopicAnalyzer",
charFilters={
@CharFilterDef(factory=HTMLStripCharFilterFactory.class)
},
tokenizer=@TokenizerDef(factory=StandardTokenizerFactory.class),
filters={
@TokenFilterDef(factory=StandardFilterFactory.class),
@TokenFilterDef(factory=StopFilterFactory.class),
@TokenFilterDef(factory=PhoneticFilterFactory.class,
params={
@Parameter(name="encoder",value="DoubleMetaphone")
}),
@TokenFilterDef(factory=SnowballPorterFilterFactory.class,
params={
@Parameter(name="language",value="English")
})
}
)
publicclassTopic{
......
@Field(index=Index.YES,analyze=Analyze.YES,store=Store.NO)
@Analyzer(definition="enTopicAnalyzer")
privateStringtitle;
......
@Field(index=Index.YES,analyze=Analyze.YES,store=Store.NO)
@Analyzer(definition="enTopicAnalyzer")
privateStringcontent;
......
@Enumerated(EnumType.STRING)
@Field(index=Index.YES,analyze=Analyze.NO,store=Store.NO,bridge=@FieldBridge(impl=EnumBridge.class))
privateTopicStatusstatus;
...
}
通过代码对已有数据创建index
ApplicationContextcontext=newClassPathXmlApplicationContext("spring-resources.xml");
SessionFactorysessionFactory=(SessionFactory)context.getBean("sessionFactory");
Sessionsess=sessionFactory.openSession();
FullTextSessionfullTextSession=Search.getFullTextSession(sess);
try{
fullTextSession.createIndexer().startAndWait();
}catch(InterruptedExceptione){
LOG.error(e.getMessage(),e);
}finally{
fullTextSession.close();
}
((AbstractApplicationContext)context).close();
创建查询fulltextsession,按照query条件获取结果
FullTextSessionfullTextSession=Search
.getFullTextSession(getSession());
QueryBuilderqueryBuilder=fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Show.class).get();
org.apache.lucene.search.QueryluceneQuery=null;
luceneQuery=queryBuilder.keyword()//.wildcard()
.onFields("title","content").matching(query.getKeyword())
//.matching("*"+query.getKeyword()+"*")
.createQuery();
FullTextQueryhibernateQuery=fullTextSession.createFullTextQuery(
luceneQuery,Show.class);
returnhibernateQuery.list();
note:
1.在一次测试过程中,修改了valueobject,添加了新的index,忘记了rebuildIndex,结果unittest没问题,生成环境就出错了。
2.搜索还不是很强大,比如搜索测,含有测试的结果可能就搜索不出来
中文词法分析
hibernatesearch底层使用Lucene,所以Lucene可以使用的中文分词,hibernatesearch都可以用来支持中文词法分析,比较常用的词法分析器包括paoding,IKAnalyzer,mmseg4j等等。具体可以参考分词分析最近分析。hibernatesearch默认的分词器是org.apache.lucene.analysis.standard.StandardAnalyzer,中文按字分词,显然不符合我们的需求。
这里介绍一下如何在hibernate中配置中文分词,选择的是Lucene自带的中文分词–。使用可以通过3种方式,一种是在hibernate的配置文件设置词法分析方法,另外一种是在每个需要被搜索的类中定义分词方法,最后一种是对单个字段配置。这里介绍下前2种的配置方式。
hibernate配置方式:
<propertyname="hibernate.search.analyzer">org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer</property>
被搜索类配置中文分词:
@Indexed @Analyzer(impl=SmartChineseAnalyzer.class)
同时需要在maven中引入相关包依赖
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>${lucene.version}</version>
</dependency>
多条件查询
hibernatesearch可以通过多组合条件来实现多条件查询,这里简单介绍一下多条件查询的一个实践。
如果只是单个条件查询,那么这个查询就可以很简单
luceneQuery=queryBuilder.keyword().onFields("title","content").matching(query.getKeyword()).createQuery()
如果是多条件并查询,那么就需要使用到MustJoin,如果是多条件或查询,就需要使用shouldJoin,这里举个MustJoin的例子
//musttrue
MustJunctionterm=queryBuilder.bool().must(queryBuilder.keyword()
.onFields("title","content").matching(query.getKeyword()).createQuery());
//mustfalse
term.must(queryBuilder.keyword()
.onField("status").matching(query.getExcludeStatus()).createQuery()).not();
完整例子:
privateFullTextQueryfindByKeywordQuery(TopicQueryquery){
FullTextSessionfullTextSession=Search
.getFullTextSession(getSession());
QueryBuilderqueryBuilder=fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Topic.class).get();
org.apache.lucene.search.QueryluceneQuery=null;
if(null==query.getStatus()&&null==query.getUsername()&&null==query.getExcludeStatus()){
luceneQuery=queryBuilder.keyword()//.wildcard()
.onFields("title","content").matching(query.getKeyword())
//.matching("*"+query.getKeyword()+"*")
.createQuery();
if(LOG.isDebugEnabled()){
LOG.debug("createcleankeywordsearchquery:"+luceneQuery.toString());
}
}else{
MustJunctionterm=queryBuilder.bool().must(queryBuilder.keyword()
.onFields("title","content").matching(query.getKeyword()).createQuery());
if(null!=query.getStatus()){
term.must(queryBuilder.keyword()
//.wildcard()
.onField("status")
.matching(query.getStatus()).createQuery());
}
if(null!=query.getExcludeStatus()){
term.must(queryBuilder.keyword()
.onField("status")
.matching(query.getExcludeStatus()).createQuery()).not();
}
if(null!=query.getUsername()){
term.must(queryBuilder.keyword()
//.wildcard()
.onField("owner.username")
.ignoreFieldBridge()
.matching(query.getUsername()).createQuery());
}
luceneQuery=term.createQuery();
if(LOG.isDebugEnabled()){
LOG.debug("createcomplicatedkeywordsearchquery:"+luceneQuery.toString());
}
}
//BooleanQuery
FullTextQueryhibernateQuery=fullTextSession.createFullTextQuery(
luceneQuery,Topic.class);
returnhibernateQuery;
}