Hibernate OGM 已不再维护

可视化数据结构并不容易,我相信我们展示在 JBoss World 2011 主旨演讲 中广受欢迎的演示的成功,很大程度上归功于多个大屏幕上展示的优秀的Web界面。这些Web应用有效地可视化了流动的推文、在标签云中突出的投票标签,以及动画化的Infinispan网格,当节点在一个理想化的hashweel上跳舞时,可视化节点间的数据分布。

因此,我相信在场的每个人都清楚地知道数据存储在Infinispan中,通过现场拔掉一台随机服务器,大家都能看到数据重新组织,这看起来是一种简单自然的方式处理大量数据。并不是所有的技术细节都得到了解释,因此在这篇和后续的博文中,我们将详细说明你 没有 看到的内容:数据是如何存储的,Drools如何过滤数据,所有可视化如何加载存储的网格数据,以及如何仍然在创纪录的时间内开发完成?

网格上的JPA

所有这些应用都只是使用了JPA:Java持久化API。想想这个名字:它显然是为了解决Java应用程序的所有持久化需求;实际上,虽然它传统上与JDBC数据库相关联,但我们刚刚展示了它并不一定局限于这些数据库:我们的应用程序运行的是Hibernate对象/网格映射器的早期预览: Hibernate OGM,简单的JPA模型映射到Infinispan,一个键/值存储。

收集需求

最初的计划不包括Hibernate OGM,因为它当时非常实验性,从未发布过,甚至没有打上标签。但很明确的是,我们想使用Infinispan:用于存储和搜索推文。Kevin Conner是负责设想技术需求的架构师,他成功推动每个参与的开发者完成各自的部分,并在创纪录的时间内将其整合成一个工作的应用程序;因此,他带着一个简单的需求列表找到了Emmanuel和我。

  • 我们希望展示Infinispan。
  • 我们希望实时存储大量来自Twitter直播流的推文。
  • 我们需要能够按时间顺序遍历它们,回滚流并重新处理(如您在演示视频中看到的那样,我们有一个虚假的作弊者,希望在第二阶段应用更严格的规则来过滤无效的推文,而不会丢失最初收集的推文)。
  • 我们需要知道哪些项目被投票最多:人们将通过在推文中使用话题标签来表达他们的偏好。
  • 我们想知道谁投票最多。
  • 它必须性能良好,可能在大量数据上。

使用Lucene。

因此,为了实现这些搜索需求,你必须考虑到,由于Infinispan是一个键/值存储,执行查询并不像在数据库上那么自然。Infinispan目前提供了两种主要方法:使用查询模块或定义一些简单的Map/Reduce任务

此外,考虑到这些要求。使用SQL,我们该如何统计包含特定话题标签的所有推文,提取所有收集话题标签的计数,并按频率排序?在一个关系型数据库中,这将是一个非常低效的查询,至少涉及全表扫描,可能每个话题标签都要进行一次扫描,并且需要预先列出要查找的话题标签列表。我们希望提取最常提及的标签,我们实际上不知道要查找什么,因为人们可以自由投票。

一个完全不同的方法是使用倒排索引:每次保存推文时,你都会对其进行分词,提取所有术语,并保持一个包含指向包含推文的指针的术语列表,并存储频率。这正是全文搜索引擎如Lucene的工作方式;此外,Lucene能够应用最先进的优化、缓存和过滤功能。我们的Infinispan查询和Hibernate Search都提供了与Lucene的便捷集成(它们实际上使用的是同一个引擎,一个针对Infinispan用户,另一个针对Hibernate和JPA用户)。

统计谁投票最多的问题在技术上与统计词频的问题类似,因此,Lucene再次是完美的选择。按时间戳对所有数据进行排序不是引入Lucene的好理由,但它仍然能做得很好,因此Lucene确实解决了这个应用程序的所有查询需求。

Hibernate OGM与Hibernate Search

