Hibernate Search(基于version3.4)--第四章Mapping entities to the index structure

本文详细介绍了Hibernate Search如何将实体类映射到索引结构,包括@Indexed、@Field、@NumericField等注解的使用,以及如何处理关联对象和嵌入对象的索引。此外,还提到了Boosting和自定义Bridge策略,以实现更灵活的索引映射。
摘要由CSDN通过智能技术生成

原文:http://sin90lzc.iteye.com/blog/1106258

Mapping entities to the index structure

 

4.1. 映射一个实体(Mapping an entity)

在第一章中,你已经知道了建立实体索引的所有元信息是通过注解描述的,所以不需要xml的映射文件。但是你依然可以使用Hibernate的映射文件来配置基本的Hibernate映射,但Hibernate Search的配置只能通过注解来表达。

 

4.1.1.基本映射(Basic mapping)

我们先介绍最常使用的注解。

 

4.1.1.1. @Indexed

首先,我们必须要声明一个持久化类是可索引的。这可以由注解@Indexed来注明,所有没有@Indexed的实体将忽略indexing步骤。

 

Example 4.1. Making a class indexable with @Indexed

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. public class Essay {  
  4.     ...  
  5. }  

你也可以设置@Indexed的index属性来指定index的名称。默认是使用持久化类的全限定名。For more information see Section 3.2, “Directory configuration”.

 

4.1.1.2. @Field

对于每个持久化类的属性域,你都可以声明它们怎样去建立索引。如果属性域上没有注解描述,那么该属性域将忽略indexing。@Field声明一个属性域可以被索引,@Field的属性可以用于配置具体indexing的过程。

  • name:保存在Lucene中Field的名字。默认为属性名。
  • store:属性域的值是否要保存到Lucene Index中。Store.YES,花费更大的硬盘空间但允许projection(see Section 5.1.3.5, “Projection”);Store.COMPRESS,会消耗更多的CPU资源; Store.NO,不保存属性域的值,这是默认值。
  • index:定义属性域怎样被建立索引和哪些类型的信息会被保存。Index.NO,不会被建立索引,所以该域不能被搜 索;Index.TOKENIZED,使用解析器(ananlyzer)去解析该属性域;Index.UN_TOKENIZED,不需要经过解析器解析, 整个属性域的值作为索引值;Index.NO_NORMS,不保存normalization数据(其实是Lucene中域的Boost值)。默认值是 TOKENIZED。

Tip :解析一个日期型属性域是没必要的。

Tip :用作排序的域是不能被解析的。

  • termVector:如何描述term-frequency对。这个属性指定是否保存documents的term vectors。默认值是TermVector.NO。

 

termVector的可选值如下表所示:

ValueDefinition
TermVector.YES保存每个文档的term vector。这种方式会产生两个同步的数组,一个数组包含了document的terms,另一个数组包含对应term的频率
TermVector.NO不何在term vector。
TermVector.WITH_OFFSETS不仅保存term vector,还保存term的offset信息(包含term的开始和结束的位置信息)。
TermVector.WITH_POSITIONS不仅保存term vector,还保存term的position信息(包含term在document中出现的位置信息)
TermVector.WITH_POSITION_OFFSETSTermVector.WITH_POSITIONS和TermVector.WITH_OFFSETS的组合。

 

  • indexNullAs:默认情况下,null值是会被忽略,不会被索引的。然而,使用indexNullAs可以指定一个字符串代替null 值。indexNullAs默认值是Field.DO_NOT_INDEX_NULL,那么null值并不会被索引。也可以修改这个值为 Field.DEFAULT_NULL_TOKEN去指定一个字符串代替null token。这个字符串通过属性hibernate.search.default_null_token配置。但是如果使用了 indexNullAs=Field.DEFAULT_NULL_TOKEN而又没有配置 hibernate.search.default_null_token属性,字符串"_null_"会用来代替null token。

Note :当使用了indexNullAs属性,你必须使用相同的token去查询null值。当然,要使用该功能最好是与un-tokenized fields一齐使用。

 

Warning :如果实现了FieldBridge 或 TwoWayFieldBridge,indexing和null值的维护的责任就只能交给开发者了。(see JavaDocs of
LuceneOptions.indexNullAs())

 

4.1.1.3. @NumericField

@NumericField必须要与@Field一起使用。该注解的使用范围与@Field,@DocumentId是一样的。它用于标注 Integer, Long, Float和Double类型的属性域。在index time,该值会用Trie结构[http://en.wikipedia.org/wiki/Trie]来创建索引。当一个属性域用numeric field来创建索引,那么它就能使用高效的范围查询(range query)和排序(sorting)了。@NumericField注解接受下表所示的属性:

ValueDefinition
forField可选的。指定对应的@Field的名称。只有在属性域上有超过一个@Field声明时,才会强制要求添加该属性。
precisionStep可选的。改变Trie结构的精度。值越小,消耗更多的硬盘空间,但能提高范围查询和排序。值越大,则相反。默认值为4

 

 

4.1.1.4. @Id

最后,一个实体的id属性域是一个特别的属性,Hibernate Search用这个属性来确保index与实体的对应关系。默认情况下,id属性应该被保存和不能被tokenized。可以使用注解 @DocumentId来标志一个属性为index id。如果某个属性域上有@Id注解,也可以省略@DocumentId。

 

Example 4.2. Specifying indexed properties

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. public class Essay {  
  4.     ...  
  5.     @Id  
  6.     @DocumentId  
  7.     public Long getId() { return id; }  
  8.       
  9.     @Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES)  
  10.     public String getSummary() { return summary; }  
  11.       
  12.     @Lob  
  13.     @Field(index=Index.TOKENIZED)  
  14.     public String getText() { return text; }  
  15.       
  16.     @Field   
  17.     @NumericField( precisionStep = 6)  
  18.     public float getGrade() { return grade; }  
  19. }  

 

