技术开发 频道

Hibernate vs. Rails:数据持久化


【IT168 技术文档】

    关于Rails的各种议题如雨后春笋般地涌现,就像许多关注Java的家伙一样,如Bruce Tate和David Geary,我目前正在关注一个新的Web构架:Rails。我最感性趣的是Rails的ORM (Object Relational Mapping)工具,活动记录(ActiveRecord)。由于一项新技术的选择总会产生一些机会成本(opportunity costs),因此,我写了这篇关于Rails和另一个ORM工具Hibernate对比的文章。本文总结了我从Rails中学到了什么,并使Rails和我非常熟悉的技术Hibernate进行一翻较量。

背后的故事

    最近,一个新的框架:Ruby on Rails已经成为开发人员所关注的热点话题之一,而且关于它的讨论正在逐渐升温。Rails是一个基于MVC模式的web框架,从概念上讲类似于Struts或Webwork,但Rails和它们不同的是,Rails是使用脚本语言Ruby写的,而Struts和Webwork是使用java语言写的。Rails不仅仅是一个web框架,它还提供了许多集成技术,如内嵌的代码自动生成技术和本身提供的ORM (Object Relational Mapping)工具,以及活动记录(ActiveRecord)。由于Rails框架集成了大量的辅助开发的工具,因此,Rails打出的口号是“使用Rails开发web程序至少比使用传统的java框架开发web程序快十倍”。
现在对于Rails的态度分为两个阵营。一个阵营是Rails的支持者,另一个阵营是Rails的反对者。前者将Rails捧上了天,而后者则将Rails打入了十八层地狱。至于持哪种态度,也许要看你热衷于的语言是以J还是以R开头。

概述

    根据我所做的研究,Rails好象非常适合处理单表或是单个的对象。这一特点使Rails非常适合处理简单模型。要想使Rails拥有更高的效率,必须使数据库以及表、字段等按着Rails的约定设置(如要按着Rails的约定起名)。Hibernate在这一点上就比Rails强大得多,它能够处理更复杂的数据模型,可以处理任何已经存在的数据库。因此,Hibernate拥有更多的特性,是一个更成熟的产品。但它们也有各自的特点,接下来就让我们揭开它们的面纱,看看它们有什么不同。下面列出了我在这篇文章中要回答的概念和问题。
    1、基本体系结构模式 — Hibernate和Rails各自使用完全不同的ORM模式。这意味着什么?
    2、显式 – Hibernate和Rails使用不同的方式定义映射。Hibernate使用显式的方式定义映射,而Rails使用了隐式的方式定义映射。这有什么用意?它有多么重要?
    3、关系– 它们都支持关系,那么如何定义关系呢?
    4、传播性持久化模型 – 它们所提供的工具是如何处理持久对象的?
    5、查询语言 – 你如何查找对象?

基本体系结构模式

    Rails活动记录和Hibernate最大的差异是它们所使用的模式不同。很显然,Rails使用的是活动记录模式,而Hibernate使用的是数据映射模式。下面就阐述这两种模式到底有什么不同。

活动记录模式

    活动记录是一个封装了数据库表或视图的某一行的对象,并且可以通过这个对象对数据库进行访问。同时还可以通过这个对象将数据库中表或视图的关系体现出来。这就意味着活动记录有查找实例的“类”方法,以及每个实例都可以进行保存、更新和删除操作。因此,Rails在简单的数据模型上将会有更好的表现。

数据映射模式

    数据映射模式是一个映射层,在这一层将对象和数据库分开,以保证对象层、映射层和数据层的相互独立。数据映射一般使用一致性映射来维护对象和数据库之间的关系。另外,它还提供了一个跟踪数据对象变化的功能,以确保数据和对象的一致性。

模式所带来的影响

    上述讨论了它们的基本差异。这些差异所带来的影响非常明显。活动记录(Rails)非常容易理解和使用,但是它对于更高级、更复杂的应用却显得无能为力。下面让我们看看这两个框架的不同之处。为了举例说明它们的不同,我们将使用一个叫“Deadwood”的工程中的代码来说明。
    显式定义的价值


    那么什么是显式定义的价值呢?Rails的活动记录的关键特性之一就是不需要为类指定字段,而这一切由Rails根据数据表中的字段动态确定。也就是说,当数据表改变后,无需改变类定义即可和数据表保持一至。如果你有一个叫“miners”的表,它的定义如下:

create table miners ( id BIGINT NOT NULL AUTO_INCREMENT, first_name VARCHAR(255), last_name VARCHAR(255), primary key (id) )
    相应的ruby类(miner.rb)和这个类的用法如下:

class Miner < ActiveRecord::Base end miner.first_name = "Brom"
    和Rails不同,Hibernate类(Miner.java)需要指定字段、getters和setters方法以及xdoclet标签,代码如下:
package deadwood; /** * @hibernate.class table="miners" */ public class Miner { private Long id; private String firstName; private String lastName; /** * @hibernate.id generator-class="native" */ public Long getId() { return id; } public void setId(Long id) { this.id = id; } /** * @hibernate.property column="first_name" */ public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } /** * @hibernate.property column="last_name" */ public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } miner.setFirstName("Brom");
    在这之前我已经提到,Rails和Hibernate都需要指定字段名。所不同的是,Hibernate的字段是在代码中指定,而Rails的活动记录是根据数据表的字段动态指定的。即Hibernate是静态的,而活动记录是动态的。所以当你查看Ruby代码时,你将看到…

class GoldClaim < ActiveRecord::Base end
    Rails虽然在映射字段方面很简单,但问题是我们怎么知识对象中有多少个字段?当然,为了知道答案,你必须启动MySQL或其它数据库的前端管理程序,查看数据库表中到底有哪些字段。当数据库中的表或视图比较少时,这当然不是问题。但是当你的工程中的表或视图超过40个或更多时,这将是非常令人痛苦的事情。至于开发人员具体喜欢哪种方式,这可能取决于它们的偏好。但可以肯定地说,将要映射的字段显式地定义在代码中将更容易理解和修改。
关系

    在最后的部分,我们看到的Miner类是一个映射到一个单表miners的类。除此之外,ORM还支持将表之间的关系映射成内存对象,但是Hibernate和Rails在处理关系映射时是不同的。它们两都可以处理大多数的映射关系。下面列出了它们所支持的映射关系。
    1、 多对一 - belongs_to
    2、 一对一 - has_one
    3、 一对多(set) - has_many
    4、 多对多(set) - has_and_belongs_to_may
    5、 单表继承
    6、 组合(每个表对应一个以上的对象)

    下面让我们看一个关于“多对一”关系的例子。我们将要扩展Deadwood例子的第一部分。我们加入一个GoldClaim对象,以使这个对象和Miner形成多对一的关系。这就意味着miners表有一个外键:gold_claim_id。这个外键表示gold_claims表的一行。
    (Java)
public class Miner { // Other fields/methods omitted private GoldClaim goldClaim; /** * @hibernate.many-to-one column="gold_claim_id" * cascade="save" */ public GoldClaim getGoldClaim() { return goldClaim; } public void setGoldClaim(GoldClaim goldClaim) { this.goldClaim = goldClaim; } } (Rails) class Miner < ActiveRecord::Base belongs_to :gold_claim end
    以上代码在功能上并没有什么不同,它们做了同样的事情。只是Hibernate显式地映射了外键,以及相应的动作(这个将在下面介绍)。保存Miner将同时保存和它相关的GoldClaim。但是更新和删除Miner将不会影响到GoldClaim对象。

传播性持久化

