`

ORM是一种很失败的设计,属于典型的反模式

阅读更多

作为Java和Ruby程序员与架构师的Yegor发表一篇博文:ORM Is an Offensive Anti-Pattern,认为ORM是一个可怕的反模式,违反了所有的面向对象原则,撕裂了对象,将它们变成哑巴和被动的数据袋,没有任何借口在任何应用程序中使用ORM,无论是成千上万的小型Web应用或企业级的基于数据表的CRUD操作系统(ORM包括Java的Hibernate/JPA,python的django,),那么取而代之是什么?会讲SQL的对象 (SQL-speaking object)。

ORM是如何工作的
对象关系数据库ORM技术或模式是使用面向对象技术如Java访问一个关系数据库,每个语言都有ORM实现,如Java的Hibernate,Ruby的active record, PHP的Doctrine, Python的 SQLAlchemy,在java中,ORM甚至被设计为标准,如JPA。

首先,让我们看看ORM是如何工作的,。 让我们使用Java,PostgreSQL,Hibernate。 假设我们有一个表在数据库中,称为post :

+-----+------------+--------------------------+
| id  | date       | title                    |
+-----+------------+--------------------------+
|   9 | 10/24/2014 | How to cook a sandwich   |
|  13 | 11/03/2014 | My favorite movies       |
|  27 | 11/17/2014 | How much I love my job   |
+-----+------------+--------------------------+


现在,我们需要为这个表产生Java应用的CRUD方式(增删改查),首先,我们曾经一个Post类:

@Entity
@Table(name = "post")
public class Post {
  private int id;
  private Date date;
  private String title;

  @Id
  @GeneratedValue
  public int get[author]Id[/author]() {
    return this.id;
  }

  @Temporal(TemporalType.TIMESTAMP)
  public Date getDate() {
    return this.date;
  }

  public Title getTitle() {
    return this.title;
  }

  public void setDate(Date when) {
    this.date = when;
  }

  public void setTitle(String txt) {
    this.title = txt;
  }
}



在使用Hibernate操作之前,我们得创建一个session工厂:

SessionFactory factory = new AnnotationConfiguration()
  .configure()
  .addAnnotatedClass(Post.class)
  .buildSessionFactory();


工厂每次我们要使用Post对象时产生一个session,每次使用session应当如下使用代码包装:

Session session = factory.openSession();
try {
  Transaction txn = session.beginTransaction();
  // your manipulations with the ORM, see below
  txn.commit();
} catch (HibernateException ex) {
  txn.rollback();
} finally {
  session.close();
}


当session准备好后,下面我们就可以从数据表中获取所有的post:

List posts = session.createQuery("FROM Post").list();
for (Post post : (List<Post>) posts){
  System.out.println("Title: " + post.getTitle());
}


我认为这是清楚的, Hibernate是一个强大的连接到数据库的引擎,通过执行必要的SQL SELECT请求,获取检索数据。 然后它创建了类Post的实例,并将数据装入其中。 当这个对象过来时,它填满了数据,我们应该使用getter方法将这些数据取出,比如我们使用 getTitle() 方法。

当我们想做一个反向操作,将一个对象发送到数据库,我们做的都基本相同,只不过以相反的顺序。 我们创建类Post的一个实例 文章,然后塞进入数据,请求Hibernate保存它:

Post post = new Post();
post.setDate(new Date());
post.setTitle("How to cook an omelette");
session.save(post);



这是几乎是每一个ORM工作原理。 基本原则始终是相同的——ORM装有数据的贫血对象。 我们谈论的是ORM框架,这些框架与数据库交互, 对象只有帮助我们将请求发送给ORM框架,并理解其响应。 除了getter和setter,对象没有其他方法。 他们甚至不知道他们来自哪个数据库。

这是对象关系映射是如何工作的。

也许你会问,怎么了? 

ORM怎么了?
说真的,有什么错吗? Hibernate是最流行的Java库,已经超过10年了。 世界上几乎每一个SQL-intensive应用程序都是使用它。 每个Java教程会提及Hibernate(或者 其他一些ORM 像TopLink或OpenJPA)用于database-connected应用程序。 它实际上是一个标准, 然而我还要说它错了? 是的。

我声称整个ORM背后的想法是错误的。 它的发明是也许OOP领域空引用之后第二大错误 。

其实,我不是唯一一个说这样的事情的人,绝对不是第一个。 很多非常受人尊敬的作者已经发表关于这个主题,包括 Matinfowler的OrmHate , Jeff Atwood的对象关系映射是计算机科学的越南战争 ,Ted Neward的计算机科学的越南 , Laurie Voss的ORM是一种反模式,等等还有许多其他人。

然而我的观点不同于他们所说的,尽管他们的理由是来自实践且有效,如ORM是慢的,数据库升级很难等,他们错失了主要点,你能从Bozhidar Bozhanov的ORM Haters Don’t Get It文章中看到非常好的实战回答。

ORM主要点并不是封装数据库交互到一个对象,释放数据,遍历时撕开了坚固且聚合的living organism(实体),对象的一部分保持数据,而另外一部分是在ORM引擎(session factory)内部执行,这些引擎知道如何处理这些数据,并且转换它到关系数据库,看看这张图,它模拟了ORM如何工作:




我作为Post文章的读者,得和两个组件打交道,1是ORM,2是返回一个砍了头的对象,我打交道的行为应该有一个单点(操作一个组件),这个对象才是OOP,而在ORM这种情况下,我得与两个点打交道,ORM和数据对象,甚至我们都不能称之为对象。

因为这个可怕的和明显地违反了面向对象范式,我们已经有很多实际中受人尊敬的出版物都在提到这个问题,这里举例一些:

SQL并没有隐藏,ORM的用户应该会使用SQL(或者方言如HQL),看看上面案例,我们调用session.createQuery("FROM Post")是为了获得所有文章Posts,即使它不是SQL,也很类型,关系模型并没有被封装到对象中,相反,它暴露在整个应用程序中,使用这个对象的每个人都必须与关系数据库打交道,以获得或保存什么,这样ORM并没有隐藏和封装SQL,而是污染了整个应用程序。

难以测试。当一些对象要与Posts文章列表交互时,它需要处理一个实例 SessionFactory 。 我们如何在测试中模拟这种依赖性? 我们必须创建一个它的模拟吗? 这个任务有多复杂? 看看上面的代码,你将意识到冗长和繁琐的单元测试。 相反,我们可以编写集成测试并将整个应用程序连接到一个测试版本的PostgreSQL。 在这种情况下,没有必要模拟一个 SessionFactory ,但这种测试将会相当缓慢,更重要的是,我们却将一个并没有和数据库有关系的数据对象却作为数据库的实例对象进行测试。非常糟糕的设计。

我要再次重申。 上面ORM问题导致的后果。 ORM基本缺点是撕裂了对象,可怕和明显违反了对象理念:一个对象是什么

 

让我们设计一下类Post,我们将其分解为两个类Post和Posts,单数与复数,好的对象总是真实世界的抽象,比如实际中我们有数据表和表行,这就是为什么我们会有两个类,Posts代表表,而Post代表行。

每个对象都应该在合约和接口下工作,那么就设计两个接口,这些接口是不可变的Posts是:

interface Posts {
  Iterable<Post> iterate();
  Post add(Date date, String title);
}



单个Post是:

interface Post {
  int id();
  Date date();
  String title();
}
 



下面是列出数据表中所有的Posts:

 Posts posts = // we'll discuss this right now
for (Post post : posts.iterate()){
  System.out.println("Title: " + post.title());
}
 



下面是创建一个新的post:

 Posts posts = // we'll discuss this right now
posts.add(new Date(), "How to cook an omelette");
 



如你所见,我们现在拥有真正的对象。 他们负责所有操作,完美地隐藏他们的实现细节。 没有事务、会议或工厂。 我们甚至不知道这些对象实际上是和PostgreSQL有关系, 如果把数据保存在文本文件中也无关系。 所有我们需要的的是Post 是一个能够为我们列出所有帖子, 创建一个新的Post。 实现细节是完全隐藏在里面。
现在让我们来看看我们如何实现这两个类。

这里使用jcabi-jdbc作为JDBC包装器,你可以使用你喜欢的普通JDBC方式,这些都没有关系,重要的是你的数据交换被隐藏在对象中。

 

 

 

 

分享到:
评论
2 楼 windshome 2014-12-23  
面向对象的最大作用是描述业务,ORM本身就是一个怪胎。

现在程序员天天打交道的是什么框架,什么工厂,都不是业务所需要的。其实Spring、Hibernate和J2EE都应该反思。


1 楼 xxh02 2014-12-23  
对象是行为和属性的封装,ORM将对象的属性暴露给程序猿,持久化的操作以及和数据库交互封装到框架,导致对象在开发者看来只有一大堆属性,典型的贫血模型。

相关推荐

    .net经典会员管理项目完整源码(包含完整源码与数据库)

    1. **C#编程语言**: C#是微软开发的一种面向对象的编程语言,广泛应用于Windows平台的软件开发,尤其是.NET框架下的应用。在这个项目中,你将看到C#如何用于构建业务逻辑、控制流、数据处理等。 2. **.NET框架**: ...

    tdd-for-web-development-with-django-and-selenium

    - Django遵循模型(Model)-视图(View)-控制器(Controller)的架构模式,这是一种常见的Web开发模式。 - 模型负责处理数据逻辑;视图处理业务逻辑并与模型交互;控制器处理用户输入并选择相应的视图。 2. **URLs.py*...

    JAVA编写的图书管理系统(derby数据库)

    Java编写的图书管理系统是一款基于Derby数据库的家庭用图书管理软件,是学习和实践Java数据库连接(JDBC)技术的一个典型示例。这个项目不仅适用于个人家庭管理私人图书收藏,也适用于小型图书馆或阅读爱好者,方便...

    ASP.NET邮件收发程序的设计与开发(源代码+论文).zip

    1. **ASP.NET框架**:ASP.NET是由微软开发的一种用于构建Web应用程序的开源框架。它基于.NET Framework,提供了丰富的服务器控件、事件驱动模型和自动状态管理,简化了Web应用的开发过程。在本项目中,开发者可能...

    JavaDatabaseProject.zip

    8. **DAO(数据访问对象)模式**:这是一种设计模式,用于分离业务逻辑和数据访问代码。DAO类封装了与数据库的所有交互,提供了一种统一的接口供其他组件使用。 9. **实体类**:对于每个数据库表,可能有一个对应的...

    sy8_1_java_图书管理_图书管理系统_

    Java是一种广泛应用于服务器端开发的编程语言,它具有跨平台的特性,能够运行在多种操作系统上,这为图书管理系统的部署提供了极大的灵活性。在本系统中,Java的面向对象特性被充分利用,将图书、作者、出版社等信息...

    ssms_java_teach1m4_SSMSjava_学生管理_学生信息管理系统_

    5. **MVC(Model-View-Controller)设计模式**:这是一种软件设计模式,用于分离业务逻辑、数据模型和用户界面。在学生管理系统中,模型代表学生数据,视图负责展示,控制器处理用户输入并更新视图。 6. **异常处理...

    第二期2020字节跳动面试题及解析.pdf

    ZAB(ZooKeeper Atomic Broadcast)协议是ZooKeeper专门设计的一种支持崩溃恢复和原子广播协议。ZAB协议支持ZooKeeper在分布式环境中的一致性。 #### 3.5 ZooKeeper的数据节点类型 ZooKeeper中数据节点Znode有四种...

    java源码:电子书店管理系统.rar

    Java是一种面向对象的编程语言,具有跨平台、安全性高、性能稳定的特点,因此被广泛用于企业级应用开发。在这个电子书店管理系统中,Java的核心特性如类、对象、接口、继承、封装和多态性等都会得到体现。 1. **类...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    随着博客人数的增加, Blog 作为一种新的生活方式、新的工作方式、新的学习方式已经被越来越多的人所接受,并且在改变传统的网络和社会结构:网络信息不再是虚假不可验证的,交流和沟通更有明确的选择和方向性,单一...

    Java Web复习.zip

    - JNDI提供了一种标准的方式来查找和访问各种命名和目录服务,如EJB、JMS队列等,在Java Web应用中常用于资源定位。 5. **JTA(Java Transaction API)与JMS(Java Message Service)** - **JTA**:用于管理...

    汽车销售系统实例.rar

    3. **MySQL数据库**:MySQL是一种广泛使用的开源关系型数据库管理系统,因其高效、稳定和易于管理而被广泛应用。在这个系统中,MySQL用于存储汽车信息、销售记录等数据。开发者需要熟悉SQL语言,包括DDL(Data ...

    C# 做的员工管理系统

    “员工信息管理系统”是一个很好的实践项目,可以帮助初学者深入理解C# WinForm开发流程,熟悉数据库操作,以及提升面向对象编程和设计模式的应用能力。 总结,C# WinForm员工管理系统是一个全面学习C#和.NET开发的...

    大厂面试必备Java核心题

    - Spring Boot是一种约定优于配置的Spring扩展,旨在简化Spring应用的初始搭建及开发过程。 10. **SpringCloud**: - Spring Cloud是一系列框架的集合,用于快速构建分布式系统中的一些常见模式。 11. **...

    计算机实习工作日记范文.docx

    - **Struts**:主要用于构建应用的表现层,它是MVC设计模式的一种实现,有助于分离视图和模型。 - **Spring**:用于管理应用中的业务逻辑,通过依赖注入(DI)和面向切面编程(AOP)等特性提高了代码的灵活性和可...

    spring3.0_struts2.1_hibernate3.3_Flex4整合登录

    在IT行业中,集成多种技术构建复杂应用是一种常见的实践。标题提到的"spring3.0_struts2.1_hibernate3.3_Flex4整合登录"是一个典型的Java Web开发示例,它结合了Spring、Struts2、Hibernate和Flex这四款强大的框架,...

    spring面试题

    **IOC(控制反转)**是一种设计模式,它通过容器来管理对象之间的依赖关系,而不是由对象自己来管理。这使得对象可以在运行时由容器动态地注入其所需的依赖项,从而降低了各组件之间的耦合度。 ### SSH整合简介 ...

    extjs+struts2+hibernate+json登录程序

    **JSON(JavaScript Object Notation)** 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。在这个登录程序中,JSON用于在ExtJS前端和Struts2后端之间传递数据。Struts2可以通过配置插件...

    asp.net电子通信录

    1. ASP.NET框架:ASP.NET是微软开发的一种Web应用程序框架,用于构建动态网站、Web应用和服务。它提供了丰富的服务器控件、事件驱动模型和自动页面生命周期管理,使得开发过程更加高效和直观。在这个电子通信录项目...

    ssh开发登录案例

    Struts2是MVC(Model-View-Controller)设计模式的一种实现,主要用于处理前端请求和控制应用程序流程。在登录案例中,Struts2接收用户的登录请求,通过Action类处理这些请求,并将结果返回给用户。它还支持自定义...

Global site tag (gtag.js) - Google Analytics