[Hibernate Search] (4) 实体类型的高级映射功能

高级映射

前面介绍的可搜索的域基本上都是字符串类型,实际上可搜索的类型是非常丰富的。

本文会介绍以下几个方面的内容:

  • Lucene对实体进行索引的过程
  • 借助Solr组件对这个过程的改进
  • 修改域的重要程度,从而让基于相关度的排序更加有意义
  • 动态决定是否对一个实体类型进行索引

桥接器(Bridges)

实体类型中可以使用的类型是无穷无尽的,但是对于Lucene索引而言,任何类型归根到底都会以字符串来表示。所以,在对实体的域进行索引时,这些域最终需要被转换为字符串类型的对象。

在Hibernate Search的术语中,这个过程叫做桥接,而实现这个过程的对象就被称为桥接器。在Hibernate Search中,已经存在很多自带的桥接器用来处理大多数常见类型到字符串类型的转换。

一对一(One-to-One)自定义转换

这是最常见的情况,表示的是实体中的一个Java属性和一个Lucene索引域的关联。下面介绍一些常见类型的映射和转换。

日期域的映射

日期类型的值首先会被转换成GMT时间,然后将它以“yyyyMMddHHmmssSSS”的字符串保存到索引中。

当一个日期类型被@Field标注是,这个过程是自动发生的。但是你也可以显式的使用@DateBridge,这样能够对存储到索引中的字符串进行更加精细的控制,比如我们只关注日期中的yyyyMMdd部分:

@Column
@Field
@DateBridge(resolution=Resolution.DAY)
private Date releaseDate;

处理null值

默认情况下,只要值是null,无论它的类型是什么都不会被保存到索引中。但是,通过@Field的indexNullAs属性也能够对这一行为进行修改:

@Column
@Field(indexNullAs=Field.DEFAULT_NULL_TOKEN)
private String description;

indexNullAs的默认值是Field.DO_NOT_INDEX_NULL。当使用Field.DEFAULT_NULL_TOKEN时,Hibernate Search会将null值以一个可配置的全局值保存到索引中。

这个可配置的全局值可以在hibernate.cfg.xml或者persistence.xml中:

hibernate.search.default_null_token=xxx

如果没有使用了Field.DO_NOT_INDEX_NULL但没有配置这个全局值,那么Hibernate Search会使用字符串“null”作为默认值。但是需要注意的是,这个是一个全局的值,意味着任何类型的null值都会以该值保存到索引中。如果需要为不同的类型设置不同的值作为null的替代,那么需要使用自定义的桥接器。

自定义的字符串转换

StringBridge

为了自定义地将一个Java属性映射到一个索引域,可以通过实现Hibernate Search提供的StringBridge接口来完成由Java属性到索引域单向的转换。

比如,对于App有一个属性叫做currentDiscountPercentage用来表示当前的折扣信息。为了计算方便,这个属性的类型时Float,但是在搜索的时候,我们希望这类信息能够通过更加有意义的方式搜索到,比如25%的折扣可以通过25搜索到,而不是0.25。此时,就需要为该属性实现一个桥接器了:

import org.hibernate.search.bridge.StringBridge;
/** Converts values from 0-1 into percentages (e.g. 0.25 -> 25) */
public class PercentageBridge implements StringBridge {
    public String objectToString(Object object) {
        try {
            float fieldValue = ((Float) object).floatValue();
            if(fieldValue< 0f || fieldValue> 1f) return "0";
            int percentageValue = (int) (fieldValue * 100);
            return Integer.toString(percentageValue);
        } catch(Exception e) {
            // default to zero for null values or other problems
            return "0";
        }
    }
}

以上的objectToString方法会将输入转换成最终存储到索引中的字符串。同时注意到,当代码产生任何异常时,都会返回一个0。这也是处理null值的一种方式,毕竟当传入的object为null时,这里是会抛出异常并被捕捉到的。

为了让以上的桥接器起作用,还需要在相应实体的域上添加一个注解:

@Column
@Field
@FieldBridge(impl=PercentageBridge.class)
private float currentDiscountPercentage;

这样一来,该桥接器就会在对该域建立索引时被调用了。

TwoWayStringBridge

除了StringBridge外,还可以通过实现TwoWayStringBridge借口来完成自定义的映射。正如它名字所表示的那样,它提供了双向映射的功能,来完成Java域到Index域的双向转化。

正是因为它是双向转换的,所以在实现该接口时还需要实现一个stringToObject的方法来完成从索引域到实体域的转化:

public Object stringToObject(String stringValue) {
    return Float.parseFloat(stringValue) / 100;
}

仅仅当实体域需要被当做Lucene索引中的ID域时,才需要实现它。也就意味着在该实体中,当需要转换被@Id或者@DocumentId标注的域,可以考虑使用它。

ParameterizedBridge

这是桥接器灵活性的体现。在实现桥接器时,甚至可以将配置参数传入其中。此时的桥接器需要额外实现ParameterizedBridge接口(StringBridge或者TwoWayStringBridge还是需要实现的)。

