Red Hat

In Relation To Strong Liu

In Relation To Strong Liu

BootstrapServiceRegistry中提供的基础服务介绍

Posted by Strong Liu    |       |    Tagged as

Apparently, most readers of this site are expecting english articles, so, I moved the original Chinese post to my personal blog

I will try to post a English version here but can't promise the time.

Hibernate ORM 新特性之 Service(Registry)

Posted by Strong Liu    |       |    Tagged as Hibernate ORM

Apparently, most readers of this site are expecting english articles, so, I moved the original Chinese post to my personal blog

I will try to post a English version here but can't promise the time.

Data Grid, Why?

Posted by Strong Liu    |       |    Tagged as Infinispan

NOTE: this post is translated from howtojboss.com and author is Shane K Johnson.

(本文翻译自howtojboss.com, 原文作者为Shane K Johnson.)

为什么需要使用数据网格呢? 本文旨在回到这个问题.

首先, 它是进化的产物.

本地缓存 > 集群缓存 > 分布式缓存(数据网格)

使用分布式缓存的原因中包括了为什么使用缓存集群, 而使用缓存集群的原因中包括了为什么使用本地缓存.(译注: 这句话感觉上真怪.)

性能

访问本地缓存中的一个对象比直接访问远端数据存储引擎(例如数据库)要快很多.

直接访问一个已经存在的对象比从数据创建一个对象要快.

  • 数据可能已经被存储在某(几)个地方了
  • 数据可能需要通过多条查询来被获取
  • 数据可能很复杂

另外, 数据网格支持一些性能调优特性 可能不被集群缓存所支持. 例如, 应用程序可以根据数据之间关联关系的紧密程度来确保相互关联的对象被保存在相同的缓存节点上.

更进一步, JBoss Data Grid还有一些自己所特有的性能调优方法, 例如, 它可以被配置成使用异步通讯并且提供了一个异步API.

一致性

本地缓存只有在应用程序被部署到单一的应用服务器上的时候才有意义, 如果它被部署到了多台应用服务器上的话, 那么本地缓存一点意义都没有, 问题出在过期数据. 集群缓存通过复制和让缓存数据失效来解决这个问题的.

除了支持JTA事物之外, 数据网格还支持XA(分布式)和两阶段提交事物.

最后, JBoss Data Grid还支持额外支持某些其它数据网格产品可能不支持的保证数据一致性的特性, 例如它支持事物处理恢复, 和基于版本号的更新或删除.

可伸缩性

集群缓存和数据网格的区别就在于可伸缩性. 数据网格是可伸缩的. 缓存数据是通过动态的分区被分发的. 结果就是, 增加一个缓存节点即提高了吞吐量也提高了容量.

JBoss Data Grid通过使用一致性Hash算法, 最小化的降低了增加或者删除一个结点所带来的结点(译注: 推荐阅读这篇文章, 或者这篇, 是中文的), 当增加或者删除一个结点的时候, 只有一部分数据被重新移动已达到平衡. 所以, 增加或者删除一个结点只会对数据网格中的一部分结点产生影响, 而别的算法就很可能会影响到数据网格中的所有结点了.

独立性

另外一个集群缓存和数据网格之间的不同点即是否支持独立访问了.

如果把一个数据网格集成进应用程序里面的话, 那么它就和应用程序耦合在一起了, 也就是, 当扩展这个内置的数据网格的时候, 同事也需要扩展应用程序, 结果, 扩展网格的同时, 增加了与之关联的应用程序(和应用服务器)的管理成本.

如下面的例子, 一个web应用被部属到了多个应用服务器, 并且它使用了内置的数据网格系统, 当这个数据网格缓存了足够多的内容的时候, 它就需要被扩容了.

这意味着什么呢?

需要安装并配置一个新的应用服务器, 然后把应用也部属到这个新的应用服务器中.

这意味着?

IT人员需要多管理一个应用服务器, 增加了管理成本.

  • 如果应用被重新部署, 内置的数据网格结点就被重新部署.*
  • 如果数据网格升级, 应用升序也得跟着升级(并且重新部署)

* 一个数据网格结点被重新部署的话, 那么整个数据网格的拓扑结构是会变化两次的: 一次是当结点被移出的时候, 另外一次是结点被部属的时候. 并且, 一旦数据网格拓扑结构发生变化, 网格内的数据会在结点之间被移动已达到平衡的(尽管只是一部分数据). 所以, 重新部属一个应用程序会对其它节点上运行着的数据网格产生影响, 并且是两次.

如果数据网格需要被调整的运行更快而应用不需要呢?

部属应用到额外的应用服务器, 尽管瓶颈并不在应用这里的话, 这样合理么? 考虑了资源利用率了么? 仅仅为了增加数据网格的吞吐量就增加应用服务器, 尽管会出现应用服务器空载, 这样又合理么?

解决方案就是使用独立的数据网格.

如下面的例子. 同样的, 一个web应用被部署到多个应用服务器, 但是, 这里, 它使用一个独立的数据王哥系统. 这时候, 数据网格达到了它所支持的最大容量, 需要被扩容.

这又意味着?

一个新的数据网格结点被安装并且配置, 就这么简单.

这样的架构允许数据网格能够独立于应用服务器而被独立的扩展. 也让数据网格的服务器能够被指派与应用服务器不同的资源. 例如, 一个数据网格结点服务器可能需要更多的内存但是更少的CPU, 相比于应用服务器的服务器.

这样的架构也让数据网格的基础架构能够独立于应用服务器的被惯例和调整.

数据网格能够独立于应用而被升级, 应用的重新部署也不会对数据网格本身产生任何影响.

基础架构

对比在基础架构中作为顶级系统的独立数据网格和内置于其它系统之中的二级数据网格服务.

举个例子, 一个企业有一个应用程序部属于应用服务器集群当中, 并且这个应用程序内置了一个数据网格系统. 接着, 这个企业有…

  • 增加了一个基于ESB的服务, 这个服务内部也内置了一个数据网格系统
  • 增加了一个门户, 部属于Portal平台, 它也内置了数据网格
  • 增加了业务流程服务跑在规则管理系统之上, 同样的, 它也内置了数据网格

能看出问题吧.

现在, 这个企业拥有多个独立的(内置)数据网格需要被管理, 也就增加了管理成本. 如果它们缓存的相同的数据(例如客户信息), 那么, 如同使用本地缓存的应用被部署到多台应用服务器一样, 面临着数据过期的风险. 如果数据被一个数据网格更新了, 那么, 在别的数据网格当中的相同数据也就没有意义了, 并且, 如果都存储的一样的数据的话, 那么数据网格的效率也是个问题, 这样, 只有他们容量总和的一部分是有效被利用的, 如同缓存集群一样, 重复的数据.

