该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2003-11-18
这是一个屁股决定脑袋的问题。如果你是做数据库设计出身的,你当然觉得数据建模好,如果你做OOP出身的,当然觉得对象建模好。
引用 我对robbin的那种直接利用oo对象就生成数据库的这种方式持保留态度,这种方式应对于小项目没有问题,对于性能要求比较高的地方,无论如何都应倾听数据库专家的意见。
这是当然的了,我现在做一个对数据库存储性能要求比较的系统的设计的时候,就是有一个数据库专家参与进来,一起进行持久层的设计。 引用 从对象模型出发的时候(不得不同时考虑了关系数据库的东西), 然后用hibernate来映射, 才发现很多东西没法很好的映射到数据表上
噢?什么东西你没有办法很好的映射到数据库表上?我目前还没有发现这种情况,Hibernate没有办法做映射的情况出现。 引用 hibernte至少不能表述java中的各种主要内存对象模型, 这也许是hibernate需要增强的地方
哪些内存对象模型不能表述? 引用 所以我设计对象的时候可能不但要考虑数据库的东西, 还得考虑当前使用的OR工具的特性
我做OO设计的时候,我丝毫不用考虑Hibernate是如何实现的,如果换了其他的ORM,我或许会担心ORM会实现不了我的设计,但是Hibernate,迄今为止,还没有出现这种情况。 引用 如果说OR之间有很差异的话, 那应当是用对象模型照顾关系数据模型还是用关系数据模型来照顾对象模型呢? 我认为是用前者
我倾向于前者,在OO模型的基础上,出于数据存储效率和性能的考虑进行优化。 数据建模有两个问题: 1、OO模型实际上设计思维是从人的思维角度层层推导,一直推导到机器实现的逻辑(参考我写的《面向对象的思维方法》),数据模型是从机器实现逻辑层层实现,一直实现到符合人的使用习惯的软件系统。 OO设计是符合人类的思维习惯的,因此设计过程更加准确,不容易出现大的设计偏差。而数据建模在需求和数据模型中间有一个巨大的鸿沟,要你从需求一下跳跃到机器存储和实现的逻辑,除了少数行业领域专家,几乎没有人可以一开始就设计出合理的数据模型,因此会造成编码过程中多次大改。 2、数据模型驱动设计出来的系统,上层逻辑和数据模型耦合性非常高,底层逻辑的修改会造成上层代码大面积修改,这样的工作量是非常可怕的。 其实谈论数据建模比较成果的领域主要集中在那些业务非常稳定的领域,特别是软件产品领域。在这些领域,整个系统早已经成型了,不存在从无到有构建一个系统中那些数据建模容易遇到的弱点,一个应用了多年的产品,整个结构已经非常稳定了,在已经存在了一个合理的系统整体结构的情况下,你注意力更多的转向性能的提高上,因此会更加强调数据建模。而对象建模更加广泛的应用在项目开发方面,从无到有的构建一个系统。 |
|
返回顶楼 | |
发表时间:2003-11-18
引用 1、OO模型实际上设计思维是从人的思维角度层层推导,一直推导到机器实现的逻辑(参考我写的《面向对象的思维方法》),数据模型是从机器实现逻辑层层实现,一直实现到符合人的使用习惯的软件系统。 OO设计是符合人类的思维习惯的,因此设计过程更加准确,不容易出现大的设计偏差。而数据建模在需求和数据模型中间有一个巨大的鸿沟,要你从需求一下跳跃到机器存储和实现的逻辑,除了少数行业领域专家,几乎没有人可以一开始就设计出合理的数据模型,因此会造成编码过程中多次大改。 2、数据模型驱动设计出来的系统,上层逻辑和数据模型耦合性非常高,底层逻辑的修改会造成上层代码大面积修改,这样的工作量是非常可怕的。 其实谈论数据建模比较成果的领域主要集中在那些业务非常稳定的领域,特别是软件产品领域。在这些领域,整个系统早已经成型了,不存在从无到有构建一个系统中那些数据建模容易遇到的弱点,一个应用了多年的产品,整个结构已经非常稳定了,在已经存在了一个合理的系统整体结构的情况下,你注意力更多的转向性能的提高上,因此会更加强调数据建模。而对象建模更加广泛的应用在项目开发方面,从无到有的构建一个系统。 1、OO设计并非完全符合人类的思维,总是有差异的。假设你一开始就用了比较复杂的对象模型,比如timeRange,接口虽然简单,但实现并非如你所想象那么简单。临界状态就较难。粒度实现也需要考虑考虑。 2、对象驱动也不是万能药,也有问题较难解决,你如何去refactoring运行中的系统的实体对象? 最终你还是要增加大量的导入导出代码。 如果设计上有了重大变更,就不好玩了---对象结构变了。 3、Hibernate不是万能的,时刻记着。 我只能说他是一款优秀的O/R工具。 |
|
返回顶楼 | |
发表时间:2003-11-18
从小的一点的角度讲,Martin Fowler当然比我们,至少比我高得多。但从层次上来讲,他离KentBeck差得太远了。
是否用OR直接生成数据库和你是否用面向对象的思路是没有直接关系的,尽管Hibernate等等这些ORM提供了直接生成的工具,但一般来说,复杂一点的项目进入实用阶段,用Hibernate的UpdateSchema是不够的。 理论上来讲,是否使用OR和是否使用面向对象的思维解决问题也没有直接的关系。如果你的系统真的复杂,那么完全有可能定制象Hibernate这样的O/R工具来适合你的需求,虽然你定制的结果不一定能够适合别人,但很多大项目必不可少要进行这样的定制,目前我就有这样的情况。 一个表有成百上千个字段不但不会使速度变慢,一般来说肯定是让速度变快,因为不管select还是update只需要处理一张表。但绝对会增加程序维护的量和可能的冲突。(违反OAOO) 不用面向对象是注定是没有前途的,例如我现在开发的一个小小的微核心+AOP框架,如果不用面向对象,我就没办法面向方面。但是我再次申明,没有前途不一定是没有钱途,也不一定是现在做不出好程序。用纯C或者PB也可以做出很好的应用程序,只不过适应变化的速度和能力差了一点而已。 |
|
返回顶楼 | |
发表时间:2003-11-18
我在程序的早期开发阶段基本上不关心数据库,Kyle Brown叫我们先用文本文件代替。
除非你的程序一辈子就用在一个数据库,不然你必须知道依赖数据库的特性是罪恶的。所以绝大多数程序应该和数据库本身的特性无关,如果有关的话,也应该有一种方式隐藏这种有关性。一个例子就是ORM往往提供所谓的nativeOID机制,而这种机制可以在不同的数据库上有不同的实现,从而充分利用数据库的性能。 因此,解决业务本身问题时,我总是从问题领域出发,以面向对象(或面向方面)的思路来分解问题,然后把这种对象模型映射到数据库。OODBMS和ORM的重大区别是OODBMS试图直接在数据库中提供内存对象模型,存储对象模型,遍历对象模型,检索对象模型从而造成速度上的问题,而对象模型本身是最适合内存中维护的(考虑把一个引用从一个对象实例变成另外一个对象实例)。而当我们在内存中维护对象模型,最后以事务的形式千变万化以后最终的对象关系快照让RDBMS来持久的话,我们的效率已经足够了。 很多刚刚进入对象领域的人通常在担心效率的问题,而实际上绝大多数应用程序中,效率是根本不成问题的。最重要的是考虑可扩展性、可维护性、可靠性和开发的效率,所以数据库需要范式、代码需要重构,这都是KentBeck有名的Once And Only Once论断的证明。 如果在开发中间或者后期发现效率问题,我们可能需要针对某种特定的数据库、某个特定的数据存取瓶颈进行优化,而只有结构良好的代码才能进行最佳的优化,譬如我们希望使用存储过程进行批量的数据操作,这个时候可能会用一个对象方法去封装这样一个存储过程。但是提高效率的同时你不可避免地丢掉了数据库的无关性。效率的优化是一种实现的细节,这种细节应当尽量封闭在下部。不应当在考虑对象模型的时候首先把细节放在最上面。现在的关系数据库已经采用了多种技术来适合对象模型的存储和存取优化(譬如连接等等)。通常因为采用对象模型而导致你的效率极低 ,不是因为面向对象不行,而是因为你的对象模型本身有问题。 |
|
返回顶楼 | |
发表时间:2003-11-18
看了前面的帖子,受益非浅,尤其是关于软工方面的,大开眼界--也只限于长长见识,因为我不是PM
呵呵,讨论到这里离dlee发贴时的初衷已经相去甚远了,我想连dlee也始料不及的:) 软工方面的我不太懂,但关于对象建模和数据建模,大家争论的焦点主要还是性能与弹性的平衡 这里其实是一个如何判断性能的瓶颈可能会出现在什么地方的问题。以我的看法这纯粹靠经验来判断。 数据库的性能会出现在什么地方?主要是三个: 1。I/O--实质是数据量有多大的问题 2。表结构--实质是表的链接和多趟扫描表的问题 3。并发性--表的锁定策略和并发访问的用户数的问题 所以,基本上数据库产品决定以后,我们还能左右性能的地方主要就是表结构了。 问题主要就集中在两个方面:表结构的变化对程序的影响 直接使用JDBC,我们要处理SQL和resultset的数据提取(这个或许可以用反射解决,不过没做过) 使用ORM,我们要改变POJO和映射文件 那种难度大?显而易见 另一个方面就是,对象建模产生的表结构能否满足性能的要求? 从hbm产生的库表会比较多,这是三种O/R映射中的一种(三种方式分别是:一个表对应整个继承结构;一个表对应一个类别--面向对象;一个表对应一个具体类别--基于对象) hbm使用的应该是第二种 实际上三种方式各有优缺点--所以ORM才会这么麻烦 我以前的经验来说,报表多的项目用第一种方式比较好,性能也是最佳的--我也只用过这一种:( 最大问题是耦合性很高 如果报表不复杂,而且数据库够强,如orcale之类的,那用第二种很不错--呵呵,脏话累活都让数据库去干 问题potian说了,实现起来困难,“复杂一点的项目进入实用阶段,用Hibernate的UpdateSchema是不够的。”,而且,对象结构的变更(加个东西什么的),就得动到数据库 分布式的没做过,不敢说 第三种映射方式夹中间,我也不知道怎么评价好 越说头越大,看来应需而变才是,大家都想复杂问题简单化,可问题复杂到一定程度就很难再简化了,就像解方程,17次方以上就没代数解法了…… PM的另一个重要任务大概就是找出这个17次方来吧 各位如何看呢? |
|
返回顶楼 | |
发表时间:2003-11-18
如果想找一种解决任何问题的灵丹妙药,那自然是没有的。什么东西都不可能抛开原来的基础。
|
|
返回顶楼 | |
发表时间:2003-11-18
引用 另一个方面就是,对象建模产生的表结构能否满足性能的要求?
从hbm产生的库表会比较多,这是三种O/R映射中的一种(三种方式分别是:一个表对应整个继承结构;一个表对应一个类别--面向对象;一个表对应一个具体类别--基于对象) hbm使用的应该是第二种 Hibernate支持上述的每一种方式,既可以将整个继承关系装入一张表,也可以将继承关系分别放到单独的表中,甚至可以组合使用。下面是我开发的项目中用到的一个例子,就是将整个继承关系装入一张表,再根据hbm生成的DDL。总共是三层继承关系,共7个类装入一张表。 先看看7个类的三层继承关系图: (用Together for Eclipse生成) Award.hbm.xml映射文件,我都是自己手工写的,其实很简单,并不复杂,如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping> <class discriminator-value="A" name="huahua.tanbao.persistent.Award" table="award"> <id column="id" name="id" type="integer" unsaved-value="null"> <generator class="identity"/> </id> <discriminator column="subclass" type="character"/> <property length="20" name="type" not-null="true" type="string"/> <many-to-one cascade="none" class="huahua.tanbao.persistent.Map" column="map_id" name="map"/> <many-to-one cascade="none" class="huahua.tanbao.persistent.Cell" column="cell_id" name="cell"/> <subclass discriminator-value="B" name="huahua.tanbao.persistent.ActualAward"> <property name="acquriedTime" not-null="true" type="date"/> <many-to-one cascade="none" class="huahua.tanbao.persistent.Player" column="player_id" name="player"/> <subclass discriminator-value="D" name="huahua.tanbao.persistent.CashAward"> <property name="amount" not-null="true" type="float"/> </subclass> <subclass discriminator-value="E" name="huahua.tanbao.persistent.ObjectAward"> <property length="50" name="name" not-null="true" type="string"/> </subclass> </subclass> <subclass discriminator-value="C" name="huahua.tanbao.persistent.VirtualAward"> <subclass discriminator-value="F" name="huahua.tanbao.persistent.VirtualPointsAward"> <property name="points" not-null="true" type="integer"/> </subclass> <subclass discriminator-value="G" name="huahua.tanbao.persistent.VirtualToolAward"> <property column="toolcode" length="20" name="code" not-null="true"/> </subclass> </subclass> </class> </hibernate-mapping> 下面是Hibernate SchemeExport出来的DDL: create table award ( id INTEGER NOT NULL AUTO_INCREMENT, subclass CHAR(1); not null, type VARCHAR(20); not null, map_id INTEGER, cell_id INTEGER, acquriedTime DATE not null, player_id INTEGER, amount FLOAT not null, points INTEGER not null, toolcode VARCHAR(20); not null, award_id INTEGER, primary key (id); ); alter table award add index (player_id);, add constraint FK58E7A5D906ADF39 foreign key (player_id); references play (id); alter table award add index (award_id);, add constraint FK58E7A5D9F52FE3D foreign key (award_id); references play (id); alter table award add index (cell_id);, add constraint FK58E7A5D2786D518 foreign key (cell_id); references cell (id); alter table award add index (map_id);, add constraint FK58E7A5DBF8B7EDE foreign key (map_id); references map (id); 下面分别是7个类的Java代码,所有7个类都被放入award表: 祖先类Award: package huahua.tanbao.persistent; import java.lang.Integer; import java.lang.String; /** * * @author Robbin Fan */ public class Award { private String type; private Cell cell; private Map map; private Integer id; /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } /** * @return */ public Map getMap(); { return map; } /** * @param i */ public void setMap(Map i); { map = i; } /** * @return */ public Cell getCell(); { return cell; } /** * @param i */ public void setCell(Cell i); { cell = i; } /** * @return */ public String getType(); { return type; } /** * @param i */ public void setType(String i); { type = i; } } 第二层继承类,ActualAward和VirtualAward类: package huahua.tanbao.persistent; import java.util.Date; import java.lang.Integer; /** * * @author Robbin Fan */ public class ActualAward extends Award { private Integer id; private Date acquriedTime; private Player player; /** * @return */ public Player getPlayer(); { return player; } /** * @param i */ public void setPlayer(Player i); { player = i; } /** * @return */ public Date getAcquriedTime(); { return acquriedTime; } /** * @param i */ public void setAcquriedTime(Date i); { acquriedTime = i; } /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } package huahua.tanbao.persistent; import java.lang.Integer; /** * * @author Robbin Fan */ public class VirtualAward extends Award { private Integer id; /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } 第三层继承类: package huahua.tanbao.persistent; import java.lang.Integer; /** * * @author Robbin Fan */ public class CashAward extends ActualAward { private Integer id; private float amount; /** * @return */ public float getAmount(); { return amount; } /** * @param i */ public void setAmount(float i); { amount = i; } /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } package huahua.tanbao.persistent; import java.lang.String; import java.lang.Integer; /** * * @author Robbin Fan */ public class ObjectAward extends ActualAward { private Integer id; private String name; /** * @return */ public String getName(); { return name; } /** * @param i */ public void setName(String i); { name = i; } /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } package huahua.tanbao.persistent; import java.lang.Integer; /** * * @author Robbin Fan */ public class VirtualPointsAward extends VirtualAward { private Integer id; private int points; /** * @return */ public int getPoints(); { return points; } /** * @param i */ public void setPoints(int i); { points = i; } /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } package huahua.tanbao.persistent; import java.lang.String; import java.lang.Integer; /** * * @author Robbin Fan */ public class VirtualToolAward extends VirtualAward { private Integer id; private String code; /** * @return */ public String getCode(); { return code; } /** * @param i */ public void setCode(String i); { code = i; } /** * @return */ public Integer getId(); { return id; } /** * @param i */ public void setId(Integer i); { id = i; } } 底下有完整的实体类关系图,实体类之间有比较复杂的各种关系,但是使用Hibernate,完成可以轻松表达和操作。 |
|
返回顶楼 | |
发表时间:2003-11-18
我的看法也是对象建模驱动应该是发展的方向,包括上面我也是这么想的,我的话是"我个人目前觉得先从数据库方面开始*有的时候*还是可行的",robbin老大可能误解了.但是为什么那么多外国专家,包括这里的中国专家都有争论的.如果放到一年前hibernate不成熟的时候,jdo,cmp2也没出来,ORM少得多也"差"得多的时候,那么这里的讨论会不会有另一种结论占上风?换句话说一年之后似乎*一切*的问题都因为一个东西迎刃而解了?我想不管怎么说,也许每个项目不一样,每个人的解决思路不一样,每个人的特点不一样,而对象驱动建模还是数据驱动建模肯定都有其优缺点,在现在不一定就是一种能够完全压倒另一种(至少看国外的"专家"也没有一致意见),不过随着ORM之类的东西不断完善成熟使我们可以在更多的情况下考虑对象方式,但也不一定意味着数据方式就扔到一边了,存储过程用的也大有人在.还是从具体问题出发可能会比较好.也许见识低的人就喜欢两面不得罪:)
我刚才也不是主要因为性能问题而抱怨ORM什么的, <<refactoring>>开始就讲到了只在必要时优化, 不要因为不必要的优化破坏其他东西. 我最关心的是对象模型和关系模型*目前*能否通过一个方便的工具轻松的映射, 就是我可以*完全*按照OO的常见设计思路来设计实体对象, 不必担心它的持久. 最理想的情况当然是我定义了实体对象, 可以轻松切换为不同的对象持久方式, 比如prevayler, jisp, hibenrate, db4o... 也许像potian讲的开始可以最简单的东西来做或者原型. 这也许是完美目标. 不过我现在还是觉得有很多障碍, 在有的情况中,为了满足关系型数据库(我认为的)较好的设计模式, 又通过hibernate做桥梁,可能多写一些额外的对象, 或者造成有点古怪的对象, 至少如果设计内存对象不会出现的玩意,我也同意weihello说的"Hibernate不是万能的"(事情也许不能说得太绝对了), 逼迫我的对象模型*有时候*设计好后必须向关系模型妥协,我想*至少*在这时候可能就有必要先拿出好的关系模型来,或者是得同时兼顾各方面.不过最牛的人肯定可以自己定制,只是这种人好像不多,如果大家都很牛,都能根据需求,定制完全完全满足自己的愿望的东西当然最好的:)就像瑞典人不喜欢OR映射,被束缚于rdbms,又希望每个对象都是aop object,他就自己选择自己定制框架改进jisp/jdbm什么的.不过话也说回来,他做的东西也只是适合他的应用而已,IMO.大部分的人可能还是得考虑迁就别人的东西. |
|
返回顶楼 | |
发表时间:2003-11-19
理论上虽然如此。
但在我做过的项目中,绝大多数项目,都不存在着ShenLi你说的这种情况。 我做过和数据库相关的项目类型比较多,包括ERP系统、行业软件(例如医院系统和宾馆系统、水泥厂系统、物业软件、证券交易系统)、通用软件(例如个人助理软件、通用进销存)、政府软件(全省性质的人力管理、资源管理,整个地区性质的工商、税务系统、公安交警)、商业运营网站、Web内容管理、和硬件相关的管理软件以及其他一些杂七杂八的软件(我连蔬菜交易系统都做过)。 从需要持久对象模型的复杂度来讲,ERP应当是算最复杂的。某些项目看起来跨的地域很大、部门之间关系很多,但其实是不复杂的。至于一般性的商业运营网站和行业性质的软件,用Hiberante(包括我早期使用的Castor)完全可以符合需要。 |
|
返回顶楼 | |
发表时间:2003-11-19
无明 写道 软工方面的我不太懂,但关于对象建模和数据建模,大家争论的焦点主要还是性能与弹性的平衡 这里其实是一个如何判断性能的瓶颈可能会出现在什么地方的问题。以我的看法这纯粹靠经验来判断。 数据库的性能会出现在什么地方?主要是三个: 1。I/O--实质是数据量有多大的问题 2。表结构--实质是表的链接和多趟扫描表的问题 3。并发性--表的锁定策略和并发访问的用户数的问题 所以,基本上数据库产品决定以后,我们还能左右性能的地方主要就是表结构了。 如果考虑OLAP, OLTP的话, 内存和CPU则会变成主要的瓶颈, I/O倒是其次了. 无明 写道 另一个方面就是,对象建模产生的表结构能否满足性能的要求? 从hbm产生的库表会比较多,这是三种O/R映射中的一种(三种方式分别是:一个表对应整个继承结构;一个表对应一个类别--面向对象;一个表对应一个具体类别--基于对象) hbm使用的应该是第二种 实际上三种方式各有优缺点--所以ORM才会这么麻烦 我以前的经验来说,报表多的项目用第一种方式比较好,性能也是最佳的--我也只用过这一种:( 最大问题是耦合性很高 如果报表不复杂,而且数据库够强,如orcale之类的,那用第二种很不错--呵呵,脏话累活都让数据库去干 问题potian说了,实现起来困难,“复杂一点的项目进入实用阶段,用Hibernate的UpdateSchema是不够的。”,而且,对象结构的变更(加个东西什么的),就得动到数据库 分布式的没做过,不敢说 第三种映射方式夹中间,我也不知道怎么评价好 补充一些, 第一种: <<一个表对应整个继承结构>>容易产生报表, 但耦合性高, 增加一个相关的属性可能会导致很多不必要的Null值, 而Null值会有歧义性和引入错误的可能. 第二种<< 一个表对应一个类别>> 优点是符合面向对象的思想, 但表过多, 查询困难, 效率不高, 还要维护表与表之间的referential integration. 第三种<<一个表对应一个具体类别>> 容易产生报表, 但子类对应的表若发生变化可能会影响到父类对应的表. 也会有表与表之间的referential integration问题. 这三种映射方法在应用中应是混合使用的, 正如robbin所说, hibernate三种方式是一起使用的. |
|
返回顶楼 | |