Example 4.2, “Specifying indexed properties” 定义了一个index包含了4个field:id , Abstract,text和grade。grade被注解为Numeric和一个比默认值稍微大点precisionStep。

 

 

4.1.2. 多次映射同一个属性(Mapping properties multiple times)

有时候可能需要映射同一个属性多次,这种方式使用了一个有点不同的indexing策略。假设有下面这样的情景:某个人想要查询某个属性域是否包含 某个token并要按该属性域排序时,那么他必须index这个属性域两次,因为查询属性域,属性域必须TOKENIZED,而对属性域排序又必须 UN_TOKENIZED。通过@Fields注解可以达到这个目的。

 

Example 4.3. Using @Fields to map a property multiple times

Java代码   收藏代码
  1. @Entity  
  2. @Indexed(index = "Book" )  
  3. public class Book {  
  4.     @Fields( {  
  5.             @Field(index = Index.TOKENIZED),  
  6.             @Field(name = "summary_forSort", index = Index.UN_TOKENIZED, store = Store.YES)  
  7.             } )  
  8.     public String getSummary() {  
  9.         return summary;  
  10.     }  
  11.     ...  
  12. }  

 

Example 4.3, “Using @Fields to map a property multiple times”中,summary属性域被索引了两次,一次是以summary作为索引文档的域名,以tokenized的方式建立索引,另一次是以 summary_forSort作为索引文档的域名,以untokenized的方式建立索引。在@Fields中,@Field支持两个有用的属性:

  • analyzer:定义@Analyzer注解针对具体的lucene field而不是实体内中的属性域。
  • bridge:定义@FieldBridge注解针对具体的lucene field而不是实体内中的属性域。

关于@Analyzer和@FieldBridge,下面的章节有更详细的说明。

 

 

4.1.3. 嵌入和关联对象(Embedded and associated objects)

关联对象和嵌入的对象都可以被索引作为根实体索引的一部分。当实体内中的属性域为关联对象时,这就会显得非常的有用。

 

假设在Example 4.4, “Indexing associations”例子中,它的目的是想要返回city为Atlanta的place,那么在Lucene query parser language中,它会被解析为address.city:Atlanta。例子中会建立名为Place的index,该index会包含这些 field: address.id, address.street和address.city,这些field都可以用于搜索。

 

Example 4.4. Indexing associations

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. public class Place {  
  4.     @Id  
  5.     @GeneratedValue  
  6.     @DocumentId  
  7.     private Long id;  
  8.       
  9.     @Field( index = Index.TOKENIZED )  
  10.     private String name;  
  11.       
  12.     @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )  
  13.     @IndexedEmbedded  
  14.     private Address address;  
  15.     ....  
  16. }  
  17. @Entity  
  18. public class Address {  
  19.     @Id  
  20.     @GeneratedValue  
  21.     private Long id;  
  22.       
  23.     @Field(index=Index.TOKENIZED)  
  24.     private String street;  
  25.       
  26.     @Field(index=Index.TOKENIZED)  
  27.     private String city;  
  28.       
  29.     @ContainedIn  
  30.     @OneToMany(mappedBy="address")  
  31.     private Set<Place> places;  
  32.     ...  
  33. }  

 

需要注意的是,当使用@IndexedEmbedded技术嵌入对象时,Lucene的index数据会变得不正常。因为Hibernate Search需要有能力知道Place对象和Address对象的改变来使index保持最新状态。为了确保当Address改变后,Place的 Lucene document也会随之更新,需要在双向关联的另一边添加注解@ContainedIn(在本例中是在Address.places属性域上添加注 解)。

 

Tip :@ContainedIn is only useful on associations pointing to entities as opposed to embedded (collection of) objects.

 

让我们看一个更复杂的例子 Example 4.5, “Nested usage of @IndexedEmbedded and @ContainedIn”.

 

Example 4.5. Nested usage of @IndexedEmbedded and @ContainedIn

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. public class Place {  
  4.     @Id  
  5.     @GeneratedValue  
  6.     @DocumentId  
  7.     private Long id;  
  8.       
  9.     @Field( index = Index.TOKENIZED )  
  10.     private String name;  
  11.       
  12.     @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } )  
  13.     @IndexedEmbedded  
  14.     private Address address;  
  15.     ....  
  16. }  
  17.   
  18. @Entity  
  19. public class Address {  
  20.     @Id  
  21.     @GeneratedValue  
  22.     private Long id;  
  23.       
  24.     @Field(index=Index.TOKENIZED)  
  25.     private String street;  
  26.       
  27.     @Field(index=Index.TOKENIZED)  
  28.     private String city;  
  29.       
  30.     @IndexedEmbedded(depth = 1, prefix = "ownedBy_")  
  31.     private Owner ownedBy;  
  32.       
  33.     @ContainedIn  
  34.     @OneToMany(mappedBy="address")  
  35.     private Set<Place> places;  
  36.     ...  
  37. }  
  38.   
  39. @Embeddable  
  40. public class Owner {  
  41.     @Field(index = Index.TOKENIZED)  
  42.     private String name;  
  43.    ...  
  44. }  

 

正如你所看到的一样,所有的标注了@*ToMany, @*ToOne 和 @Embedded 的属性域都可以注解为@IndexedEmbedded,相关联对象的属性域就会被添加到根实体的index中去。Example 4.5, “Nested usage of @IndexedEmbedded and @ContainedIn”生成的index将包含以下的 field:id,name,address.street,address.city,address.ownedBy_name。根据传统的 object navigation convention,关联对象的属性field name使用默认的前缀propertyName. 。也可以在 @IndexedEmbedded标注中添加prefix属性来自定义前缀(如例子中的ownedBy属性)。

 