解决方案就是使用在基础架构中作为顶级系统的独立数据网格服务.

当然, 使用内置的数据网格服务也有其自身的优点, 可能会也可能不会超过使用独立缓存服务所带来的好处.

最后, 扼要重述, 使用数据网格的好处是它的可扩展性, 和独立性, 并且, 作为顶级基础设施组件, 它能够同时提供本地缓存和集群缓存所能够提供的性能和一致性.

Migrating Spring Applications to Java EE 6

Posted by Strong Liu    |       |    Tagged as Java EE

commentary 译注

This is a translation of this post, so if you're a english speaker, please read the original post.

这是我第一次正经的翻译整篇的英文文章, 感觉真是比自己写还要累啊...... :(

------------------------------------------------------------------------------------------------------------------

Bert Ertman, Paul BakkerLuminis 写了一系列的文章来介绍如何把应用从Spring迁移到Java EE6上, 并且他们还提供了一个简单的实例项目.

在本系列中, Paul和Bert从基本的原理开始介绍整个Spring到Java EE6的迁移过程, 并且通过实际的例子展示了如何升级Web UI显示层, 替换数据访问层, 如何从AOP切换到CDI 拦截器, 迁移JMX, 如何处理JDBC Templates等等, 另外, 还额外的演示了如何使用Arquillian来对基于Java EE6的应用程序做集成测试.

简介

在过去的几年中, 越来越多的人开始对Java EE感兴趣了, Java EE 5的出现就让大家耳目一新, Java EE 6则重建了开发人员对Java EE技术体系的信心. 现在, Java EE 已经是当下的热点技术, 许多开发人员都在重新对其进行评估, 已审视自己对其的固有印象. 在2003/2004年的时候, J2EE正处于最低潮的时期, 由于它太过于阳春白雪而忽视了如何解决实际问题, 让开发人员对J2EE嗤之以鼻. 正是那个时期, 一本书横空出世, 从本质上改变了之后五年内企业级Java开发的方式. 这本书正是J2EE Design and Development, 不久之后, 又有一本J2EE without EJB出版, 这两本书都出自Rod Johnson(译注:最近Rod Johnson刚刚从SrpingSource离开)之手.

开发人员当时非常喜爱这两本书和书中对他们在开发中碰到的问题所给出的解决方案. 事实上, 开发人员现在仍然很喜欢这些书, 但是, 他们并没有意识到现在的世界和几年前已经发生了根本性的改变. 现在, 我们需要思考的是那些在Rod Johnson 2003/2004的书中所提出的前提条件是否还合理. 按照我们的观点, 所有书中所提到的问题, 现在都可以使用基于轻量级POJO的 Java EE技术所以解决. 所以, 如果你仍然在意那两本书, 那么表达你的敬意的最好的方式可能就是用他们垫个桌子/显示器啥的.

有两种情况最有可能会讨论到究竟是用Spring还是Java EE, 从头开发一个全新的企业级应用, 或者, 升级一个遗留下来的应用. 如果是升级一个六七年前的Java应用, 那么不管是用Spring 还是Java EE, 都是个相当大的麻烦事. 不过, 如果是一个从头开始的项目, 我们认为选择Srping还是Java EE根本就不是一个问题了, Java EE由于其是Java业界的标准, 并且轻量, 对于主流企业级应用开发中的问题都有合适的解决方案, 自然是我们应该优先选择的方案. 如果没有特殊的原因, 你不应该选择标准技术之外的东东.

本文并不着重于讨论究竟哪种技术更好, Spring还是Java EE, 我们想要展示的是如何解决在升级老的Spring项目的过程中所遇到的问题. 并且, 我们也不鼓励完全按照本文所讲的路径进行升级. 现实中, 可能会有各种各样的原因没有办法完全遵循本文所介绍的迁移步骤进行, 例如时间, 金钱和经验等因素. 不过我们保证, 当你遗留的Spring项目遇到问题, 并且你想把它迁移到一个现代的企业软件架构, 还需要保证这个架构在未来五年内能够满足要求的话, 本文将为你演示一个可行的方案.

为什么要迁移?

在我们讨论如何迁移一个遗留的Spring项目之前, 首先需要问自己的是 为什么要这样搞? 这绝对是个合理的问题, 并且它可以有很多的答案. 首先, Spring是一个创建于主流技术不能满足大家要求的年代的开源项目, 它从诞生以来一直都很成功, 所以, 它从一开始就很吸引风险投资的眼球, 最终, 被专注于虚拟话的VMware所收购. 对于Java世界的人来说, 大家可能都知道可以用它在我们的Mac上跑虚拟机运行Windows. 不过Spring和它的众多子项目, 没有一个成为Java 标准, 不过还是有公司和个人在推动Spring的标准化进程的. 据我们所知, 推动Spring的标准化, 不管是对Spring项目, 还是它身后的SpringSource公司, 都没有任何明显的好处. 当然, 这些都是公司间的政治, 大堆数开发人员对此一点都不感冒, 所以, 还是让我们从技术的角度来看看为什么迁移到Java EE是一个更好的选择.

从技术角度来看, 就算你要把老版本的Spring项目升级到新版的, 也还是有好多工作要做, 老版本中所用到的好多Spring技术可能很快或者已经不被支持了.

当我们说老版本的Spring项目的时候, 我们脑海中浮现的是, 大量复杂的XML配置文件, 过时的DAO层技术, 例如JDBC Template, Kodo 或者TopLink, 还有像使用早就不推荐使用了的Web MVC的扩展 -- SimpleFormController.

现在还有程序员对这些技术很精通么? 就算有, 那么未来五年还能有么? 从我们自身的经验来说, 这些老的项目每年都需要维护, 需要每年都更换不同的Web / ORM 框架去适应这个框架在当时的最新版本.所有的上面这些, 都需要对项目进行大规模的改动, 部分程序需要从根本上被更新, 有些时候甚至需要从头来过, 既然你这都能够忍, 那为什么不一步到位, 直接把项目转成符合Java EE 标准的?

谣言终结者

如果你仍然对Java EE是否能够满足你的需求持怀疑态度, 那么我们建议你继续阅读本文, 希望能够消除你对其的一些误解. 大家都J2EE的一些缺点可能都很了解了, 但是, 一些开发人员就从此完全放弃了Java EE, 也不再关心其的最新进展了, 所以可能对当前Java EE不是很了解.

本文的目的不是向你完整的介绍Java EE6, 只是通过对比Spring 和 Java EE 6中的一些关键技术来说明为什么我们认为你应该选择Java EE 6.

轻量

过去大家经常抱怨Java EE的一点就是它太过于沉重了, 一些人甚至把Java EE解释成Java Evil Edition. 这个问题现在还存在么? 光听我们说可能还不够, 让我们来看看现在的应用服务器的启动时间. 在一台不是很老的PC上, 最新版的JBoss AS 7能够在2到5秒内完成启动和部属一个完整的Java EE6项目, 基本上和使用Tomcat + Spring相同. 不过, 如果考虑到你的项目的war/ear包的大小, 就会有明显的不同了, 一个标准的Java EE6项目最终的部属文件(war/ear)也就大约100K的大小, 而一个Spring, 则差不多要30M, 这是因为对于基于Java EE的项目来说, 所有的标准Java EE 服务都是由应用服务器所提供的, 就不需要再把依赖的Jar打进你自己的项目当中了.

一个我们最喜欢的观点来自于JBoss的工程师Andrew Lee Rubinger, 他说没有任何Java EE标准要求应用服务器要是笨重并且缓慢的 -- 非常正确!

依赖注入

单纯从技术角度, 我们值得看看过去的J2EE在这方面有哪些缺失, 而Spring也正是从那里切入的. 过去, 大家选择Spring的很重要的一个原因是它提供了反转控制(Inversion of Control, IoC), 或者, 现在比较流行的名字, 依赖注入(Dependency Injection, DI). 根据Wikipedia上的表述, 反转控制是一种开发模式, 核心在于让可重用的结构性代码控制业务逻辑代码. 这个模式有一个隐性的前提条件就是结构性代码和业务逻辑代码是分别独立开发的, 然后再整合进一个程序当中. 反转控制有时候也被表述称好莱坞原则, 即不要给我打电话, 有需要的时候我会叫你的, 因为其实现通常依赖于回调函数.

尽管反转控制通常被认为是由Spring框架的作者发明的, 但实际上, 早在上个世纪八十年代就已经有公开的出版物提到过这个古老的设计模式了. Spring 所做的, 实际上把这个设计模式成功的应用到了企业级Java开发当中, 并且借助于此, 大幅度的对代码之间的关系进行解耦, 从而得到了更好的可测试代码. Java EE在很长一段时间内都做不到这一点, 但是, 从Java EE6开始, 基于上下文关系的依赖注入(Contextal Dependency Injection, CDI), 成为了EE标准的一部分, 它提供了强大的基于上下文关系的依赖注入, 并且能够非常灵活的注入各种标准规范所提供的服务. 如果你还不了解它, 那可能真是一种损失了.

面向方面编程

另一项由Spring引领而流行起来的技术就是面向方面编程(Aspect Oriented Programming, AOP). 通过使用AOP, 基于面向对象的语言能够在编译期或者运行期被织入一些横向关注点. 典型的场景就要数日志和安全了, 当然, AOP的能力并不局限于这两部分. 但是过度的使用AOP则是另外一个常见的误区, 这会导致代码可读性降低. 这是因为AOP所关注的切面, 和切面逻辑的实现代码并不在同一个位置. 这样, 要是想知道一段代码在运行时究竟干了哪些事情就不是那么清楚了. 当然, 这并不是说AOP就一无是处, 如果你能够合理的使用AOP的话, 它能够帮你很好的做到关注点分离和代码重用.

我们通常把在有限的位置使用AOP称之为轻量级的AOP, 这也正是Java EE中的拦截器所提供的功能. EJB中过滤器的实现能够在某种程度上达到限制滥用AOP的作用. 过滤器模型是在Java EE 5的时候被引入的, 如果把它和CDI (在Java EE 6中被引入) 结合起来, 可以做到很清晰的关注分离架构, 并且大幅度的减少重复代码.

测试

在上面介绍依赖注入的时候, 我们已经简单的提了一下这块内容, 在过去的J2EE中, 基本上很难做到真正的功能代码测试, 主要是因为大部分J2EE的组件都依赖于各种运行时的服务才能够正常工作. 这一点是可测试设计的死穴. 而现在的Java EE已经回归到基于POJO的组件设计了, 可测试性提升不少, 不过, 各个组件依旧依赖于运行时的服务. 为了真正的对Java EE的组件在应用服务器中进行测试, 我们需要创建一个模拟(mock)框架. 或者不需要?

正是在不久之前, Arquillian 框架横空出世, 为Java EE领域中的功能测试创造了可能性.

通过使用Arquillian, 我们可以抛开各种模拟框架, 直接在容器中对Java EE组件进行测试. 在测试被执行之前, Arquillian会先创建一个所谓的微型部属文件(micro deployment)包含了被测试的Java EE组件和它直接依赖的服务, 然后再由此创建出一个可部属文件并将其部属至一个正在运行的Java EE容器(或者临时启动一个)当中.

Arauillian也不会强迫你转向一个新的测试技术, 相反, 它会无侵入性的与你喜爱的测试框架集成, 例如JUnit. 在测试领域, Arquillian可以说是Java EE的最佳伴侣了. 在Arquillian的网站上提供了非常好的文档和大量的示例, 同时, 在本系列文章中, 我们也会向你展示如何写基本的功能性测试.

工具

最后, 我们都有过这样的经历, 开发工具太过笨重, 缓慢, 还得先画一堆UML, 再通过复杂的向导一步步的往下走, 幸运的情况下才能够把向导走完, 才能做出个简单的东西来. 你的电脑还会像一辆小汽车正在被一个大卡车蹂躏一样的狂叫. 幸运的是, 这些情况都过去了. 现在主流的三大Java IDE都已经对开发Java EE组件有了很好的支持, 并且占用资源也不那么多. 开发Java EE应用也不再被限定在某一家的工具上了, 你可以按照自己的喜好来选择IDE, 还有应用服务器了. 通过使用诸如JBoss Forge等工具来创建一个基于Maven的Java EE项目现在轻而易举.

Spring和Java EE的功能对比

功能 Spring Java EE
依赖注入 Spring 容器 CDI
事务 AOP/Annotations EJB
Web 框架 Spring Web MVC JSF
AOP AspectJ (只支持Spring Beans) 拦截器
消息 JMS JMS/CDI
数据访问 JDBC Template / other ORM / JPA JPA
RESTful Spring Web MVC(3.x) JAX-RS
集成测试 Spring Test Framework Arquillian

作为总结, 我们可以负责任的说, 轻量级的Java EE能够提供Spring所提供的所有功能.

迁移方法

怎样做才是最好的方法来开始这个迁移呢? 一些人可能会直接扔掉遗留项目然后选择从头开始, 尽管这种方式很吸引人, 并且如果时间和资金没有限制的话这样做非常可行. 不过, 对于大多数现实情况来说, 这显然不可取. 我们需要一种渐进式替换的方法, 并且保证在这个过程当中不会出现什么灾难性的故障. 我们需要有一个迁移方案来带领我们一步步的向前推进, 这个方案不应限制我们自己的创造性, 并且, 它应该允许我们在任何一步的时候停下来. 由此, 我们得出如下的迁移方案:

  1. 升级Spring到最新版本
  2. 替换Spring中使用的过时的框架(例如ORM, Web框架等)
  3. 同时运行Spring 容器和应用服务器
  4. 完全替换掉Spring
  5. 移除Spring容器

在本系列的下一篇中, 我们会把重点放在如何迁移上面, 然后通过一个真实的示例项目向你展示迁移过程中的不同阶段. 届时会有实例代码和一个github上的项目提供给你, 这样, 你可以自己动手来尝试整个迁移过程.

关于作者

作者: Bert Ertman, Paul Bakker

Bert Ertman(@BertErtman) 是Luminis公司的院士, 该公司位于荷兰, 他同时还是荷兰Java用户组的领导人(该用户组大约3500名成员).

Paul Bakker(@pbakker)是Luminis公司的一名高级工程师, 他还是JBoss Seam, Arquillian 和Forge等项目的contribitor.

这两名作者都有着非常丰富的企业应用软件开发经验, 无论是使用J2EE之前的技术, 还是J2EE, Spring以及现代的Java EE技术. 他们对现代的企业软件开发技术有过很多的讨论, 也包括Spring和Java EE的对比, 现在, 他们相信, 不管是全新的企业应用软件, 还是迁移已经被广泛部属的遗留项目, Java EE 6都可以提供最好的技术. 本文的作者们已经在全球很多技术会议中介绍推荐过Java EE 6技术了, 包括J-Fall, Jfokus , Devoxx 和 JavaOne. 本系列文章是基于他们广受好评的在JavaOne 2011上的演讲迁移Spring到Java EE6的最佳实践写成.

Hibernate ORM 4.1.6.Final Release

Posted by Strong Liu    |       |    Tagged as Hibernate ORM Releases

Hibernate ORM version 4.1.6.Final has just been released. This is a minor bug fix release containing 16 bug fixes, See the changelog for the complete list of fixes. Specific fixes of note include:

  • HHH-6130 - The join map key was not correctly handled in the Criteria query, it was impossible to use criteria query by MapJoin#key, thanks the pull reuqest provided by Mattias Jiderhamn now it is fixed.
  • HHH-2808 - This is a five years old issue, and I fixed it in this release :D, Tair Sabirgaliev contributed the test case.
  • HHH-7108 - This issue is fixed by pull request from Janario Oliveira. With this fix, now it is possible to use @TypeDef with enums, and even a enum type property doesn't have @EnumType annotated, hibernate will still found the correct enum type by the implicitly type resolution. see the JIRA for more details.
  • HHH-7431 - Multi-Tenancy is an important feature that first introduced into hibernate since 4.0, and we're continue improving it based on feedback from community. Now the CacheKey's hashcode generation also considers tenant id (if there is any) and the equals method is also checking the tenant id. So it is possible to do some 2L cache optimization for different tenancy.
  • HHH-7426 - Using Dom4j as entity mode was an experimental feature in Hibernate and it had been removed in HHH-6330, sorry for the long delay of remove the relative content from Hibernate document.
  • HHH-7385 - We have upgraded to Gradle 1.1 as build tool, but you can still use the wrapper ./gradlew :)

