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实战