当对象是一个循环类依赖的结构的时候(例如Owner存在一个引用指向Place),就应该指定depth 属性。那么只有当嵌入的属性到达指定的depth时(或达到了对象图界限时),Hibernate Search才会停止indexing。在例子Example 4.5中,因为depth为1,因此在Owner类中标注了@IndexedEmbedded的属性域都会被忽略。

 

使用@IndexedEmbedded标识对象关联,也是

 

 

使用Lucene查询语法也能表达@IndexedEmbedded标注的对象关联关系:

  • 返回place名字中包含有JBoss 并且address的city为Atlanta
    Lucene查询语句代码   收藏代码
    1. +name:jboss +address.city:atlanta  
     
  •  返回place名字中包含有JBoss 而且owner的名字包含有Joe
    Lucene查询语句代码   收藏代码
    1. +name:jboss +address.orderBy_name:joe  
     

Note :一个被关联的对象自身也可以是@Indexed,但并不是必需的。

 

当@IndexedEmbedded标注了一个实体,那么关联就必须是双向的,并且另一端的引用必须标注为@ContainedIn(如前面的例子)。如果不这样的话,当关联对象改变的时候,Hibernate Search将不会跟着更新根实体的index。

 

有时候,标注了@IndexedEmbedded的对象的类型并不是具体的对象类型(例如接口和它们的实现类)。这时候,就需要使用targetElement属性来指定具体的对象类型。

 

Example 4.6. Using the targetElement property of @IndexedEmbedded

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. public class Address {  
  4.     @Id  
  5.     @GeneratedValue  
  6.     @DocumentId  
  7.     private Long id;  
  8.       
  9.     @Field(index= Index.TOKENIZED)  
  10.     private String street;  
  11.       
  12.     @IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class)  
  13.     @Target(Owner.class)  
  14.     private Person ownedBy;  
  15.       
  16.     ...  
  17. }  
  18.   
  19. @Embeddable  
  20. public class Owner implements Person { ... }  

 

 

 

4.2. 比重(Boosting)

Lucene中有一个概念叫做Boosting。Boosting用于定义某些document或field相对于其他document或 field的比重。Lucene在index time的boosting与search time的boosting是不一样的。下面的章节介绍如何在Hibernate Search中完成index time的boosting。

 

4.2.1. Static index time boosting

可以通过类级别或属性级别的注解@Boost定义一个static boost值。先可以通过@Field.boost来指定。

 

Example 4.7. Different ways of using @Boost

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. @Boost(1.7f)  
  4. public class Essay {  
  5.     ...  
  6.     
  7.     @Id  
  8.     @DocumentId  
  9.     public Long getId() { return id; }  
  10.       
  11.     @Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES, boost=@Boost(2f))  
  12.     @Boost(1.5f)  
  13.     public String getSummary() { return summary; }  
  14.       
  15.     @Lob  
  16.     @Field(index=Index.TOKENIZED, boost=@Boost(1.2f))  
  17.     public String getText() { return text; }  
  18.       
  19.     @Field  
  20.     public String getISBN() { return isbn; }  
  21. }  

 

 

4.2.2. Dynamic index time boosting

在有些情况下,boost因子可能依赖于实际对象的状态。在这种情况下,你就要自定义一个BoostStrategy的实现,并使用@DynamicBoost来注册该实现。

 

Example 4.8. Dynamic boost examle

Java代码   收藏代码
  1. public enum PersonType {  
  2.     NORMAL,  
  3.     VIP  
  4. }  
  5.   
  6. @Entity  
  7. @Indexed  
  8. @DynamicBoost(impl = VIPBoostStrategy.class)  
  9. public class Person {  
  10.     private PersonType type;     
  11.       
  12.     // ....  
  13. }  
  14.   
  15. public class VIPBoostStrategy implements BoostStrategy {  
  16.     public float defineBoost(Object value) {  
  17.         Person person = ( Person ) value;  
  18.         if ( person.getType().equals( PersonType.VIP ) ) {  
  19.             return 2.0f;  
  20.         }  
  21.         else {  
  22.             return 1.0f;  
  23.         }  
  24.     }  
  25. }  

 

在Example 4.8, “Dynamic boost examle”中,dynamic boost定义在类级别,指定了BoostStrategy的实现类VIPBoostStrategy。@DynamicBoost可以标注在类级别或属 性级别上,这依赖于你想要传递整个对象还是属性域的值给BoostStrategy的defineBoost 方法。在上例中,VIP比normal用户的比重大2倍。

 

Note :BoostStrategy的实现必须定义一个无参构造函数。

 

 

4.3. 解析(Analysis)

Analysis是一个把文本转换成terms(索引词,搜索词)的过程,是全文搜索引擎中非常重要的一个功能。Lucene使用Analyzer来管理这个过程。下面的章节将介绍如何在Hibernate Search中配置Analyzer。

 

4.3.1. Default analyzer and analyzer by class

默认的analyzer 通过hibernate.search.analyzer属性配置。该属性的默认值为org.apache.lucene.analysis.standard.StandardAnalyzer。

 

你也可以在实体类、属性域、甚至在@Field(对于单属性域多个field的情形来说很有用)中配置Analyzer。

 