As usual, the artifacts have been uploaded to the JBoss Nexus repository (and will be synchronized to Maven Central in a day or two) and the release bundles have been uploaded to the Hibernate SourceForge in both ZIP and TGZ formats.

Btw, the master branch has moved to JDK 7 in compile time and 4.1 branch remains using JDK 6, so if you want to contribute to hibernate (which is very welcome) please aware of this.

Thanks & Enjoy!

JBug Beijing, 26th July

Posted by Strong Liu    |       |    Tagged as Events

Yes, we will have a JBug in Thursday night next week, there will be three topics all relates to EAP 6 and Cloud.

I will talk OGM on that and show how to migrate an existing JPA project to use Infinispan and MongoDB as persistence Store. So, if you're in Beijing that time, welcome drop by.

you can find more details from here, though it is written in Chinese :)

Hibernate OGM 实战

Posted by Strong Liu    |       |    Tagged as Hibernate OGM

本文是上文的延续.

我想先介绍一下JBoss Developer Framework, 这是JBoss 社区推出的一个全新的项目, 旨在帮助开发者更好的理解和使用JBoss的相关技术, 它提供了大量的实例教程(50+), 视频, 文章的内容, 迁移向导(从Java EE 5到EE 6, 从Spring到Java EE)等, 教你一点点的学会Java EE 6相关的各种技术, 并且涵盖了 REST, HTML5 等新的热点技术.