因此,Infinispan查询可能是一个很好的选择。但我们选择了Hibernate OGM与Search,因为它们将提供相同的索引功能,而且在JPA接口之上。我还必须承认,Hibernate OGM最初被摒弃,因为它缺少HQL查询解析器:是我的错,因为我晚于实现它,但在这种情况下,这不是问题,因为所有我们需要的查询都通过全文查询得到了更好的解决,这些查询不是通过HQL定义的。

模型

那么我们的模型是什么样的呢?非常简单,它是一个单一的JPA实体,增强了Hibernate Search注解。

@Indexed(index = "tweets")
@Analyzer(definition = "english")
@AnalyzerDef(name = "english",
	tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
	filters = {
		@TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
		@TokenFilterDef(factory = LowerCaseFilterFactory.class),
		@TokenFilterDef(factory = StopFilterFactory.class, params = {
				@Parameter(name = "words", value = "stoplist.properties"),
				@Parameter(name = "resource_charset", value = "UTF-8"),
				@Parameter(name = "ignoreCase", value = "true")
		})
})
@Entity
public class Tweet {
	
	private String id;
	private String message = "";
	private String sender = "";
	private long timestamp = 0L;
	
	public Tweet() {}
	
	public Tweet(String message, String sender, long timestamp) {
		this.message = message;
		this.sender = sender;
		this.timestamp = timestamp;
	}

	@Id
	@GeneratedValue(generator = "uuid")
	@GenericGenerator(name = "uuid", strategy = "uuid2")
	public String getId() { return id; }
	public void setId(String id) { this.id = id; }

	@Field
	public String getMessage() { return message; }
	public void setMessage(String message) { this.message = message; }

	@Field(index=Index.UN_TOKENIZED)
	public String getSender() { return sender; }
	public void setSender(String sender) { this.sender = sender; }

	@Field
	@NumericField
	public long getTimestamp() { return timestamp; }
	public void setTimestamp(long timestamp) { this.timestamp = timestamp; }

}

请注意标识符的 uuid 生成器:在分布式环境中,目前这是最有效的选择。除了标准的 @Entity 以外,@Indexed 启用了 Lucene 索引引擎,@AnalyzerDefAnalyzer 指定了我们想要应用于索引推文的文本清理方式,@Field 选择要索引的属性,@NumericField 确保数值排序会高效执行,将索引值真正视为数字而不是额外的关键词:始终记住 Lucene 专注于自然语言匹配。

示例查询

由于我正在扩展所有功能以提高清晰度,所以这看起来可能有点冗长

public List<Tweet> allTweetsSortedByTime() {

	//this is needed only once but we want to show it in this context:
	QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity( Tweet.class ).get();

	//Define a Lucene query which is going to return all tweets:
	Query query = queryBuilder.all().createQuery();

	//Make a JPA Query out of it:
	FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery( query );

	//Currently needed to have Hibernate Search work with OGM:
	fullTextQuery.initializeObjectsWith( ObjectLookupMethod.PERSISTENCE_CONTEXT, DatabaseRetrievalMethod.FIND_BY_ID );

	//Specify the desired sort:
	fullTextQuery.setSort( new Sort( new SortField( "timestamp", SortField.LONG ) ) );

	//Run the query (or alternatively open a scrollable result):
	return fullTextQuery.getResultList();
}

下载它

要查看完整的示例,我已经将一个完整的 Maven 项目推送到 github。它包括所有查询的测试以及运行项目所需的所有详细信息,例如 Infinispan 和 JGroups 配置,以及启用 HibernateOGM 所需的 persistence.xml。

请克隆它以开始使用 OGM:https://github.com/Sanne/tweets-ogm

并在 IRCHibernate Search 论坛 或全新的 Hibernate OGM 论坛 上寻求任何解释。

实体如何在网格中持久化?

Emmanuel 将很快撰写关于此主题的博客,请关注博客!更新:已发布 OGM 博客


回到顶部