原文: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
- @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配置与映射:hibernate-configuration-3.0.dtd与hibernate-mapping-3.0.dtd解析》 在Java世界里,Hibernate作为一款强大的对象关系映射(ORM)框架,极大地简化了数据库操作。而`hibernate-...
4. `org.hibernate.boot.model`和`org.hibernate.boot.model.source`:这两个包包含了模型构建和源代码解析的相关类,用于构建实体类的元模型,是Hibernate映射的基础。 5. `org.hibernate.boot.registry`:这部分...
《深入理解Hibernate JPA 2.1 API》 在Java世界中,ORM(Object-Relational Mapping)框架已经成为开发数据库应用程序不可或缺的一部分。其中,Hibernate作为一款功能强大的ORM框架,极大地简化了Java对象与数据库...
hibernate-mapping-3.0.dtd 最新从官网下载,无损无修改
6. **事务管理**:Hibernate 3.4支持基于JTA(Java Transaction API)和JPA的事务管理策略,可以方便地集成到各种应用服务器中。 7. **缓存机制**:Hibernate提供了一级缓存和二级缓存,通过缓存策略提高数据访问...
在深入探讨`hibernate-mapping`参数之前,我们先理解一下Hibernate的核心概念。Hibernate是一个开源的对象关系映射(ORM)框架,它允许开发者使用面向对象的方式来操作数据库。`hibernate-mapping`是Hibernate配置...
因为Hibernate在读出hbm.xml文件时需要通过网络读取到hibernate-mapping-3.0.dtd 文件。 如果没有网络不能正常工作。 所以提供上述文件。 以及hibernate-mapping-3.0.dtd,hibernate-configuration-3.0.dtd提供下载...
《深入理解Hibernate JPA 2.1 API:源码解析》 在Java世界里,ORM(Object-Relational Mapping)框架的出现极大地简化了数据库操作,其中Hibernate作为一款强大的ORM工具,备受开发者青睐。本篇文章将重点围绕...
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中实现Many-to-One关系映射,这是一种常见的数据库关联,表示一个实体可以与多个其他实体相关联。 在Many-to-One关系中,通常一个实体(如部门)可以有多个相关实体(如员工),而...
在Java的持久化框架Hibernate中,Many-to-Many映射是一种常见的关系模型,它用于表示两个实体类之间多对多的关系。在这个主题中,我们将深入探讨如何使用注解来实现这种映射,以及其背后的数据库原理和实际应用。 ...
《Hibernate Mapping:深入理解与应用》 Hibernate Mapping是Java领域中一种重要的对象关系映射(ORM)技术,它使得开发者可以使用面向对象的方式来操作数据库,极大地简化了数据存取的操作。在Hibernate中,...
1. **对象关系映射(ORM)**: Hibernate是ORM框架的代表,它通过Java类和数据库表之间的映射(Mapping),将数据库操作转化为对Java对象的操作,减少了直接SQL的使用,提高了代码的可读性和可维护性。 2. **Session...
Hibernate ORM(Object-Relational Mapping)是一种将Java对象模型与关系数据库进行映射的技术,旨在简化数据库操作,提高开发效率。它通过XML或注解的方式定义对象与数据库表之间的映射关系,使得开发者可以像操作...
映射实体到索引结构(Mapping entities to the index structure) - **基本映射**:如何将实体属性映射到索引字段。 - **多次映射属性**:允许同一属性在索引中存在多个实例,用于多字段查询。 - **嵌入式和关联...