在“面向对象建模与数据库建模两种分析设计方法的比较”一文中我们比较了在对需求分析时两种方法的不同,所谓数据库建模分析,就是项目一开始就根据需求建立数据库模型,如数据表结构和字段等,这种错误现象大量普遍存在我们国内项目实践中,从每年大量招聘启示中就可见一斑:招聘数据库建模人员,招聘Java面向对象程序员。这些说明软件业一边在大量使用Java/.NET/Ruby on Rails这样OO语言同时,还在同时使用与OO体系抵触的围绕关系数据库的分析设计方法。

  为进一步说明OO和关系数据库是属于两个不同世界观,存在天然矛盾,就象有神论和无神论,就象水与火那样属于截然相反的两种编程知识。正好最近TheServerSide刊登一篇大谈对象数据库ODBMS的文章:When to use an Embedded ODBMS(以下简称ODBMS一文) 
http://www.theserverside.com/tt/articles/article.tss?l=EmbeddedODBMS

  这篇文章不但谈到了OO和关系数据库天然阻抗;谈到了ODBMS和RDBMS区别,也谈到了ODBMS和O/R Mapping如Hibernate的区别,甚至谈到了ODBMS在非C/S如嵌入式方面的优点。这篇文章我看最重要价值就是它用大量篇幅阐述了对象和关系数据库存在的天然矛盾,我这里就将这部分篇幅大意转载这里,可以说是经过我咀嚼后反馈的结果,版权思想还是属于这篇文章,我这里只是用中国人更易于接受方式把这个天然矛盾转述出来:

对象和关系数据库累赘转换

  在一个面向对象系统中,数据存在于对象中,在关系数据库中,数据存在于数据表的记录中。
在关系数据库世界中,我们必须掌握表结构、表记录等概念,这里面没有任何对象概念,当我们在OO语言中使用关系数据库时,因为在OO世界中是大量对象,所以必须将数据在这两个不同世界之间转换传输。

   这就导致了大量垃圾临时对象,增加了应用系统的复杂性。这也是我们很多人使用了Java等这样的OO系统后,反而觉得开发效率并没有OO宣传那么高的原因, 说白了,OO思想没有完全占领应用系统,因为有关系数据库盘踞着数据库这个最后堡垒负隅顽抗。

  下面看看我们程序如何为了让OO语言和关系数据库捆绑在一起工作,如何弥补两个世界的裂缝,如何在他们之间和稀泥。 为了将对象保存到关系数据库中,我们必须将对象中的数据解散开来,再将它们组装到SQL中,然后执行SQL。
一个类如下:

public class Course { 
  public string name; 
  public int courseID; 
  int deptID;
  public int creditHours; 
}

将上述对象保存到关系数据库的SQL语句如下:

PreparedStatement insertStatement = connection.prepareStatement(
"INSERT INTO Courses (name, courseID, deptID, creditHourse) " +
"VALUES (?, ?, ?, ?)");
insertStatement.setString(1, course.name);
insertStatement.setInt(2, course.courseID);
insertStatement.setInt(3, course.deprtID);
insertStatement.setInt(4, course.creditHours);
insertStatement.executeUpdate();

上述在两个世界之间做的翻译工作与复杂性取决于对象Course这样的复杂性。如果Course字段有十几个,那么这个保存SQL将更复杂。

   更重要的是,万一有一个字段发生变化,更改量就很大。我们不但要修改对象,而且还要修改数据表结构,有过数据库编程经验的人知道,如果表中有数据,表结构变动带来的问题可能就象噩梦一样,所以,我们程序员经常以这个需要更改表结构拒绝一些软件的维护和拓展,这其实更是错上加错;在ODBMS一文中提出了对象数据库解决方案:只要更改类结构,其他都有ODBMS搞定;使用ORM如Hibernate也只多一个步骤:更改映射配置。