Example 4.9. Different ways of using @Analyzer

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. @Analyzer(impl = EntityAnalyzer.class)  
  4. public class MyEntity {  
  5.     @Id  
  6.     @GeneratedValue  
  7.     @DocumentId  
  8.   private Integer id;  
  9.       
  10.     @Field(index = Index.TOKENIZED)  
  11.     private String name;  
  12.       
  13.     @Field(index = Index.TOKENIZED)  
  14.     @Analyzer(impl = PropertyAnalyzer.class)  
  15.     private String summary;  
  16.       
  17.     @Field(index = Index.TOKENIZED, analyzer = @Analyzer(impl = FieldAnalyzer.class)  
  18.     private String body;  
  19.   
  20.   
  21.     ...  
  22. }  

 

在这个例子里面,MyEntity属性域的解析工作由EntityAnalyzer完成,除了summary 和 body属性域,它们分别由PropertyAnalyzer,FieldAnalyzer来解析。

 

Caution :在同一个实体 中混合多个不同的analyzer并不是一个好的实践。它会使得建立查询变得更复杂和使得结果很难预料(对于新手来说),特别是当你使用一个 QueryParser来创建Query时,QueryParser在整个查询过程中需要使用相同的analyzer。根据经验所得,不管是 indexing time还是search time,所有的field都应该使用相同的analyzer。

 

 

4.3.2. analyzer的命名(Named analyzers)

Analyzer可以变得非常复杂。基于这个原因,HibernateSearch中提出一个概念analyzer definition。analyzer definition可以由@Analyzer注解重用。analyzer definition由以下几部分组成:

 

  • a name:analyzer definition的唯一的引用名。
  • a list of char filters:每个char filter在划分分词(tokenization)之前对输入源(字符集)进行预处理。Char filter可以添加、修改、除出字符。最常用的一个用途是字符的规范化。
  • a tokenizer:负责把输入源划分成一个一个的词语。
  • a list of filters:负责添加,删除,修改由tokenizer解析出来的词语。

Analyzer的工作被划分成一系列的任务,这些任务由一系列的char filter,一个tokenizer和一系列的filter来完成,每个char filter,filter,tokenizer都能完成一项具体的任务,把char filter,filter,tokenizer组合起来就是一个analyzer了(像玩积木一样)。简而言之,char filters完成输入字符的预处理,Tokenizer把文本流划分成一个一个的词语,再经由TokenFilter对生成的词语进行过滤。 Hibernate Search利用了Solr analyzer framework来提供这方面的支持。

 

Note :有些analyzer和filter需要额外的依赖包。例如使用 snowball stemmer解析器,需要添加 lucene-snowball的jar包,对于 PhoneticFilterFactory,还需要 commons-codec [ http://commons.apache.org/codec ] 的jar包 。Hibernate Search提代了这些jar包在它的distribution的lib/optional目录下。查看Table 4.2,“Example of available tokenizers” 和 Table 4.3, “Examples of available filters”找到更多analyzer或filter所需的依赖包。在之前的Hibernate Search版本3.3.0.Beta2,当用到 analyzer definition framework时, 需要添加Solr的依赖 org.apache.solr:solr-core。当然,如果你使用Maven来管理依赖,这就没必要了,因为所有的Solr依赖被定义在 artifact为 org.hibernate:hibernate-search-analyzers中。只需要pom中添加下面的配置

Xml代码   收藏代码
  1. <dependency>  
  2.    <groupId>org.hibernate</groupId>  
  3.    <artifactId>hibernate-search-analyzers</artifactId>  
  4.    <version>3.4.0.Final</version>  
  5. <dependency>  

 

让我们看一个具体的例子——Example 4.10, “@AnalyzerDef and the Solr framework”。首先一个char filter通过它的factory来定义。在这个例子中,使用的是一个mapping char filter,它会基于指定的mapping file来替代输入流中的字符。之后是tokenizer的定义。这个例子使用标准的tokenizer。最后,一系列的filter通过它们的 factory来定义。在这个例子中,StopFilter过滤器会读取在property file中指定的专有名词。该过滤器将忽略大小写。

 

 

Example 4.10. @AnalyzerDef and the Solr framework

Java代码   收藏代码
  1. @AnalyzerDef(name="customanalyzer",  
  2.   charFilters = {  
  3.     @CharFilterDef(factory = MappingCharFilterFactory.class, params = {  
  4.       @Parameter(name = "mapping",  
  5.         value = "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")  
  6.     })  
  7.   },  
  8.   tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
  9.   filters = {  
  10.     @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class),  
  11.     @TokenFilterDef(factory = LowerCaseFilterFactory.class),  
  12.     @TokenFilterDef(factory = StopFilterFactory.class, params = {  
  13.       @Parameter(name="words",  
  14.         value= "org/hibernate/search/test/analyzer/solr/stoplist.properties" ),  
  15.       @Parameter(name="ignoreCase", value="true")  
  16.     })  
  17.   })  
  18. public class Team {  
  19.     ...  
  20. }  

 

 

Tip :Filters 和 char filters会按他们在@AnalyzerDef中定义的顺序来应用的。因些顺序是重要的。

 

有些tokenizers, token filters 或char filters需要加载资源文件(比如配置文件,元数据文件等)。stop filter和synonym filter就需要这样的文件。如果资源文件的charset与VM默认的不一样,你可以通过resource_charset 参数明确地指定资源文件的charset。

 

Example 4.11. Use a specific charset to load the property file

 

Java代码   收藏代码
  1. @AnalyzerDef(name="customanalyzer",  
  2.   charFilters = {  
  3.     @CharFilterDef(factory = MappingCharFilterFactory.class, params = {  
  4.       @Parameter(name = "mapping",  
  5.         value = "org/hibernate/search/test/analyzer/solr/mapping-chars.properties")  
  6.     })  
  7.   },  
  8.   tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
  9.   filters = {  
  10.     @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class),  
  11.     @TokenFilterDef(factory = LowerCaseFilterFactory.class),  
  12.     @TokenFilterDef(factory = StopFilterFactory.class, params = {  
  13.       @Parameter(name="words",  
  14.         value= "org/hibernate/search/test/analyzer/solr/stoplist.properties" ),  
  15.       @Parameter(name="resource_charset", value = "UTF-16BE"),  
  16.       @Parameter(name="ignoreCase", value="true")  
  17.   })  
  18. })  
  19. public class Team {  
  20.     ...  
  21. }  

 

 

 

一旦定义了一个Analyzer,它就可以通过@Analyzer注解来重用。如Example 4.12, “Referencing an analyzer by name”.所示。

 

