- 浏览: 157225 次
- 性别:
- 来自: 广州
最新评论
-
leonhe_cn:
感谢楼主分享的文章
Hibernate Search(基于version3.4)--第三章Configuration -
gllfeixiang:
我有一堆商品 价格从1-1000不等,我用Hibernate ...
Hibernate Search(基于version3.4)--第五章Querying -
lanmolsz:
没有仔细看,但发现了一些翻译错误,比如:3.11锁配置中 配置 ...
Hibernate Search(基于version3.4)--第三章Configuration -
lanmolsz:
真是好人啊,这么大的工作量都做完了
Hibernate Search(基于version3.4)--第一章Getting Start -
Mybeautiful:
兄弟,可不可以发个完整的例子看看?
Hibernate Search(基于version3.4)--第一章Getting Start
Hibernate Search(基于version3.4)--第四章Mapping entities to the index structure
- 博客分类:
- 翻译
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
@Entity @Indexed public class Essay { ... }
你也可以设置@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的可选值如下表所示:
Value | Definition |
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_OFFSETS | TermVector.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注解接受下表所示的属性:
Value | Definition |
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
@Entity @Indexed public class Essay { ... @Id @DocumentId public Long getId() { return id; } @Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES) public String getSummary() { return summary; } @Lob @Field(index=Index.TOKENIZED) public String getText() { return text; } @Field @NumericField( precisionStep = 6) public float getGrade() { return grade; } }
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
@Entity @Indexed(index = "Book" ) public class Book { @Fields( { @Field(index = Index.TOKENIZED), @Field(name = "summary_forSort", index = Index.UN_TOKENIZED, store = Store.YES) } ) public String getSummary() { return summary; } ... }
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
@Entity @Indexed public class Place { @Id @GeneratedValue @DocumentId private Long id; @Field( index = Index.TOKENIZED ) private String name; @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } ) @IndexedEmbedded private Address address; .... } @Entity public class Address { @Id @GeneratedValue private Long id; @Field(index=Index.TOKENIZED) private String street; @Field(index=Index.TOKENIZED) private String city; @ContainedIn @OneToMany(mappedBy="address") private Set<Place> places; ... }
需要注意的是,当使用@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
@Entity @Indexed public class Place { @Id @GeneratedValue @DocumentId private Long id; @Field( index = Index.TOKENIZED ) private String name; @OneToOne( cascade = { CascadeType.PERSIST, CascadeType.REMOVE } ) @IndexedEmbedded private Address address; .... } @Entity public class Address { @Id @GeneratedValue private Long id; @Field(index=Index.TOKENIZED) private String street; @Field(index=Index.TOKENIZED) private String city; @IndexedEmbedded(depth = 1, prefix = "ownedBy_") private Owner ownedBy; @ContainedIn @OneToMany(mappedBy="address") private Set<Place> places; ... } @Embeddable public class Owner { @Field(index = Index.TOKENIZED) private String name; ... }
正如你所看到的一样,所有的标注了@*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 :
+name:jboss +address.city:atlanta
- 返回place名字中包含有JBoss 而且owner的名字包含有Joe 。
+name:jboss +address.orderBy_name:joe
Note :一个被关联的对象自身也可以是@Indexed,但并不是必需的。
当@IndexedEmbedded标注了一个实体,那么关联就必须是双向的,并且另一端的引用必须标注为@ContainedIn(如前面的例子)。如果不这样的话,当关联对象改变的时候,Hibernate Search将不会跟着更新根实体的index。
有时候,标注了@IndexedEmbedded的对象的类型并不是具体的对象类型(例如接口和它们的实现类)。这时候,就需要使用targetElement属性来指定具体的对象类型。
Example 4.6. Using the targetElement property of @IndexedEmbedded
@Entity @Indexed public class Address { @Id @GeneratedValue @DocumentId private Long id; @Field(index= Index.TOKENIZED) private String street; @IndexedEmbedded(depth = 1, prefix = "ownedBy_", targetElement = Owner.class) @Target(Owner.class) private Person ownedBy; ... } @Embeddable 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
@Entity @Indexed @Boost(1.7f) public class Essay { ... @Id @DocumentId public Long getId() { return id; } @Field(name="Abstract", index=Index.TOKENIZED, store=Store.YES, boost=@Boost(2f)) @Boost(1.5f) public String getSummary() { return summary; } @Lob @Field(index=Index.TOKENIZED, boost=@Boost(1.2f)) public String getText() { return text; } @Field public String getISBN() { return isbn; } }
4.2.2. Dynamic index time boosting
在有些情况下,boost因子可能依赖于实际对象的状态。在这种情况下,你就要自定义一个BoostStrategy的实现,并使用@DynamicBoost来注册该实现。
Example 4.8. Dynamic boost examle
public enum PersonType { NORMAL, VIP } @Entity @Indexed @DynamicBoost(impl = VIPBoostStrategy.class) public class Person { private PersonType type; // .... } public class VIPBoostStrategy implements BoostStrategy { public float defineBoost(Object value) { Person person = ( Person ) value; if ( person.getType().equals( PersonType.VIP ) ) { return 2.0f; } else { return 1.0f; } } }
在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
@Entity @Indexed @Analyzer(impl = EntityAnalyzer.class) public class MyEntity { @Id @GeneratedValue @DocumentId private Integer id; @Field(index = Index.TOKENIZED) private String name; @Field(index = Index.TOKENIZED) @Analyzer(impl = PropertyAnalyzer.class) private String summary; @Field(index = Index.TOKENIZED, analyzer = @Analyzer(impl = FieldAnalyzer.class) private String body; ... }
在这个例子里面,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中添加下面的配置
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-search-analyzers</artifactId> <version>3.4.0.Final</version> <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
@AnalyzerDef(name="customanalyzer", charFilters = { @CharFilterDef(factory = MappingCharFilterFactory.class, params = { @Parameter(name = "mapping", value = "org/hibernate/search/test/analyzer/solr/mapping-chars.properties") }) }, tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = StopFilterFactory.class, params = { @Parameter(name="words", value= "org/hibernate/search/test/analyzer/solr/stoplist.properties" ), @Parameter(name="ignoreCase", value="true") }) }) public class Team { ... }
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
@AnalyzerDef(name="customanalyzer", charFilters = { @CharFilterDef(factory = MappingCharFilterFactory.class, params = { @Parameter(name = "mapping", value = "org/hibernate/search/test/analyzer/solr/mapping-chars.properties") }) }, tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = ISOLatin1AccentFilterFactory.class), @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = StopFilterFactory.class, params = { @Parameter(name="words", value= "org/hibernate/search/test/analyzer/solr/stoplist.properties" ), @Parameter(name="resource_charset", value = "UTF-16BE"), @Parameter(name="ignoreCase", value="true") }) }) public class Team { ... }
一旦定义了一个Analyzer,它就可以通过@Analyzer注解来重用。如Example 4.12, “Referencing an analyzer by name”.所示。
Example 4.12. Referencing an analyzer by name
@Entity @Indexed @AnalyzerDef(name="customanalyzer", ... ) public class Team { @Id @DocumentId @GeneratedValue private Integer id; @Field private String name; @Field private String location; @Field @Analyzer(definition = "customanalyzer") private String description; }
由@AnalyzerDef声明的Analyzer同样可以用在SearchFactory中,这在创建query时会非常有用。
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
Factory | Description | Parameters | Additional dependencies |
MappingCharFilterFactory |
按提供的mapping file替代某些字符 |
mapping:指向一个资源文件包含如下的mapping格式 |
none |
HTMLStripCharFilterFactory | 移除HTML中标准的标签,保留文本内容 | none | none |
Table 4.2. Example of available tokenizers
Factory | Description | Parameters | Additional dependencies |
StandardTokenizerFactory | 使用Lucene的StandardTokenizer | none | none |
HTMLStripCharFilterFactory | 移除HTML中标准的标签,保留文本容,之后交由StandardTokenizer处理 | none | solr-core |
PatternTokenizerFactory |
通过一个指定的正则表达式来划分词语。 |
pattern:用于划分词语的正则表达式。 group:says which pattern group to extract into tokens |
solr-core |
Table 4.3. Examples of available filters
Factory | Description | Parameters | Additional dependencies |
StandardFilterFactory | 从token中移除'.'和''s' | none | solr-core |
LowerCaseFilterFactory | 小写所有的token | none | solr-core |
StopFilterFactory | 移除与列表中的stop word匹配的token |
words:指向一个包含stop words的资源文件 ignoreCase:与stop word的比较是否要忽略大小写 |
solr-core |
SnowballPorterFilterFactory |
把一个单词还原为它的词干形式(像protect, protects, |
language:Danish,Dutch, English,Finnish, French,German, Italian,Norwegian,Portuguese, Russian,Spanish, Swedish 或其他语言 | solr-core |
ISOLatin1AccentFilterFactory |
移除发音符,比如French(法语) |
none | solr-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
@Entity @Indexed @AnalyzerDefs({ @AnalyzerDef(name = "en", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = EnglishPorterFilterFactory.class ) }), @AnalyzerDef(name = "de", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class), filters = { @TokenFilterDef(factory = LowerCaseFilterFactory.class), @TokenFilterDef(factory = GermanStemFilterFactory.class) }) }) public class BlogEntry { @Id @GeneratedValue @DocumentId private Integer id; @Field @AnalyzerDiscriminator(impl = LanguageDiscriminator.class) private String language; @Field private String text; private Set<BlogEntry> references; // standard getter/setter ... } public class LanguageDiscriminator implements Discriminator { public String getAnalyzerDefinitionName(Object value, Object entity, String field) { if ( value == null || !( entity instanceof Article ) ) { return null; } return (String) value; } }
使用@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
org.apache.lucene.queryParser.QueryParser parser = new QueryParser( "title", fullTextSession.getSearchFactory().getAnalyzer( Song.class ) ); org.apache.lucene.search.Query luceneQuery = parser.parse( "title:sky Or title_stemmed:diamond" ); org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery( luceneQuery, Song.class ); 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只保存到具体的'日',而不是毫秒。
@Entity @Indexed public class Meeting { @Field(index=Index.UN_TOKENIZED) @DateBridge(resolution=Resolution.MINUTE) private Date date; ... }
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
/** * Padding Integer bridge. * All numbers will be padded with 0 to match 5 digits * * @author Emmanuel Bernard */ public class PaddedIntegerBridge implements StringBridge { private int PADDING = 5; public String objectToString(Object object) { String rawInteger = ( (Integer) object ).toString(); if (rawInteger.length() > PADDING) throw new IllegalArgumentException( "Try to pad on a number too big" ); StringBuilder paddedInteger = new StringBuilder( ); for ( int padIndex = rawInteger.length() ; padIndex < PADDING ; padIndex++ ) { paddedInteger.append('0'); } return paddedInteger.append( rawInteger ).toString(); } }
你可以使用通过@FieldBridge注解的impl参数应用上面定义的StringBridge。
@FieldBridge(impl = PaddedIntegerBridge.class) 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
public class PaddedIntegerBridge implements StringBridge, ParameterizedBridge { public static String PADDING_PROPERTY = "padding"; private int padding = 5; //default public void setParameterValues(Map parameters) { Object padding = parameters.get( PADDING_PROPERTY ); if (padding != null) this.padding = (Integer) padding; } public String objectToString(Object object) { String rawInteger = ( (Integer) object ).toString(); if (rawInteger.length() > padding) throw new IllegalArgumentException( "Try to pad on a number too big" ); StringBuilder paddedInteger = new StringBuilder( ); for ( int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++ ) { paddedInteger.append('0'); } return paddedInteger.append( rawInteger ).toString(); } } //property @FieldBridge(impl = PaddedIntegerBridge.class, params = @Parameter(name="padding", value="10") ) 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
public class PaddedIntegerBridge implements TwoWayStringBridge, ParameterizedBridge { public static String PADDING_PROPERTY = "padding"; private int padding = 5; //default public void setParameterValues(Map parameters) { Object padding = parameters.get( PADDING_PROPERTY ); if (padding != null) this.padding = (Integer) padding; } public String objectToString(Object object) { String rawInteger = ( (Integer) object ).toString(); if (rawInteger.length() > padding) throw new IllegalArgumentException( "Try to pad on a number too big" ); StringBuilder paddedInteger = new StringBuilder( ); for ( int padIndex = rawInteger.length() ; padIndex < padding ; padIndex++ ) { paddedInteger.append('0'); } return paddedInteger.append( rawInteger ).toString(); } public Object stringToObject(String stringValue) { return new Integer(stringValue); } } //id property @DocumentId @FieldBridge(impl = PaddedIntegerBridge.class, params = @Parameter(name="padding", value="10") private Integer id;
4.4.2.2. FieldBridge
有些用例不仅仅是把对象转换成字符串来映射属性到lucene index中,但实现FieldBridge会让你得到最大的灵活性。该接口为你提供了属性值,并让你按你所希望的方式映射到Lucene Document。比如说你希望把一个属性域保存在两个不同的document field中。这个接口的概念与Hibernate UserType非常类似。
Example 4.18. Implementing the FieldBridge interface
/** * Store the date in 3 different fields - year, month, day - to ease Range Query per * year, month or day (eg get all the elements of December for the last 5 years). * @author Emmanuel Bernard */ public class DateSplitBridge implements FieldBridge { private final static TimeZone GMT = TimeZone.getTimeZone("GMT"); public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { Date date = (Date) value; Calendar cal = GregorianCalendar.getInstance(GMT); cal.setTime(date); int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH) + 1; int day = cal.get(Calendar.DAY_OF_MONTH); // set year luceneOptions.addFieldToDocument( name + ".year", String.valueOf( year ), document ); // set month and pad it if needed luceneOptions.addFieldToDocument( name + ".month", month < 10 ? "0" : "" + String.valueOf( month ), document ); // set day and pad it if needed luceneOptions.addFieldToDocument( name + ".day", day < 10 ? "0" : "" + String.valueOf( day ), document ); } } //property @FieldBridge(impl = DateSplitBridge.class) 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
@Entity @Indexed @ClassBridge(name="branchnetwork", index=Index.TOKENIZED, store=Store.YES, impl = CatFieldsClassBridge.class, params = @Parameter( name="sepChar", value=" " ) ) public class Department { private int id; private String network; private String branchHead; private String branch; private Integer maxEmployees ... } public class CatFieldsClassBridge implements FieldBridge, ParameterizedBridge { private String sepChar; public void setParameterValues(Map parameters) { this.sepChar = (String) parameters.get( "sepChar" ); } public void set( String name, Object value, Document document, LuceneOptions luceneOptions) { // In this particular class the name of the new field was passed // from the name field of the ClassBridge Annotation. This is not // a requirement. It just works that way in this instance. The // actual name could be supplied by hard coding it below. Department dep = (Department) value; String fieldValue1 = dep.getBranch(); if ( fieldValue1 == null ) { fieldValue1 = ""; } String fieldValue2 = dep.getNetwork(); if ( fieldValue2 == null ) { fieldValue2 = ""; } String fieldValue = fieldValue1 + sepChar + fieldValue2; Field field = new Field( name, fieldValue, luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector() ); field.setBoost( luceneOptions.getBoost() ); document.add( field ); } }
在这个例子中, 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
@ProvidedId (bridge = org.my.own.package.MyCustomBridge) @Indexed public class MyClass{ @Field String MyString; ... }
4.6. Programmatic API
Warning:该功能还在测试阶段。API在以后可能会有所改变。
略。
发表评论
-
Hibernate Search(基于version3.4)--第九章Advanced features
2011-07-13 11:57 0Advanced features 在最后章节,我们提 ... -
Hibernate Search(基于version3.4)--第八章Monitoring
2011-07-13 11:06 1691Monitoring Hibernate Sea ... -
Hibernate Search(基于version3.4)--第七章Index Optimization
2011-07-13 10:35 1877Index Optimization 随着时间推 ... -
Hibernate Search(基于version3.4)--第六章Manual index changes
2011-07-13 09:51 3160Manual index changes 当Hi ... -
Hibernate Search(基于version3.4)--第五章Querying
2011-06-27 17:46 10803Querying Hibernate Search的第 ... -
Hibernate Search(基于version3.4)--第二章Archetype
2011-06-20 16:17 2284Archetype 2.1概述 H ... -
Hibernate Search(基于version3.4)--第一章Getting Start
2011-06-20 13:59 3880Getting Start 1. ...
相关推荐
hibernate-configuration-3.0.dtd、hibernate-mapping-3.0.dtd、hibernate.properties(5.0.7)
hibernate-mapping参数详解.docx
hibernate-mapping-3.0.dtd 配置后,就会在xml中进行提示
因为Hibernate在读出hbm.xml文件时需要通过网络读取到hibernate-mapping-3.0.dtd 文件。 如果没有网络不能正常工作。 所以提供上述文件。 以及hibernate-mapping-3.0.dtd,hibernate-configuration-3.0.dtd提供下载...
hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-3.0.dtd hibernate-mapping-...
hibernate-mappinghibernate-mapping
最新整理的hibernate-configuration的dtd文件,欢迎大家下载!最新整理的hibernate-configuration的dtd文件,欢迎大家下载!
Hibernate continues to be the most popular out-of-the-box framework solution for Java Persistence and data/database accessibility techniques and patterns. It is used for e-commerce–based web ...
Real-Time Photon Mapping Approximation on the GPU
Central Gateway Architecture A new Network Protocol: FlexRay A new Architecture: Backbone ...Dynamic Multi-PDU-to-Frame Mapping Ethernet and SOME/IP Testing and Analyzing Dynamic PDUs Summary
无偿奉献代码
XCN-CMMI-V2-0-to-V1-3-Practice-Mapping CMMI 2.0到1.3的实践映射中文版
Myeclipse开发struts+hibernate+spring新手入门---环境配置----项目开发示例 Myeclipse开发struts+hibernate+spring小记 开发前准备工作: 1、下载eclipse3.1版本 下载地址: 2、下载Myeclipse插件 下载地址: 3...
NULL 博文链接:https://liqita.iteye.com/blog/1497077
(b) the name of the table, the names of the table's attributes, the data types of the table's attributes, the formats of the table's attributes, and the maximum number of rows that the table can have...