继承关系的尴尬实现

  对象和关系数据库的不匹配还表现在关系数据库难以实现对象世界中的继承关系,如下图:

  这是一个典型的对象世界表达客观世界的类图,学生和教授都是人,都具备人一些共性,当然他们之间也有区别,所以我们用上述继承关系来表达这样一个情况。

  那么如果这样继承关系的类图如何保存到关系数据库中呢?有两种方式:one-class-per-table(一个类对应一个表)和one-object-per-row(一个对象对应一个表行)。

  在one-class-per-table方式中,Student对象放在Student表中,Professor放在Professor表中。但是Student和Professor实际具有共性Person,这实际上将Person这个共性对象中数据分割成两个表保存,从语义上所:也就是将一个对象分割成两个部分了;而且当你要获取这个对象时,需要两次Select。同样道理增删改查都要两次。

  如果按照one-object-per-row,将这Student和Professor放到一个表中,就会产生空白字段。Student对象中数据保存到Student对象对应的字段中,那么Professor对象对应的字段就是空的,反之也然。

  这些都反映了对象和关系数据库天然不匹配,两者根本就无法搭配在一起工作,只有一方作出妥协,大部分情况是对象作出妥协,这样,一个OO语言Java/.NET系统就做成了一个数据库中心系统,根本无法享受OO语言带来快捷方便,可维护性强,由于Java出现比较早,更多程序员将项目失败归结于Java语言本身,转而使用数据库思维去做.NET,去做Ruby On Rails,还是会碰到上面这些问题,没有碰到,只能说明他们系统中就没有对象概念,解决方式很简单很可笑:矛盾双方,消灭一方不久解决矛盾了。

类的复杂关系实现

  下面我们再看看ODBMS一文中列举的类复杂关系如何用关系数据库实现保存:

public class Department { 
    private Professor[] professors; 
... } 

  这个Department类很明确告诉我们这样一个意思,在一个Department中,存在很多Professor,大白话就是:一个部门里有很多教授,这个类就自然准确地表达这个意思,这也体现了使用OO表达需求的自然性和方便性。

  那么我们下面看看,如果我们使用关系数据库来保存Department这个对象,很显然,这里需要使用关系数据库的表外键,通过表外键来表达Department表和Professor之间1:N关系,关键问题是:这个外键一般是设计在Professor表中,这就造成语义上的误解。

  对象Department表达的需求含义是:
  1. 我是一个部门对象,在我里面包含很多教授对象。

  当这个对象在关系数据库,由于外键在Professor表中,那么意思就是:
  2. 我是一个教授对象,这里有一个我所属的部门对象。

  前后对比发现,其实完全是两者不同表达方式,这已经属于拷贝走样了,当我们从关系数据库中获得Department 对象时,得按照关系数据库规则绕一个弯来获得完整的Department 对象。也就是说:每次获得一个对象Department,我们得将这个需求意思翻译成关系数据库的表达方式,这种内耗式的翻译不但容易出错,也带来了程序员开发上的负担。万一不留神,翻译出错,问题就严重了。

  当然,如果我们使用ODBMS,就不必做这些费力不讨好的翻译转换,也不再绕着弯子编程,只要直接告诉ODBMS或ORM框架如Hibernate,就能够获得一个真正对象意义上的Department。

  以上是TSS的ODBMS一文大量篇幅阐述了对象和关系数据库矛盾的方面,所以,我们才认为:如果你使用对象语言,如Java/.NET/Ruby On Rails等,那么就必须坚持OO思想和方法,从程序中杜绝关系数据库对软件的影响,将关系数据库只看成是活动对象的“冬眠”(英文Hibernate)地方,这也就是ORM框架Hibernate的本义所在。