Example 4.12. Referencing an analyzer by name

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. @AnalyzerDef(name="customanalyzer", ... )  
  4. public class Team {  
  5.     @Id  
  6.     @DocumentId  
  7.     @GeneratedValue  
  8.     private Integer id;  
  9.       
  10.     @Field  
  11.     private String name;  
  12.       
  13.     @Field  
  14.     private String location;  
  15.       
  16.     @Field   
  17.     @Analyzer(definition = "customanalyzer")  
  18.     private String description;  
  19. }  

 

由@AnalyzerDef声明的Analyzer同样可以用在SearchFactory中,这在创建query时会非常有用。

 

Java代码   收藏代码
  1. Analyzer analyzer = fullTextSession.getSearchFactory().getAnalyzer("customanalyzer");  

 

查询字符串应该使用indexing time相同的analyzer,看作两者在说同一种“语言”:在query和indexing的过程中都生成同样的tokens。这个规则也会产生一些 问题,但对于大多数时候是很正确的。请遵循这个规则,除非你知道如何处理使用不同analyzer的情形。

 

 

4.3.2.1. 可用的解析器(Available analyzers)

Solr和Lucene自带了许多有用的char filter,tokenizer,filter。你可以在 http://wiki.apache.org/solr/AnalyzersTokenizersTokenFilters 找到完整的char filter factories, tokenizer factories 和 filter factories列表。让我们看一下其中的一部分解析器。

Table 4.1. Example of available char filters

 

FactoryDescriptionParametersAdditional
dependencies
MappingCharFilterFactory

按提供的mapping file替代某些字符

mapping:指向一个资源文件包含如下的mapping格式

                    "á" => "a"
                    "ñ" => "n"
                    "ø" => "o"

 none
HTMLStripCharFilterFactory 移除HTML中标准的标签,保留文本内容 none none

 

 

Table 4.2. Example of available tokenizers

FactoryDescriptionParametersAdditional
dependencies
StandardTokenizerFactory使用Lucene的StandardTokenizernonenone
HTMLStripCharFilterFactory移除HTML中标准的标签,保留文本容,之后交由StandardTokenizer处理nonesolr-core
PatternTokenizerFactory

通过一个指定的正则表达式来划分词语。

pattern:用于划分词语的正则表达式。

group:says which pattern group to extract into tokens

solr-core

 

Table 4.3. Examples of available filters

 

FactoryDescriptionParametersAdditional
dependencies
StandardFilterFactory从token中移除'.'和''s'nonesolr-core
LowerCaseFilterFactory小写所有的tokennonesolr-core
StopFilterFactory移除与列表中的stop word匹配的token

words:指向一个包含stop words的资源文件

ignoreCase:与stop word的比较是否要忽略大小写

solr-core
SnowballPorterFilterFactory

把一个单词还原为它的词干形式(像protect, protects,
protection都会使用protect作为token)

language:Danish,Dutch, English,Finnish, French,German, Italian,Norwegian,Portuguese, Russian,Spanish, Swedish 或其他语言solr-core
ISOLatin1AccentFilterFactory

移除发音符,比如French(法语)

nonesolr-core
PhoneticFilterFactory

向token stream插入发音相似的单词作为相似token(similar token)

encoder:值为DoubleMetaphone,Metaphone, Soundex或RefinedSoundex之一。

 

inject:true就插入新的token到token stream,false就替代现有的token。

 

maxCodeLength:设置可生成code的最大长度。只支持Metaphone和DoubleMetaphone encoder。

solr-core and commons-codec
CollationKeyFilterFactory

转换每个token成java.text.CollationKey,并使用IndexableBinaryStringTools对java.text.CollationKey进行编码,从而把它保存到index term。

 

 custom,  language,country,  variant,strength,decomposition  see Lucene's CollationKeyFilter javadocs for more info solr-core and commons-io

 

我们推荐通过IDE工具查看org.apache.solr.analysis.TokenizerFactory和org.apache.solr.analysis.TokenFilterFactory的所有可用的实现。

 

4.3.3. 动态解析器选择(试验性的)(Dynamic analyzer selection (experimental))

到目前为止,我们所定义的analyzer都是静态的。然而,可能有这样的需求,我们需要根据实体类的具体状态来决定使用哪个analyzer,比 如说在一个多语言的应用环境中。拿Example 4.13, “Usage of @AnalyzerDiscriminator”中的BlogEntry类来说,analyzer需要根据language属性来决定使用哪个 analyzer,那么对词干(stemmer)解析就可以指定正确的语言来解析文本。

 

Hibernate Search引进了AnalyzerDiscriminator注解来实现Dynamic analyzer selection。Example 4.13, “Usage of @AnalyzerDiscriminator”展示了这个注解的使用。

 

Example 4.13. Usage of @AnalyzerDiscriminator

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. @AnalyzerDefs({  
  4.   @AnalyzerDef(name = "en",  
  5.     tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
  6.     filters = {  
  7.       @TokenFilterDef(factory = LowerCaseFilterFactory.class),  
  8.       @TokenFilterDef(factory = EnglishPorterFilterFactory.class  
  9.       )  
  10.     }),  
  11.   @AnalyzerDef(name = "de",  
  12.     tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
  13.     filters = {  
  14.       @TokenFilterDef(factory = LowerCaseFilterFactory.class),  
  15.       @TokenFilterDef(factory = GermanStemFilterFactory.class)  
  16.     })  
  17. })  
  18. public class BlogEntry {  
  19.     @Id  
  20.     @GeneratedValue  
  21.     @DocumentId  
  22.     private Integer id;  
  23.       
  24.     @Field  
  25.     @AnalyzerDiscriminator(impl = LanguageDiscriminator.class)  
  26.     private String language;  
  27.       
  28.     @Field  
  29.     private String text;  
  30.       
  31.     private Set<BlogEntry> references;  
  32.     // standard getter/setter  
  33.     ...  
  34. }  
  35.   
  36. public class LanguageDiscriminator implements Discriminator {  
  37.      public String getAnalyzerDefinitionName(Object value, Object entity, String field) {  
  38.         if ( value == null || !( entity instanceof Article ) ) {  
  39.             return null;  
  40.         }  
  41.         return (String) value;  
  42.     }  
  43. }  

 