本文即以kitchensink, 一个 JBoss Developer Framework 提供的实例为基础展开.

首先, 先让我介绍一下kitchensink吧, 最新的源代码可以在这里找到.

这个实例主要演示了如下几种技术:

  • Bean Validation 1.0
  • EJB 3.1
  • JAX-RS
  • JPA 2.0
  • JSF 2.0
  • CDI 1.0
  • Arquillian

想要运行这个实例的话, 你需要 JDK 6/7, Maven 3 和 JBoss AS 7

注意, 有一些依赖是只存在于JBoss 的maven 仓库中的, 所以, 可能需要对maven 的settings.xml文件做些配置, 添加JBoss maven仓库, 具体请参考这里这里

具体配置信息就不多说了, 上面给出的链接很详细, 这些也不是本文的重点, 接下来就让我们看看代码

首先是persistence.xml, 位于main/resources/META-INF/persistence.xml, 都是标准的位置

    <persistence version="2.0"
       xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
            http://java.sun.com/xml/ns/persistence
            http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
       <persistence-unit name="primary">
          <jta-data-source>java:jboss/datasources/KitchensinkQuickstartDS</jta-data-source>
          <properties>
             <property name="hibernate.hbm2ddl.auto" value="create-drop" />
             <property name="hibernate.show_sql" value="false" />
          </properties>
       </persistence-unit>
    </persistence>

可以看到, 这个文件定义的很简单, 就是定义了一个数据源和两个属性, 注意, hibernate.hbm2ddl.auto=creat-drop, 意思是在创建session factory的时候自动创建表结构, 关闭session factory的时候会自动drop掉表.