     Non-demo应用程序倾向于和深层次的对象集一起工作。ORM提供一种可以探测内存对象和数据库变化的方法是非常重要的。Hibernate所提供的方法是级联持久化。Rails在这一方面的功能好像比较有限,它是基于关系类型的。例如,Rails默认情况下好像是通过belongs_to来模仿Hibernate的cascade=”save”行为。
miner = Miner.new("name" => "Brom Garrott") miner.gold_claim = GoldClaim.new( "name" => "Western Slope") miner.save # This saves both the Miner and GoldClaim objects miner.destroy # Deletes only the miner row from the database
    Hibernate的删除操作为所有的关系类型提供了很多不同的级联行为(cascading behaviors),以使它拥有更大的弹性。例如,设置 cascade=”all” 将使GoldClaim在保存,更新和删除的同时也操作Miner。

Miner miner = new Miner(); miner.setGoldClaim(new GoldClaim()); session.save(miner); // Saves Miner and GoldClaim objects. session.delete(miner); // Deletes both of them.
   为了公平,你也可以使用Rails做同样的事情,但你必须通过回调(callback)来定制Miner对象的行为。在Rails中,级联操作只能在”has_one”中使用。以下的代码并不能变化GoldClaim的name属性。

miner = Miner.find(@params['id']) miner.gold_claim.name = "Eastern Slope" miner.save
    如果要想更新name,可按如下代码所示

class GoldClaim < ActiveRecord::Base has_one :miner end claim = GoldClaim.find(@params['id']) claim.miner.name = "Seth Bullock" claim.save # Saves the miner's name
   使用 cascade = “save-update:”,你可以在任何映射关系中使用这个行为,而不管外键是否基于传播性持久化的关系类型。还是级联类型,这将使Hibernate变得更强有力。接下来,让我们看看每一个框架如何查找已经持久化了的对象。
查询语言

    Hibernate和Rails在查询语言、查询能力和用法上有许多相似的地方。从本质上说,Rails使用SQL进行查询,这是从数据库中输入、输出数据的标准方法。另外,通过动态finder方法的使用,可以使开发人员利用这些方法写一些简单的查询,我认为,这相当于为Rails增加了一种“mini”语言。

    另一方面,Hibernate有它自己的面象对象查询语言(Hibernate Query Language – HQL),这种查询语言和SQL非常类似。但和SQL不同的是,HQL可以让开发人员根据对象和属性,而不是表和列来表达他(她)们的查询。Hibernate将HQL翻译成优化的相应数据库的SQL,显然,发明一种新的查询语言非常重要的。HQL丰富的表现力和强大的功能是Hibernate的卖点之一。现在,让我们看一些关于这方面的例子。

Rails Finders 方法

   Rails的finder方法使用是非常简单的。Rails可以为对象动态加入finder方法,然后再将其加翻译成SQL。如在下面的例子中,我们想查miners的first_name和last_name。

    @miners = Miner.find_by_first_name_and_last_name("Elma", "Garrott")

    除此之外,活动记录还提供了finder方法让你输入SQL语句。

# Returns only the first record @miner = Miner.find_first("first_name = ?", "Elma") # Finds up to 10 miners older than 30, ordered by age. @miners = Miner.find_all ["age > ?", 30], "age ASC", 10 # Like find all, but need complete SQL @minersWithSqA = Miner.find_by_sql [ "SELECT m.*, g.square_area FROM gold_claims g, miners m " + " WHERE g.square_area = ? and m.gold_claim_id = g.id", 1000]
   用HQL查询对象

    上述可知,HQL可以表示成对象和列。而对于简单的查询,Rails更容易定义。当你必须使用对象来查询时,HQL是非常方便的。下面让我们来看一个例子。
// Find first Miner by name Query q = session.createQuery("from Miner m where m.firstName = :name"); q.setParameter("name", "Elma"); Miner m = (Miner) q.setMaxResults(1).uniqueResult(); // Finds up to 10 miners older than 30, ordered by age. Integer age = new Integer(30); Query q = session.createQuery( "from Miner m where m.age > :age order by age asc"); List miners = q.setParameter("age", age).setMaxResults(10).list(); // Similar to join query above, but no need to manually join Query q = session.createQuery( "from Miner m where m.goldClaim.squareArea = :area"); List minersWithSqA = q.setParameter("area", new Integer(1000)).list();
结论

    本文并未覆盖持久化的所有内容,我们到现在为止已经发现了一些这两个框架的不同之处。希望读者能对它们的“机会成本”有更好的认识。本文已经覆盖了Rails和Hibernate的基本体系结构模式,还有如何应用这两个框架中其本的持久化类。对于关系来说,ORM是可能拥有更多的映射关系。但本文只覆盖了大多数开发人员使用的基本映射关系。

   Rails和Hibernate在查询语言上是有很大差异的。在它们的查询语言上做一个全面的比较是不可能的,一般地,Rails从单独的表或对象上查询数据是非常快的,而Hibernate更适合处理关系复杂的查询。Rails使用大多数开发者都熟悉的SQL,而Hibernate提供了一种新的面向对象的查询语言:HQL。
0
相关文章