对象和数据库的天然阻抗 转摘于:http://www.jdon.com/mda/oo-reltaion2.html的更多相关文章

  1. 使用command对象操作数据库

    1.Command对象查询数据库 protected void Button1_Click(object sender, EventArgs e) { //读取web.config节点配置 strin ...

  2. MSSQL 2012 拒绝了对对象 'extended_properties' (数据库 'mssqlsystemresource',架构 'sys')的 SELECT 权限

    查看数据库的表的时候报如下错误: MSSQL 拒绝了对对象 ) 解决方法: 在数据库里相应的用户权限中,把db_denydatareader的复选框的勾去掉.db_denydatareader是拒绝访 ...

  3. 拒绝了对对象 'sp_sdidebug'(数据库 'master',所有者 'dbo')的 EXECUTE 权限。

    如果在调试过程中出现异常“拒绝了对对象 'sp_sdidebug'(数据库 'master',所有者 'dbo')的 EXECUTE 权限.”则可以通过以下方式解决: 打开master数据库,打开扩展 ...

  4. ORMBase对象/关系型数据库映射在MVC中的应用

    ORM这个字眼在我们操作数据库的时候,是我们使用频率最高的.它到底是个什么东西呢,我们先来看看一些对它的含义解释. 对象/关系数据库映射(object/relational mapping(ORM)) ...

  5. 拒绝了对对象 'sp_OACreate' (数据库 'mssqlsystemresource',架构 'sys')的 EXECUTE 权限。

    执行一个存储过程, 由于里面使用到了一些 --创建对象  EXEC sp_OACreate 'VBScript.RegExp', @objRegex OUT  --设置属性  EXEC sp_OASe ...

  6. 集合对象与自定义javabean对象接收数据库查询的数据 (基础知识扫盲)

    一.集合对象(List,Map,数组)等对象接收数据库查询的记录,如果没有一条记录,就得到的内容为空的集合,不是null: 例如:List查不到记录得到的就是size=0的list 二.自定义的jav ...

  7. Hibernate通过实体对象对应数据库表信息

    Hibernate通过实体对象对应数据库表信息,包括:数据库表名称.主键列名.非主键列名等. 获取对象映射缓存管理类: AbstractEntityPersister aep = (AbstractE ...

  8. 对象关系型数据库管理系统(PostgresQL )

    PostgresQL是   对象关系型数据库管理系统(ORDBMS).PostgreSQL支持大部分SQL标准并且提供了许多其他现代特性:复杂查询.外键.触发器.视图.事务完整性.MVCC.同样,Po ...

  9. 拒绝了对对象 'XXX' (数据库 'XXX',架构 'dbo')的 SELECT 权限

    2010-04-17 23:16 在IIS里测试ASP.NET网站时会遇到这样的问题(ASP.NET+SQL2005)我自己的解决方法是这样的: 1.打开SQL2005管理界面(没有安装SQLServ ...

随机推荐

  1. robotframework API 源码阅读笔记----robot.utils.asserts

    http://robot-framework.readthedocs.io/en/latest/autodoc/robot.utils.html#robot.utils.asserts.assert_ ...

  2. [SCOI2003]字符串折叠(区间dp)

    P4302 [SCOI2003]字符串折叠 题目描述 折叠的定义如下: 一个字符串可以看成它自身的折叠.记作S = S X(S)是X(X>1)个S连接在一起的串的折叠.记作X(S) = SSSS ...

  3. ForkJoin简单示例

    import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import jav ...

  4. element-ui中使用el-radio单选切换表格

    应用场景:点击单选,切换表格数据 代码: data里的数据:(这里的值是默认选中的   和label值是对应的) change事件操作切换,这里面添加@click事件是不生效的,注意...

  5. pyCharm和解释器下载安装

    参考:(mac) 安装流程和注意: http://blog.csdn.net/limin2928/article/details/69267184 解释器下载地址: https://www.pytho ...

  6. PHP curl_init函数

    curl_init — 初始化一个cURL会话 说明 resource curl_init ([ string $url = NULL ] ) 初始化一个新的会话,返回一个cURL句柄,供curl_s ...

  7. PHP curl_close函数

    说明 void curl_close ( resource $ch ) 关闭一个cURL会话并且释放所有资源.cURL句柄ch 也会被释放. 参数 ch 由 curl_init() 返回的 cURL ...

  8. manacher 和 扩展KMP

    manacher 和 扩展KMP 事实上,这两个东西是一样的. 考虑 manacher 的过程 我们实时维护最远扩展的位置 \(mx\) 以及这个回文串的回文中心 \(l\) ,那么显然当然位置如果没 ...

  9. 十、future其他成员函数、shared_future、atomic(原子操作)

    一. int mythread(){ cout<<"thread"<<endl; std::chrono::milliseconds dura();//5秒 ...

  10. element-ui中的loading的实际应用

    实际开发中,要如何指定loading在我们想要的区域加遮罩呢? 前提: 你已经引入element-ui,如下: import ElementUI from 'element-ui' import { ...