同时, 还可以看到main/resources目录中有import.sql这个文件, 当使用hibernate创建表结构的时候, 创建完成之后, hibernate会自动的导入import.sql文件, 这样可以添加一些初始数据.

另外, 引用的数据源是定义在main/webapp/WEB-INF/kitchensink-quickstart-ds.xml文件中的.

然后再来看看本文关注的另外一个方面, 实体定义:

    @Entity
    @XmlRootElement
    @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
    public class Member implements Serializable {
       /** Default value included to remove warning. Remove or modify at will. **/
       private static final long serialVersionUID = 1L;
    
       @Id
       @GeneratedValue
       private Long id;
    
       @NotNull
       @Size(min = 1, max = 25)
       @Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
       private String name;
    
       @NotNull
       @NotEmpty
       @Email
       private String email;
    
       @NotNull
       @Size(min = 10, max = 12)
       @Digits(fraction = 0, integer = 12)
       @Column(name = "phone_number")
       private String phoneNumber;    
       // getters / setters
    } 

很简单的一个entity mapping, 需要注意的是javax.xml.bind.annotation.XmlRootElement 是JAXB里面的一个annotation, 在这里可以把这个实体对象转化成xml表示

还有就是这个entity里面定义了一些BV的annotation, 具体可以参考Hibernate Validator的文档.

Okay, 本实例中用到的其它技术暂时不做介绍了, 下面终于该进入正题了

上面介绍过了, 这个实例使用的是JBoss AS7中的数据源(java:jboss/datasources/KitchensinkQuickstartDS), 那么, 我们接下来就是看看如何做很少的更改, 让这个实例使用insinispan做为存储替换掉数据源中使用H2

首先, 是添加依赖, 因为我们这里是想要通过Hibernate OGM, 把实体对象保存进Infinispan当中, 所以只需要加入下面的依赖项即可:

        <dependency>
            <groupId>org.hibernate.ogm</groupId>
            <artifactId>hibernate-ogm-core</artifactId>
            <version>4.0.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.6</version>
        </dependency>
        <dependency>
            <artifactId>infinispan-core</artifactId>
            <groupId>org.infinispan</groupId>
            <version>5.1.5.FINAL</version>
            <scope>provided</scope>
        </dependency>

接下来就是修改persistence.xml了, 在上文曾经提到过, Hibernate OGM本身也是一个JPA的实现, 但是由于JBoss AS7默认集成的是Hibernate ORM 4, 所以我们需要在persistence.xml中显示的声明我们希望使用Hibernate OGM作为JPA的实现.

     <persistence version="2.0"
       xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
            http://java.sun.com/xml/ns/persistence
            http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
       <persistence-unit name="primary">
           <provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
           <properties>
               <property name="hibernate.ogm.datastore.provider"
                         value="org.hibernate.ogm.datastore.infinispan.impl.InfinispanDatastoreProvider"/>
               <property name="hibernate.ogm.infinispan.configuration_resourcename" value="infinispan-ogm-config.xml"/>
           </properties>
           <class>org.jboss.as.quickstarts.kitchensink.model.Member</class>
       </persistence-unit>
    </persistence>

在这个更新过的文件中我们可以看到如下的变化:

  • 使用 org.hibernate.ogm.jpa.HibernateOgmPersistence 作为JPA的实现
  • 去掉了datasource的引用, Hibernate OGM不使用RMDBS
  • 通过 hibernate.ogm.datastore.provider指定使用infinispan作为data store
  • 通过 hibernate.ogm.infinispan.configuration_resourcename 属性指定infinispan的配置文件

src/main/webapp/WEB-INF/kitchensink-quickstart-ds.xml 这个文件已经没有用了,可以删掉.

至此, 配置方面就需要这么多的改动, 很简单吧

现在, 我们已经通过使用Hibernate OGM, 把底层的存储从RMDBS切换成了Infinispan, 但是, 由于RMDBS和NO-SQL 本质的不同, 我们还需要做一些修改.

使用uuid作为主键

在使用Hibernate ORM的时候, 我们通常会使用@GeneratedValue来得到数据库自动生成的id, 并且, 通常我们建议把id设置成long类型的以得到更好的性能.

可是, 如果使用的是NO-SQL的话, 如果是K-V类型的NO-SQL的话,他们是没有一个主键的概念的 (mongodb等文档型数据库是会提供自动生成的id的), 所以为了统一, 并且保证全局唯一, 在Hibernate OGM中我们建议使用UUID作为主键生成策略, 并且, 在Hibernate ORM中早已提供了此种策略, 我们在这里可以直接使用.

org.jboss.as.quickstarts.kitchensink.model.Member#id 需要修改成如下的样子.

   @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    private String id;