使用@AnalyzerDiscriminator的先决条件是所有的analyzers都通过@AnalyzerDef来预定义。 @AnalyzerDiscriminator可应用于类级别和属性级别。通过impl参数来指定Discriminator接口具体的实现类。该接口只 需要实现getAnalyzerDefinitionName() 方法,它是在field添加到Lucene document的时候调用。那个被索引的实体也会被传递给接口方法。value参数只有当@AnalyzerDiscriminator应用于属性域上 时才会被传递进来。在这个例子中,language属性的当前值传递进了接口方法。

 

所有的Discriminator接口的实现类都应该返回已定义analyzer的名称,如果返回的是null,则使用默认的analyzer。

 

Note :@AnalyzerDiscriminator依然处于测试阶段,这里的API可能会在以后的版本中修改。我们希望得到你们的使用反馈给我们社区。

 

4.3.4. 获取Analyzer(Retrieving an analyzer)

在某些情形下,需要手动控制来获取Analyzer。例如,在域模型中使用了多个analyzer,在生成query的你又需要使用与index time相同analyzer。

 

Note :如果你有一个好的理 由的话,你可以打破这个规则。如果你不确定的话,应该使用相同的analyzer。如果你使用的是Hibernate Search query DSL (see Section 5.1.2,“Building a Lucene query with the Hibernate Search query DSL”),你不需要关心这一点。因为 query DSL会完全透明地使用正确的analyzer。

 

无论你使用的是Lucene编程API还是Lucene的QueryParser,你都可以获取某个实体类的scoped analyzer。scoped analyzer是一个能自动地对索引中的field使用正确的analyzer的解析器。记住这一点,在一个实体类中,可以为各个属性域定义不同的 analyzer。这个原理看起来有点复杂,但在query中使用正确的analyzer是一件很容易的事。

 

Example 4.14. Using the scoped analyzer when building a full-text query

Java代码   收藏代码
  1. org.apache.lucene.queryParser.QueryParser parser = new QueryParser(  
  2.     "title",   
  3.     fullTextSession.getSearchFactory().getAnalyzer( Song.class )  
  4. );  
  5. org.apache.lucene.search.Query luceneQuery =   
  6.     parser.parse( "title:sky Or title_stemmed:diamond" );  
  7. org.hibernate.Query fullTextQuery =   
  8.     fullTextSession.createFullTextQuery( luceneQuery, Song.class );  
  9. List result = fullTextQuery.list(); //return a list of managed objects   

 

在这个例子中,song title被索引在两个field中,title field使用的是standard analyzer,而title_stemmed使用的是stemming analyzer。query会根据对应的field使用了合适的analyzer。

 

Tip :通过@AnalyzerDef定义的analyzer,你同样通过searchFactory.getAnalyzer(String)来获取。

 

4.4. Bridges

在我们讨论实体类的基本映射的时候,一个很重要的因素一直被我们忽略。在Lucene所有的index field必须渲染为字符串类型。所有标注为@Field的属性域都要转换为String来建立索引。我们到现在才提出这个问题是因为在大多数情况 下,Hibnerate Search都会自动地完成转换工作,这些工作都由内建的bridge完成的。然而,有些时候你需要一个更好的控制这个转换过程。

 

4.4.1. Built-in bridges

Hibernate Search捆绑了大量的bridge完成Java属性类型到字符串的转换。

 

 

 

Hibernate Search comes bundled with a set of built-in bridges between a Java property type and
its full text representation.

 

null:默认地,null值不会被索引。Lucene也不支持null元素。然而有时候使用一个自定义的token来代替null值会显得非常有用。 See Section 4.1.1.2, “@Field” for more information

 

java.lang.String:按原本的值来索引。

 

short, Short, integer, Integer, long, Long, float, Float, double, Double, BigInteger, BigDecimal的数值型类型:转换到字符串的表示形式。注意,Lucene不能自动完成数值型的对比工作(如在range query中),它们需要补齐(padded)。

 

Note :使用range query是有争议的而且有一些缺点。一个可选的方法是使用一个Filter query,它可以过滤一个查询到合适的范围。Hibernate Search支持补齐机制(padding mechanism)。

 

java.util.Date:日期型保存成yyyyMMddHHmmssSSS的字符串形式 (例如200611072203012表示2006年11月7日 22:03 12ms) 。你不必为它的格式化忧心,重要的是,当你使用DateRange查询的时候,你需要知道日期应该表示成GMT形式。

 

一般来说,保存的Date不需要精确到毫秒。@DateBridge定义了合适的解决方法让你去保存Date到index。@DateBridge(resolution=Resolution.DAY)定义了Date只保存到具体的'日',而不是毫秒。

 

Java代码   收藏代码
  1. @Entity   
  2. @Indexed  
  3. public class Meeting {  
  4.     @Field(index=Index.UN_TOKENIZED)  
  5.     @DateBridge(resolution=Resolution.MINUTE)  
  6.     private Date date;  
  7.     ...      
  8. }  

 

Warning :当Date的resolution低于MILLISECOND,不能标注为@DocumentId

 

java.lang.Class:转换到它的全限定名。The thread context classloader is used when the class is rehydrated

 

 

4.4.2. 自定义bridge(Custom bridges)

有时候,当Hibernate Search内建的bridge并不能满足你的需要的时候,那么你就需要自定义bridge。下面的段落介绍了一些解决方法。

 

