Hibernate Search 是一个库,它通过自动索引实体,将 Hibernate ORM 与 Apache Lucene 或 Elasticsearch 集成,从而实现高级搜索功能:全文、地理空间、聚合等。有关更多信息,请参阅 hibernate.org 上的 Hibernate Search。

搜索论坛stackoverflow 上的这些问题启发,我决定撰写一篇博客,介绍如何使用当前可用的工具(Search)(4.1.1.Final)解决这个问题。但让我们先从问题开始。

问题

如何为在自定义类桥中添加的字段定义一个自定义分析器?让我们看一个例子。在下面的类Foo定义了一个自定义桥FooBridge。如何为此桥添加的字段指定一个自定义分析器?

@Entity
@Indexed
@ClassBridge(impl = FooBridge.class)
public static class Foo {
	@Id
	@GeneratedValue
	private Integer id;
}

解决方案 1

直接的方法看起来是这样的。

@Entity
@Indexed
@ClassBridge(name = "myCustomField", impl = FooBridge.class, analyzer = @Analyzer(impl = MyCustomAnalyzer.class))
public static class Foo {
	@Id
	@GeneratedValue
	private Integer id;
}

这可以正常工作,前提是你添加到FooBridge的字段名称为myCustomField。如果你添加的字段(甚至多个字段)具有不同的名称,则此方法将不再有效。由于 Lucene 分析器是按字段名称指定的,因此从你的@ClassBridge定义中,我们无法知道你添加了哪些字段,因此无法注册和应用正确的分析器。请参阅相关问题 HSEARCH-904

解决方案 2

在第二个解决方案中,你将自行管理分析器。相关的部分在桥实现中

public class FooBridge implements FieldBridge {

	@Override
	public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
		Field field = new Field(
				name,  
				"",
				luceneOptions.getStore(),
				luceneOptions.getIndex(),
				luceneOptions.getTermVector()
		);
		try {
			String text = "whatever you want to index";
			MyCustomAnalyzer analyzer = new MyCustomAnalyzer( );
			field.setTokenStream( analyzer.reusableTokenStream( name, new StringReader( text ) ) );
			document.add( field );
		}
		catch ( IOException e ) {
			// error handling
		}
	}
}
如你所见,你需要自行实例化分析器,然后设置你想要添加的字段的标记流。这将有效,但与@AnalyzerDef这通常用于在应用程序中全局定义和重用分析器。让我们来看看这个解决方案。

解决方案 3

让我们直接从代码开始

	@Entity
	@Indexed
	@AnalyzerDefs({
			@AnalyzerDef(name = "analyzer1",
					tokenizer = @TokenizerDef(factory = MyFirstTokenizer.class),
					filters = {
							@TokenFilterDef(factory = MyFirstFilter.class)
					}),
			@AnalyzerDef(name = "analyzer2",
					tokenizer = @TokenizerDef(factory = MySecondTokenizer.class),
					filters = {
							@TokenFilterDef(factory = MySecondFilter.class)
					}),
			@AnalyzerDef(name = "analyzer3",
					tokenizer = @TokenizerDef(factory = MyThirdTokenizer.class),
					filters = {
							@TokenFilterDef(factory = MyThirdFilter.class)
					})
	})
	@ClassBridge(impl = FooBridge.class)
	@AnalyzerDiscriminator(impl = FooBridge.class)
	public static class Foo {
		@Id
		@GeneratedValue
		private Integer id;
	}

	public static class FooBridge implements Discriminator, FieldBridge {

		public static final String[] fieldNames = new String[] { "field1", "field2", "field3" };
		public static final String[] analyzerNames = new String[] { "analyzer1", "analyzer2", "analyzer3" };

		@Override
		public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
			for ( String fieldName : fieldNames ) {
				Fieldable field = new Field(
						fieldName,
						"Your text to analyze and index",
						luceneOptions.getStore(),
						luceneOptions.getIndex(),
						luceneOptions.getTermVector()
				);
				field.setBoost( luceneOptions.getBoost() );
				document.add( field );
			}
		}

		public String getAnalyzerDefinitionName(Object value, Object entity, String field) {
			for ( int i = 0; i < fieldNames.length; i++ ) {
				if ( fieldNames[i].equals( field ) ) {
					return analyzerNames[i];
				}
			}
			return null;
		}
	}
这里有很多事情在发生,示例展示了搜索的许多有用特性。首先@AnalyzerDefs用于声明式地定义和构建分析器。这些分析器在给定的名称下全局可用,可以在整个应用程序中重用(也请参阅 SearchFactory#getAnalyzer(String))。您通过首先指定其分词器,然后应用一系列过滤器来构建分析器。有关更多信息,请参阅在线文档中的命名分析器

接下来,示例使用了@ClassBridge(impl = FooBridge.class)来定义自定义类桥接。这里没有特别之处。您在实现中添加哪些字段取决于您。

最后但同样重要的是,我们有@AnalyzerDiscriminator(impl = FooBridge.class)。这个注解通常用于基于实体状态的动态分析器选择。然而,它也可以很容易地在这个上下文中使用。为了方便起见,我让字段桥接直接实现所需的Discriminator接口。Discriminator#getAnalyzerDefinitionName现在将为索引中添加的每个字段调用。您还会传递实体本身以及值(如果字段桥接定义在属性上),但这在这个情况下并不重要。剩下的所有事情就是确保根据传递的字段名称返回正确的分析器名称。

解决方案 3 可能看起来更长,但它是一种声明式方法,并允许您重用分析器配置。它还表明,当前的 API 可能仍存在一些不足(例如,HSEARCH-904),但使用现有的工具,通常可以找到解决方案。

祝您分析愉快,

Hardy


返回顶部