比如我们需要根据具体的需求来指定折扣信息的精度,比如当discount的值是0.2533时,使用之前的桥接器得到的结果只会得到一个取整后的结果25,而显然我们能够显示的更加精确比如25.33。因此,可以通过定义一个参数来表达小数点后应该保留的位数信息:

public class PercentageBridge implements StringBridge, ParameterizedBridge {
    public static final String DECIMAL_PLACES_PROPERTY = "decimal_places";
    private int decimalPlaces = 2; // default
    public String objectToString(Object object) {
        String format = "%." + decimalPlaces + "g%n";
        try {
            float fieldValue = ((Float) object).floatValue();
            if(fieldValue< 0f || fieldValue> 1f) return "0";
            return String.format(format, (fieldValue * 100f));
        } catch(Exception e) {
            return String.format(format, "0");
        }
    }

    public void setParameterValues(Map<String, String> parameters) {
        try {
            this.decimalPlaces = Integer.parseInt(parameters.get(DECIMAL_PLACES_PROPERTY) );
        } catch(Exception e) {}
    }
}

此时,桥接器期待接受一个名为decimal_places的参数。该参数会被保存到变量decimalPlaces中。如果没有参数被传入,那么也会使用默认的2作为decimalPlaces的值。

现在的问题就是,如何传入这个参数。答案是,通过@FieldBridge注解的params属性传入:

@Column
@Field
@FieldBridge(
    impl=PercentageBridge.class,
    params=@Parameter(
        name=PercentageBridge.DECIMAL_PLACES_PROPERTY, value="4")
    )
private float currentDiscountPercentage;

另外需要注意的是,StringBridgeTwoWayStringBridge的实现都不是线程安全的。所以要避免在其中保存任何状态,如果确实需要保存某种状态的话,可以考虑让桥接器实现ParameterizedBridge来进行参数的传入。

使用FieldBridge完成更复杂的映射

以上的映射都只是一对一的映射,即一个实体域对应一个索引域。其实,映射还可以是一对多或者多对一的关系,即一个实体域对应多个索引域或者多个实体域对应一个索引域。

映射一个实体域到多个索引域

比如当我们对文件名这一域建立索引时,会希望能够通过文件名或者文件扩展名进行搜索。那么就需要建立两个索引域,这时可以考虑使用FieldBridge接口,这个接口需要实现一个set方法,在该方法中进行索引域的设置:

import org.apache.lucene.document.Document;
import org.hibernate.search.bridge.FieldBridge;
import org.hibernate.search.bridge.LuceneOptions;

public class FileBridge implements FieldBridge {
    public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
        String file = ((String) value).toLowerCase();
        String type = file.substring(file.indexOf(".") + 1 ).toLowerCase();
        luceneOptions.addFieldToDocument(name+".file", file, document);
        luceneOptions.addFieldToDocument(name+".file_type", type, document);
    }
}

set方法的参数解释如下:

  • String name:表示该实体域的名字
  • Object value:表示当前被映射的实体域的值
  • Document document:表示的是Lucene用来表达该实体的索引结构
  • LuceneOptions luceneOptions:为了和Lucene交互,如向索引中添加更多的域

set方法中的参数中的LuceneOptions对象就是为了让代码能够和Lucene交互。而Document对象。我们可以使用luceneOptions.addFieldToDocument(name+".file",
file, document);
来完成索引域的添加。

最后,通过使用@FieldBridge来使用它:

@Column
@Field
@FieldBridge(impl=FileBridge.class)
private String file;

映射多个实体域到一个索引域

为了实现将多个实体域映射到一个索引域,需要在实体类层次上进行一些操作。所以此时桥接器不再是单纯的域桥接器(Field Bridge),而是一个类桥接器(Class Bridge)。

比如对于Device实体类型,我们不想为它的manufacturer和name分别建立索引,而希望将它们作为一个整体,建立一个索引:

public class DeviceClassBridge implements FieldBridge {
    public void set(String name, Object value, Document document, LuceneOptionsluceneOptions) {
        Device device = (Device) value;
        String fullName = device.getManufacturer() + " " + device.getName();
        luceneOptions.addFieldToDocument(name + ".name", fullName, document);
    }
}

此时就可以在该实体类型上使用它了:

@Entity
@Indexed
@ClassBridge(impl=DeviceClassBridge.class)
public class Device {

    @Id
    @GeneratedValue
    private Long id;

    @Column
    private String manufacturer;

    @Column
    private String name;
    // constructors, getters and setters...
}

TwoWayFieldBridge

之前我们接触到了为了实现双向转换的TwoWayStringBridge。而同样的,FieldBridge接口也有其“双向”的版本:TwoWayFieldBridge。和TwoWayStringBridge类似,在被@Id或者@DocumentId标注的域上使用@FieldBridge时必须使用TwoWayFieldBridge。

在单向的FieldBridge中,我们只需使用set方法来指明从实体域到索引域是如何映射的。而在双向的TwoWayFieldBridge中,我们还需要实现一个get方法来得到索引中的字符串表示并进行必要的转换:

public Object get(String name, Object value, Document document) {
    // return the full file name field... the file type field
    // is not needed when going back in the reverse direction
    return document.get(name + ".file");
}

public String objectToString(Object object) {
    // "file" is already a String, otherwise it would need conversion
    return object;
}
时间: 2024-11-05 02:19:05

[Hibernate Search] (4) 实体类型的高级映射功能的相关文章

[Hibernate Search] (5) 高级查询 - 过滤,投影和分面

高级查询 在介绍了更多的高级映射功能之后,是时候回顾一下之前介绍过的查询功能了,看看如何借助这些高级的映射功能来使用一些高级的查询功能.本文会通过以下几个方面进行介绍: 如何在不和数据库进行任何交互的前提下,借助Lucene的力量来动态的筛选结果 如何通过使用基于投影(Projection)的查询来获取需要的属性,从而避免与数据库的交互 如何使用分面搜索(Faceted Search)对搜索结果进行划分 如何使用查询时提升(Boosting) 如何给查询设置时间限制 过滤(Filtering)

asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)

原文:asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型) 一. 支持字段 EF允许读取或写入字段而不是一个属性.在使用实体类时,用面向对象的封装来限制或增强应用程序代码对数据访问的语义时,这可能很有用.无法使用数据注释配置.除了约定,还可以使用Fluent API为属性配置支持字段. 1.1 约定 public class Blog { // _<camel-cased property name> private string _url; public int

[Hibernate Search] 实体类型的映射

实体类型映射 在上一篇文章中,简要介绍了如何使用Hibernate Search来对一个实体进行全文搜索. 然而,在真实的应用中,实体与实体之间的关系也许更为复杂.为了对复杂实体进行搜索,就需要让底层的Lucene查询也能够理解这些关系. 下图反映了Database,Hibernate,Hibernate Search和Lucene之间的关系: 域映射选项(Field Mapping Options) 我们已经知道@Field注解用来让某个域可以被全文搜索到. 实际上,在添加该注解后,Hiber

hibernate之实体类型到映射文件

1.通过写hibernate的映射文件,将实体类型转换成数据库中的表 其中那个映射文件是根据实体类型而写的. 实体类型User.java package cn.wwh.www.hibernate.dd.property; import java.util.Arrays; import java.util.Date; /** *类的作用: * * *@author 一叶扁舟 *@version 1.0 *@创建时间: 2014-8-17 下午08:05:30 */ public class Use

hibernate实体类型映射文件

1.通过写hibernate映射文件.实体类型转换为数据库中的表 据实体类型而写的. 实体类型User.java package cn.wwh.www.hibernate.dd.property; import java.util.Arrays; import java.util.Date; /** *类的作用: * * *@author 一叶扁舟 *@version 1.0 *@创建时间: 2014-8-17 下午08:05:30 */ public class User { private

Hibernate应用SQL查询返回实体类型

Hibernate应用SQL查询返回实体类型 Hibernate使用SQL查询返回实体类型 以前,使用SQL查询,结果放在 RS 结果集中,还要去转换影射到Java类中.Hibernate中,可以自动帮我们注入到Hibernate 甚至是 非 Hibernate 类型当中. String sql = "select * from T_TMP_PUBLIC_POLICY TP"; SQLQuery sqluery = session.createSQLQuery(sql).addEnti

Hibernate之jpa实体映射的三种继承关系

在JPA中,实体继承关系的映射策略共有三种:单表继承策略(table per class).Joined策略(table per subclass)和Table_PER_Class策略. 1.单表继承策略 单表继承策略,父类实体和子类实体共用一张数据库表,在表中通过一列辨别字段来区别不同类别的实体.具体做法如下: a.在父类实体的@Entity注解下添加如下的注解: @Inheritance(Strategy=InheritanceType.SINGLE_TABLE)@Discriminator

[Hibernate Search] (3) 基础查询

基础查询 眼下我们仅仅用到了基于keyword的查询,实际上Hibenrate Search DSL还提供了其他的查询方式,以下我们就来一探到底. 映射API和查询API 对于映射API.我们能够通过使用Hibernate提供的注解来完毕映射工作.同一时候我们也能够使用JPA提供的注解来完毕.类似的,对于查询API,我们也能够从Hibernate和JPA提供的查询API中进行选择. 每种方式都有它的长处和缺点,比方当我们使用Hibernate提供的查询API时,意味着能够使用很多其它的特性,毕竟

mybatis入门基础(六)----高级映射(一对一,一对多,多对多)

一:订单商品数据模型 1.数据库执行脚本 创建数据库表代码: 1 CREATE TABLE items ( 2 id INT NOT NULL AUTO_INCREMENT, 3 itemsname VARCHAR(32) NOT NULL COMMENT '商品名称', 4 price FLOAT(10,1) NOT NULL COMMENT '商品定价', 5 detail TEXT COMMENT '商品描述', 6 pic VARCHAR(64) DEFAULT NULL COMMENT