4.4.2.1. StringBridge

这是最简单的解决方法建立Object到String的bridge,你只需要向Hibernate Search提交org.hibernate.search.bridge.StringBridge接口的实现类。所有的实现类都必须是线程安全的。

 

Example 4.15. Custom StringBridge implementation

Java代码   收藏代码
  1. /** 
  2.  * Padding Integer bridge. 
  3.  * All numbers will be padded with 0 to match 5 digits 
  4.  * 
  5.  * @author Emmanuel Bernard 
  6.  */  
  7. public class PaddedIntegerBridge implements StringBridge {  
  8.       
  9.     private int PADDING = 5;  
  10.       
  11.     public String objectToString(Object object) {  
  12.         String rawInteger = ( (Integer) object ).toString();  
  13.         if (rawInteger.length() > PADDING)   
  14.             throw new IllegalArgumentException( "Try to pad on a number too big" );  
  15.         StringBuilder paddedInteger = new StringBuilder( );  
  16.         for ( int padIndex = rawInteger.length() ; padIndex < PADDING ; padIndex++ ) {  
  17.             paddedInteger.append('0');  
  18.         }  
  19.         return paddedInteger.append( rawInteger ).toString();  
  20.     }  
  21. }  

 

你可以使用通过@FieldBridge注解的impl参数应用上面定义的StringBridge。

Java代码   收藏代码
  1. @FieldBridge(impl = PaddedIntegerBridge.class)  
  2. private Integer length;    

 

4.4.2.1.1. 参数化bridge(Parameterized bridge)

向bridge的实现类传递参数会变得更加灵活。Example 4.16, “Passing parameters to your bridge implementation”  实现了ParameterizedBridge接口并参数通过注解@FieldBridge传递进来。

 

Example 4.16. Passing parameters to your bridge implementation