注意, 改变了id的类型之后, 我们还需要修改 org.jboss.as.quickstarts.kitchensink.rest.MemberResourceRESTService#lookupMemberById

        @GET
    	@Path("/{id:[0-9][0-9,\\-]*}")
        @Produces(MediaType.APPLICATION_JSON)
        public Member lookupMemberById(@PathParam("id") String id) {
            Member member = repository.findById(id);
            if (member == null) {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
            return member;
        }

这个方法提供了一个通过REST接口来查找Member的功能, 但是因为我们已经把id的类型改成了String, 所以我们需要对@Path做一些修改, 让它能够接受字符 -- @Path("/{id:[0-9,a-z][0-9,a-z,\\-]*}")

查询

因为Hibernate OGM还是一个很年轻的项目, 有一些功能还没有完全的实现, 例如, 我们在JPA/Hibernate中经常使用的Criteria 查询, 但是幸好, Hibernate OGM和Hibernate Search有很好的集成, 我们可以使用Hibernate Search来完成这部分工作.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findByEmail 方法是通过email来查询一个Member, 内部实现是通过Criteria来查询的.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findAllOrderedByName 方法是查询所有的Member并且按照name排序, 同样是使用的Criteria.

这两个方法的实现很简单, 相信大家都能看懂.

那么, 我们现在就需要使用Hibernate Search替换掉这两个方法.

首先, 把Hibernate Search相关的依赖添加进pom当中

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-orm</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-engine</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-analyzers</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-search-infinispan</artifactId>
            <version>4.2.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-lucene-directory</artifactId>
            <version>5.1.5.FINAL</version>
        </dependency>

这里所依赖的org.hibernate:hibernate-search-infinispanorg.infinispan:infinispan-lucene-directory提供了一个Lucene的Directory的实现, 可以把Lucene的index保存在infinispan当中, 从而实现比保存在文件系统当中更好的性能和可扩展性(因为Infinispan是一个分布式的数据网格系统).

在persistence.xml当中,我们需要添加上两个Hibernate Search的属性 这里所依赖的org.hibernate:hibernate-search-infinispanorg.infinispan:infinispan-lucene-directory提供了一个Lucene的Directory的实现, 可以把Lucene的index保存在infinispan当中, 从而实现比保存在文件系统当中更好的性能和可扩展性(因为Infinispan是一个分布式的数据网格系统).

在persistence.xml当中,我们需要添加上两个Hibernate Search的属性

<property name="hibernate.search.default.directory_provider" value="infinispan"/>
    <property name="hibernate.search.infinispan.configuration_resourcename" value="infinispan.xml"/>
第一个hibernate.search.default.directory_provider告诉Hibernate Search使用Infinispan作为Lucene Index Directory, 第二个指定了Hibernate Search所使用的Infinispan的配置文件.

接下来, 我们需要对org.jboss.as.quickstarts.kitchensink.model.Member做一些改动, 添加上Hibernate Search所需要的Annotation.

   @Entity
    @XmlRootElement
    @Indexed
    @Table(uniqueConstraints = @UniqueConstraint(columnNames = "email"))
    @Proxy(lazy = false)
    public class Member implements Serializable {
       /** Default value included to remove warning. Remove or modify at will. **/
       private static final long serialVersionUID = 1L;
    
       @Id
       @GeneratedValue(generator = "uuid")
       @GenericGenerator(name = "uuid", strategy = "uuid2")
       private String id;
    
       @NotNull
       @Size(min = 1, max = 25)
       @Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
       @Fields({
    		   @Field(analyze = Analyze.NO, norms = Norms.NO, store = Store.YES, name = "sortableStoredName"),
    		   @Field(analyze = Analyze.YES, norms = Norms.YES)
       })
       private String name;
    
       @NotNull
       @NotEmpty
       @Email
       @Field(analyze = Analyze.NO)
       private String email;
    
       @NotNull
       @Size(min = 10, max = 12)
       @Digits(fraction = 0, integer = 12)
       @Column(name = "phone_number")
       @Field(analyze = Analyze.NO)
       private String phoneNumber;

第一个是@Indexed, 它告诉Hibernate Search, 这个实体类是需要被索引的, 还有@Field (在name和phoneNumber属性上)告诉Hibernate Search这两个属性是需要被索引的 ,具体信息请参考Hibernate Search 文档

想要在保存一个实体对象的时候,让Hibernate Search自动索引的话, 我们需要使用org.hibernate.search.jpa.FullTextEntityManager来替换javax.persistence.EntityManager.

org.jboss.as.quickstarts.kitchensink.service.MemberRegistration#register这个方法调用了EntityManager#persist, 那么我们需要做的就是把这个类中的@Inject private EntityManager em;换成 @Inject private FullTextEntityManager em; 这里, 之前的EntityManager和现在的FullTextEntityManager都是由CDI负责自动注入的, 可是, CDI并不知道如何创建Hibernate Search所特有的FullTextEntityManager, 所以, 为了让自动注入工作, 我们需要一个Producer.

而这个Producer, 在我们的实例当中就是org.jboss.as.quickstarts.kitchensink.util.Resources, 它已经存在了, 用来提供EntityManager的注入和Logger的注入(org.jboss.as.quickstarts.kitchensink.util.Resources#produceLog)

我们只需要添加Hibernate Search的内容 (CDI相关内容可以参考Weld文档)

	@Produces
	public FullTextEntityManager getFullTextEntityManager() {
		return Search.getFullTextEntityManager( em );
	}

	@Produces
	@ApplicationScoped
	public SearchFactory getSearchFactory() {
		return getFullTextEntityManager().getSearchFactory();
	}

	@Produces
	@ApplicationScoped
	public QueryBuilder getMemberQueryBuilder() {
		return getSearchFactory().buildQueryBuilder().forEntity( Member.class ).get();
	}

Okay, 现在万事俱备, 我们可以使用Hibernate Search来替换org.jboss.as.quickstarts.kitchensink.data.MemberRepository中那两个使用了Criteria的方法了

注意, org.jboss.as.quickstarts.kitchensink.data.CriteriaMemberRepository#findById 是需要把参数的类型从long改成String的, 除此之外, Hibernate OGM是支持直接使用id进行查询的, 所以不需要修改.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findByEmail 方法是通过email来查询一个Member, 内部实现是通过Criteria来查询的.

org.jboss.as.quickstarts.kitchensink.data.MemberRepository#findAllOrderedByName 方法是查询所有的Member并且按照name排序, 同样是使用的Criteria.

@Inject
	private QueryBuilder queryBuilder;

	@Inject
	private FullTextEntityManager em;

	@Override
	public Member findById(String id) {
		return em.find( Member.class, id );
	}

	@Override
	public Member findByEmail(String email) {
		Query luceneQuery = queryBuilder
				.keyword()
				.onField( "email" )
				.matching( email )
				.createQuery();
		List resultList = em.createFullTextQuery( luceneQuery )
				.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID )
				.getResultList();
		if ( resultList.size() > 0 ) {
			return (Member) resultList.get( 0 );
		}
		else {
			return null;
		}
	}

	@Override
	public List<Member> findAllOrderedByName() {
		Query luceneQuery = queryBuilder
				.all()
				.createQuery();
		List resultList = em.createFullTextQuery( luceneQuery )
				.initializeObjectsWith( ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.FIND_BY_ID )
				.setSort( new Sort( new SortField( "sortableStoredName", SortField.STRING_VAL ) ) )
				.getResultList();
		return resultList;
	}

可以看到, 在这个新的类中, 我们首先使用CDI自动注入了Hibernate Search的QueryBuilder和FullTextEntityManager (参见上面修改后的org.jboss.as.quickstarts.kitchensink.util.Resources 工厂类)

在findById方法中, FullTextEntityManager#find实际上是代理给Hibernate OGM来处理的.

而在其余两个方法中, 则完全是使用Hibernate Search的Query API创建了查询条件, 然后交过Lucene来搜索的, 还记得我们上面修改了Member类, 在它被保存的时候创建Lucene索引的吧 (另, 上面提到过, 这个索引也是保存在infinispan当中的)

部属到JBoss AS 7

方便的是, 我的同事Hardy已经准备好了这样一个修改过的项目, 你可以直接从前面的链接中下载到本文中所介绍到的项目的源代码.

这是一个maven项目, 所以你需要先安装好maven, Hardy还很贴心的在pom里面使用了cargo插件, 所以你不需要下载JBoss AS 7了(尽管我还是推荐你下载一个看看, 很值得的), cargo会自动帮你下载, 并且完成部属等事情.

  • 编译 $ mvn clean package
  • 运行 $ mvn cargo:run
  • 测试(基于Arquillian) $mvn test

跑起来之后你可以访问http://127.0.0.1:8080/ogm-kitchensink 来看看具体的效果.

另外, 如果你尝试输入一个不合法的名字, 电话号码或者email地址的话, 你会看到错误提示, 这就是Bean Validation所自动帮你提供的输入校验, 来看看代码, 还记得 org.jboss.as.quickstarts.kitchensink.model.Member 类中的属性上定义的Bean Validation Annotations么?(下面代码中我只保留了这部分的annotation)

        @NotNull
	@Size(min = 1, max = 50)
	@Pattern(regexp = "[A-Za-z ]*", message = "must contain only letters and spaces")
	private String name;
	
	@NotNull
	@NotEmpty
	@Email
	private String email;

        @NotNull
	@Size(min = 10, max = 12)
	@Digits(fraction = 0, integer = 12)
	private String phoneNumber;

Okay, 现在你已经有了一个使用Hibernate OGM + NO-SQL的程序跑在 JBoss AS7上面了

另外, JBoss AS 7的启动速度很快, 非常快!

23:27:10,050 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: 
JBoss AS 7.1.1.Final "Brontes" started in 2009ms - Started 133 of 208 services (74 services are passive or on-demand)

上面的日志是在我的机器上启动的速度, 应该和Tomcat也差不多了吧, 但是JBoss AS7 可是一个完整的通过Java EE6认证的应用服务器, 而Tomcat只是一个servlet container.

Hardy还非常贴心的提供了一个小工具来帮助我们自动的插入数据, 这就是在项目根目录下的member-generator.rb

要使用这个工具的话, 你首先需要安装(gem install)如下的gem:

  • gem install httparty
  • gem install nokogiri
  • gem install choice

然后就可以通过执行下面的命令来创建一些测试数据了.

ruby member-generator.rb -a http://localhost:8080/ogm-kitchensink -c 20

部属到 Openshift

openshift是Redhat所提供了一个Paas服务, 它背后的技术已经开源, 可以在这里找到其源代码和众多的实例项目.

openshift同时提供免费的服务和付费的服务, 对于我们简单的玩玩来讲, 免费的账户已经足够了, 但是如果你的项目是一个正式上线的项目, 还是推荐使用收费的带有支持的服务的.

openshift所支持的平台包括:

  • Java
  • Ruby
  • Node.js
  • Python
  • PHP
  • Perl

并且它还提供了开箱即用的数据库支持, 包括RMDBS和NO-SQL

  • Mysql
  • PostgreSQL
  • Mongodb

例如我们可以在常见部属一个自己的wordpress或者drupal程序, 很方便.

对于我们Java程序员来讲, openshift最重要的是它内置了JBoss AS7 (和其商业版本JBoss EAP6)的支持, 所以, 对于想要尝试Java EE6的童鞋来说就有福了, 你可以直接使用此服务.

首先, 第一步没得说, 先到这里来创建一个免费的账号.

然后从这里下载客户端, Linux / Mac / Windows都有对应的客户端供下载.

接着, 你需要创建一个domain, 命令为 rhc domain create -n ${my domain name}, 之后, 你所有部属到openshift上的应用都会是http://${app name}-${domain name}.rhcloud.com的格式(免费账户可以创建3个程序, 并且你可以使用自己的域名).

然后该创建应用了, 我们想要把这个程序部属到JBoss AS7上, 使用下面的命令rhc-create-app -a ${your app name} -t jbossas-7 --nogit, 命令完成之后, 你会从输出中看到有一个git repo的地址, 现在, 你可以把这个地址作为一个remote添加到之前clone出来的ogm-kitchensink项目当中

git remote add openshift ${repo-url}

然后, 把ogm-kitchensink 推送到openshift提供的git repo当中

git push -f openshift master

最后, 你就可以访问你的应用了, 地址如同之前所说的, 是

http://${your app name}-${your domain name}.rhcloud.com

我创建了一个demo, 可以访问http://ogm-stliu.rhcloud.com.

Hibernate ORM 4.1.5.SP1 release

Posted by Strong Liu    |       |    Tagged as Hibernate ORM Releases

There is a blocker bug in the just released 4.1.5.Final, it would cause Natural id cache not usable with 2LC.

I feel sorry for this mistake :(

Full change log can be found here, in this release, I also merged a pull request provided by Rob Worsnop, which fixes a five years old issue, with this fix, now you can use filter within sub-class. Thanks Rob Worsnop.

As usual, the artifacts have been uploaded to the JBoss Nexus repository (and will be synchronized to Maven Central in a day or two) and the release bundles have been uploaded to the Hibernate SourceForge in both ZIP and TGZ formats.

Last but not the least, I would like to give special thanks to Lukasz Antoniak and Adam Warski, Hibernate Envers Developers from community and who are helping us on Envers module testing on DB matrix, Lukasz (with Adam's help) worked tirelessly and fixed ALL envers test failures we found, Great work and keep contributing :D

Hibernate ORM 4.1.5 Release

Posted by Strong Liu    |       |    Tagged as Hibernate ORM Releases

Hibernate ORM version 4.1.5 has just been released. This is a minor bug fix release containing 20 bug fixes, See the changelog for the complete list of fixes. Specific fixes of note include:

  • HHH-4394 - This issue was created 5 years ago, and watched by 12 community members, now it is finally resolved :D, it has a long discussion history, so I'd not summary it here, check the link and find the details.
  • HHH-7440 / HHH-7370 / HHH-7369 - The dialect specific limit clause generation were causing lots of issues, esp with MS SQL Server dialect, now, Lukasz has redesigned it with a much cleaner API/code, and also resolved those issues.
  • HHH-7431 - Contributed by Eric Dalquist, he found NaturalIdCacheKey was using pre-generated toString, and this caused high memory footprint.
  • HHH-7426 - cacheable attribute in orm.xml was not handled.

I'd like take this opportunity to thank our awesome community, most hibernate developer are working on the new metamodel stuff, which we're hoping to get into Hibernate 5, so, the community contributed most of bug fixes / improvements of this release, YOU ARE DOING GREAT, THANKS !

Enjoy!

Hibernate OGM 带你进入NO-SQL的世界, 用你熟悉的方式

Posted by Strong Liu    |       |    Tagged as Hibernate OGM

Sorry, this is an Chinese only post :D

现在 NO-SQL 的概念被互联网界炒的很火, 貌似哪个网站要是不用上这个, 不推出自己的 K-V 存储都不好意思出来混了. 现在网上有很多介绍各种各样 NO-SQL 的内容, 所以本文不注重与此, 但是, 作者想要强调的是, 做技术有个很重要的原则是不跟风, 选择合适自己的技术最重要. NO-SQL 不是银弹, 它并不能(起码目前)完全取代关系型数据库, 可参考这篇文章.

作为Java程序员的我们, 已经(应该)很熟悉RMDBS了, 并且, 有一套很完善(有人管这叫笨重, 但是取决于你的项目类型, 如果是一个startup, 那么自然笨重, 可是如果是给铁道部开发的系统, 再怎么谨慎也不为过)的开发方法论了.

我们所熟悉的 Hibernate 现在已经改名叫做 Hibernate ORM 了, 因为现在Hibernate team出品的项目并不仅仅是一个O/R Mapping的框架了, 我们还有提供了完善的搜索支持的Hibernate Search和用于校验的Hibernate Validator, 以及本文介绍的Hibernate OGM, 所以, 需要一个更准确的命名.

对于一个Java项目, 一般先创建好领域模型, 然后定义实体对象以及他们之间的关系, 剩下的事情Hiberate ORM会帮你全都打理好, 很简单也很熟悉.

那么, 如果想要用NO-SQL呢?

什么是Hibernate OGM

OGM == Object/(Data) Grid Mapping

ORM的概念大家都很熟悉了, 那么OGM的意思是对象--数据网格 映射, Hibernate OGM同样是一个JPA的实现, 所以你可以用熟悉的JPA API (当然, 或者Hibernate API)来把实体模型存储进NO-SQL中, 和执行查询等操作.

通过使用Hibernate OGM, 我们可以把现有的, 基于JPA/Hibernate ORM的项目不加改动的从RMDBS切换到NO-SQL之上.

Hibenrate OGM并不是从头搭建的全新项目, 事实上, 它跟牛顿一样, 也站在了巨人的肩膀上.

通过使用Hibernate ORM的核心, 提供了完整的JPA支持, 以及实体对象生命周期管理等功能, 确保了稳定性.

通过Hibernate Search提供了完善的全文检索功能, 这也正是NO-SQL相比RMDBS所缺少的部分.

Infinispan, 一个高性能, 分布式的网格存储引擎, 则是Hibernate OGM所支持的标准NO-SQL实现(当然不仅限于Infinispan), 同时, infinispan还可以作为Hibernate Search所需要是lucene index的存储.

当程序和存储(无论RMDBS 还是 NO-SQL)打交道的时候, 核心问题实际上就是两个: 存进去, 取出来.

领域模型如何被持久化进NO-SQL store

抛开各种NO-SQL的实现方式, 例如K-V, Documents, Graph等, 我们可以把NO-SQL简化成我们容易理解的 HashMap .

那么, 现在问题是, 如果你有一个简单的实体类, 你该如何把它存储进一个HashMap呢?

对于RMDBS来说, 这不是个问题, 每个属性对应表中的一个column就好了.

可是如果是NO-SQL的话, 情况就复杂一些了, 最简单的方式是用这个实体类的类名和其id作为key(注意, 这里的HashMap是个概念, 指代NO-SQL store, 所以其是全局的), 把这个实体对象(经过序列化)作为value来存储.

可是, 这个方案会有些问题:

1. NO-SQL中存储的内容和你的领域模型紧密耦合, 如果后面领域模型有改动, 那么在反序列化之前存储的内容的时候会出问题. 2. 别的系统可能会无法使用该NO-SQL中的数据, 例如, 你还有一个ruby的系统, 那么两个系统之间可能会无法共享一个NO-SQL, 因为ruby没办法反序列化Java Object. 3. 序列化的时候, 可能会把整个对象图 (因为对象之间相互关联) 都保存起来

RMDBS在此则有一些比较好的原则:

实体对象和存储内容解耦 (所以才有O/R Mapping) 使用基本类型, 例如varchar, integer等 主键确保唯一性 外键确保一致性

Hibernate OGM在把实体对象持久化的时候, key是这样构造的 -- 表名,主键列名,主键值, 而value呢, 是存储成一个基本数据类型组成的元组的, 可以相像成Map<String,Object>, 这个map的key是属性名, 而值则是属性值的基本类型表示, 例如 URL 会表示成一个 String. 我们相信使用基本类型更有助于数据的可移植性.

实体之间的关联关系, 则是另外一个让人头痛的地方 -- 如果自己处理实体的存储的话.

但是, 得益于JPA/Hibernate早已定义的很完善的关联关系处理方案, 这个在Hibernate OGM中也很简单, 也是存放在元组当中的, 其中key是由 -- 关联表名,外键列名,外键值 组成的. 这样的结构保证了, 关联关系可以由实体中保存的信息获取到.

如下图所示:

上图中, 如果想找到 user id为1的人的名字和地址的话, 那么就可以:

1. 通过key tbluser,userIdpk,1 得到 {userId_pk=1, name=Emmanuel} 2. 通过key tbluseraddress,userIdfk,1 得到 { { userId_fk=1, addressId_fk=3}, { userId_fk=1, addressId_fk=5} } 得到与此user所关联的两个address的id分别为3和5. 3. 再分别构造两个key tbladdress,addressIdpk,3 和 tbladdress, addressIdpk,5得到此人的地址.

所有上面介绍的这些都是发生在Hibernate OGM内部的, 用户所需要的仅仅是:

entitymanager.persist(user);
entitymanager.persist(address);
entitymanager.find(User.class, 1);

查询

目前, 绝大多数应用对于存储的需求都是读大于写的, 而传统的关系型数据库, 由于其有完善的关系数学模型保证, 所以能够提供完善的查询, 甚至多表级联查询(join), 而另一方面, NO-SQL因为起schema free / K-V的本质, 基本上只能通过key来查找.

如上面例子所示, 对于id已知的情况, 那么NO-SQL表现的很好, 可是, 如果想根据地址来搜索的话, 那就比较麻烦了.

而Hibernate Search, 正是在此起的作用, 通过在实体对象添加一些annotation, 定义要索引的字段, 当你通过Hibernate ORM/OGM (Hibernate Search和这两个都集成的很好) 保存或更新实体对象的时候, Hibernate Search会自动的对此对象创建lucene索引, 之后, 就可以通过Hibernate Search Query API 或者 Lucene Query API对保存的实体对象进行全文索引了.

继续阅读Hibernate OGM实战

back to top