Java代码   收藏代码
  1. public class PaddedIntegerBridge implements StringBridge, ParameterizedBridge {  
  2.       
  3.     public static String PADDING_PROPERTY = "padding";  
  4.     private int padding = 5//default  
  5.       
  6.     public void setParameterValues(Map parameters) {  
  7.         Object padding = parameters.get( PADDING_PROPERTY );  
  8.         if (padding != nullthis.padding = (Integer) padding;  
  9.     }  
  10.       
  11.     public String objectToString(Object object) {  
  12.         String rawInteger = ( (Integer) object ).toString();  
  13.         if (rawInteger.length() > padding)   
  14.             throw new IllegalArgumentException( "Try to pad on a number too big" );  
  15.         StringBuilder paddedInteger = new StringBuilder( );  
  16.         for ( int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++ ) {  
  17.             paddedInteger.append('0');  
  18.         }  
  19.         return paddedInteger.append( rawInteger ).toString();  
  20.     }  
  21. }  
  22.   
  23.   
  24. //property  
  25. @FieldBridge(impl = PaddedIntegerBridge.class,  
  26.              params = @Parameter(name="padding", value="10")  
  27.             )  
  28. private Integer length;  

 

ParameterizedBridge接口可以与StringBridge,TwoWayStringBridge,FieldBridge一起实现。所有的实现必须是线程安全的,参数必须在初始化时设置。

 

4.4.2.1.2. 按类型的bridge(Type aware bridge)

 

有时候能知道bridge应用在哪个类型上面是非常有用的:

  • 知道getter方法返回类型的bridge
  • 知道类的类型通过类级别的bridge

一个例子是bridge处理enums在一种自定义的形式,但在访问时应用实际的enum类型。任何实现了 AppliedOnTypeAwareBridge接口的bridge会自动的注入所需要的类型。像parameters一样,类型注入不需要关心线程安全。

 

 

4.4.2.1.3. 两种方式的bridge(Two-way bridge)

如果你的bridge实现应用在id属性(使用了@DocumentId注解),你需要使用TwoWayStringBridge接口,该接口继承 于StringBridge接口。Hibernate Search需要读取id的字符串表示并生成对象表示形式。同样也是使用@FieldBridge注解。

 

Example 4.17. Implementing a TwoWayStringBridge usable for id properties

Java代码   收藏代码
  1. public class PaddedIntegerBridge implements TwoWayStringBridge, ParameterizedBridge {  
  2.     public static String PADDING_PROPERTY = "padding";  
  3.     private int padding = 5//default  
  4.     public void setParameterValues(Map parameters) {  
  5.         Object padding = parameters.get( PADDING_PROPERTY );  
  6.         if (padding != nullthis.padding = (Integer) padding;  
  7.     }  
  8.     public String objectToString(Object object) {  
  9.         String rawInteger = ( (Integer) object ).toString();  
  10.         if (rawInteger.length() > padding)   
  11.             throw new IllegalArgumentException( "Try to pad on a number too big" );  
  12.         StringBuilder paddedInteger = new StringBuilder( );  
  13.         for ( int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++ ) {  
  14.             paddedInteger.append('0');  
  15.         }  
  16.         return paddedInteger.append( rawInteger ).toString();  
  17.     }  
  18.     public Object stringToObject(String stringValue) {  
  19.         return new Integer(stringValue);  
  20.     }  
  21. }  
  22. //id property  
  23. @DocumentId  
  24. @FieldBridge(impl = PaddedIntegerBridge.class,  
  25.              params = @Parameter(name="padding", value="10")   
  26. private Integer id;  

 

 

 

4.4.2.2. FieldBridge

有些用例不仅仅是把对象转换成字符串来映射属性到lucene index中,但实现FieldBridge会让你得到最大的灵活性。该接口为你提供了属性值,并让你按你所希望的方式映射到Lucene Document。比如说你希望把一个属性域保存在两个不同的document field中。这个接口的概念与Hibernate UserType非常类似。

 

Example 4.18. Implementing the FieldBridge interface

Java代码   收藏代码
  1. /** 
  2.  * Store the date in 3 different fields - year, month, day - to ease Range Query per 
  3.  * year, month or day (eg get all the elements of December for the last 5 years). 
  4.  * @author Emmanuel Bernard 
  5.  */  
  6. public class DateSplitBridge implements FieldBridge {  
  7.     private final static TimeZone GMT = TimeZone.getTimeZone("GMT");  
  8.       
  9.     public void set(String name, Object value, Document document,   
  10.                     LuceneOptions luceneOptions) {  
  11.         Date date = (Date) value;  
  12.         Calendar cal = GregorianCalendar.getInstance(GMT);  
  13.         cal.setTime(date);  
  14.         int year = cal.get(Calendar.YEAR);  
  15.         int month = cal.get(Calendar.MONTH) + 1;  
  16.         int day = cal.get(Calendar.DAY_OF_MONTH);  
  17.     
  18.         // set year  
  19.         luceneOptions.addFieldToDocument(  
  20.             name + ".year",  
  21.             String.valueOf( year ),  
  22.             document );  
  23.     
  24.         // set month and pad it if needed  
  25.         luceneOptions.addFieldToDocument(  
  26.             name + ".month",  
  27.             month < 10 ? "0" : "" + String.valueOf( month ),  
  28.             document );  
  29.     
  30.         // set day and pad it if needed  
  31.         luceneOptions.addFieldToDocument(  
  32.             name + ".day",  
  33.             day < 10 ? "0" : "" + String.valueOf( day ),  
  34.             document );  
  35.     }  
  36. }  
  37.   
  38. //property  
  39. @FieldBridge(impl = DateSplitBridge.class)  
  40. private Date date;  

 

在Example 4.18, “Implementing the FieldBridge interface”中,fields并不是直接地添加到Document。相反,添加操作委托给LuceneOptions helper;helper会应用注解中的@Field的选项,像Store,TermVector或@Boost值。这也用于封装复杂的 COMPRESS的实现。虽然委托LuceneOptions去添加fields到Document是推荐的,但你也可以直接编辑Document和忽略 LuceneOptions。

 

Tip :像LuceneOptions这些类从Lucene API中变化而来的,它们作用是保护你的应用和简化代码。你可以使用它们,但并不是必须的。

 

4.4.2.3. ClassBridge

有时候组合一个实体的多个属性到Lucene index中会很有用的。@ClassBridge包含一个或多个@ClassBridge可以定义在class级别。在这种情况下, 自定义的field bridge的value参数不再是属性域的值,而是该实体实例。@ClassBridge支持termVector属性。

 

Example 4.19. Implementing a class bridge

Java代码   收藏代码
  1. @Entity  
  2. @Indexed  
  3. @ClassBridge(name="branchnetwork",  
  4.              index=Index.TOKENIZED,  
  5.              store=Store.YES,  
  6.              impl = CatFieldsClassBridge.class,  
  7.              params = @Parameter( name="sepChar", value=" " ) )  
  8. public class Department {  
  9.     private int id;  
  10.     private String network;  
  11.     private String branchHead;  
  12.     private String branch;  
  13.     private Integer maxEmployees  
  14.     ...  
  15. }  
  16. public class CatFieldsClassBridge implements FieldBridge, ParameterizedBridge {  
  17.     private String sepChar;  
  18.     public void setParameterValues(Map parameters) {  
  19.         this.sepChar = (String) parameters.get( "sepChar" );  
  20.     }  
  21.   
  22.     public void set(  
  23.         String name, Object value, Document document, LuceneOptions luceneOptions) {  
  24.         // In this particular class the name of the new field was passed  
  25.         // from the name field of the ClassBridge Annotation. This is not  
  26.         // a requirement. It just works that way in this instance. The  
  27.         // actual name could be supplied by hard coding it below.  
  28.         Department dep = (Department) value;  
  29.         String fieldValue1 = dep.getBranch();  
  30.         if ( fieldValue1 == null ) {  
  31.             fieldValue1 = "";  
  32.         }  
  33.         String fieldValue2 = dep.getNetwork();  
  34.         if ( fieldValue2 == null ) {  
  35.             fieldValue2 = "";  
  36.         }  
  37.         String fieldValue = fieldValue1 + sepChar + fieldValue2;  
  38.         Field field = new Field( name, fieldValue, luceneOptions.getStore(),  
  39.             luceneOptions.getIndex(), luceneOptions.getTermVector() );  
  40.         field.setBoost( luceneOptions.getBoost() );  
  41.         document.add( field );  
  42.    }  
  43. }  

 

在这个例子中, CatFieldsClassBridge被应用到 department实例,field bridge结合了branch和network属性域并添加到index。

 

4.5. Providing your own id

Warning :这部分文档的工作还在进行中。

 

如果你扩展了Hibernate Search内部功能,你可以提供你自己的id。你可以生成唯一的id值添加到index中去。它必须在创建an org.hibernate.search.Work的时候提供给Hibernate Search——document id是构造器中的一个参数。

 

4.5.1. The ProvidedId annotation

@PrivideId与@DocumentId不一样,它是标注在类级别上的。你可以通过bridge属性来指定一个自定义的bridge实现。同 样地,如果你使用@ProvidedId注解一个类,该类的子类也会也会得到该注解,但是它不是通过@Inherited得到这种效果的。另外,要确保 @ProvidedId与@DocumentId不能同时使用,否则你的系统就会崩溃。

 

Example 4.20. Providing your own id

Java代码   收藏代码
  1. @ProvidedId (bridge = org.my.own.package.MyCustomBridge)  
  2. @Indexed  
  3. public class MyClass{  
  4.     @Field  
  5.     String MyString;  
  6.     ...  
  7. }  

 

4.6. Programmatic API 

Warning:该功能还在测试阶段。API在以后可能会有所改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值