3.2.1 传统的4种关系
传统的关系包含一对一、多对一、一对多、多对多。这4种关系在类与类之间存在,在表与表之间也存在,所以可以直接转换。
首先是一对一关系。在以上案例中,“申辩申请单明细”与“过错行为”就是一对“一对一”关系,如图3-4所示。在该关系中,一个“申辩申请单明细”必须要对应一个“过错行为”,没有一个“过错行为”的对应就不能成为一个“申辩申请单明细”。这种约束在数据库设计时,可以通过外键来实现。此外,一对一关系还有另外一个约束,就是一个“过错行为”最多只能有一个“申辩申请单明细”与之对应。这个约束暗含的是一种唯一性的约束。因此,我们将过错行为表中的主键作为申辩申请单明细表的外键,并将该字段升级为申辩申请单明细表的主键。
图3-4 一对一关系示例
接着是多对一关系,在日常的分析设计中最常见的一种关系。在以上案例中,一个“过错行为”对应一个“税务人员”、一个“纳税人”与一个“过错类型”。同时,一个“税务人员”,或“纳税人”,或“过错类型”,都可以对应多个“过错行为”。它们就形成了“多对一”关系。在数据库设计时,通过外键就可以建立这种“多对一”关系。因此,我们进行了如图3-5所示的数据库设计。
多对一关系在数据库设计上比较简单,然而落实到程序设计时,需要好好探讨一下。在以上案例中,在按照这样的方式设计以后,在查询时往往需要在查询过错行为的同时显示它们对应的税务人员、纳税人与过错类型。这时,以往的设计是增加一个join语句。然而随着数据量不断增大,查询性能将受到极大的影响。也就是说,join操作往往是关系型数据库在面对大数据时最大的瓶颈之一。一个更好的方案就是先查询过错行为表,分页,然后再补填当前页的其他关联信息。这时,就需要在过错行为这个值对象中通过属性变量增加对税务人员、纳税人与过错类型等信息的引用。
一对多关系往往表达的是一种主-子表的关系。譬如,以上案例中的“申辩申请单”与“申辩申请单明细”就是一对“一对多”关系,订单与订单明细、表单与表单明细,也是一对多关系。一对多关系在数据库设计上比较简单,就是在子表中增加一个外键去引用主表中的主键,如本案例中,申辩申请单明细表通过一个外键去引用申辩申请单表中的主键,如图3-6所示。
图3-5 多对一关系的示例
图3-6 一对多关系的示例
除此之外,在程序的值对象设计时,主对象中也应当有一个集合的属性变量去引用子对象。如本例中,在“申辩申请单”值对象中有一个集合属性去引用“申辩申请单明细”。这样,当通过申辩申请单号查找到某个申辩申请单时,同时就可以获得它的所有申辩申请单明细。一对多关系的代码如下所示。
public class Sbsqd { private Set<SbsqdMx> sbsqdMxes; public void setSbsqdMxes(Set<SbsqdMx> sbsqdMxes){ this.sbsqdMxes = sbsqdMxes; } public Set<SbsqdMx> getSbsqdMxes(){ return this.sbsqdMxes; } ...... }
最后一种关系是多对多关系,比较典型的例子就是“用户角色”与“功能权限”。一个“用户角色”可以申请多个“功能权限”,而一个“功能权限”又可以分配给多个“用户角色”使用,这样就形成了一个“多对多”关系。这种多对多关系在对象设计时,可以通过一个“功能-角色关联类”来详细描述。因此,在数据库设计时就可以添加一个“角色功能关联表”,而该表的主键就是关系双方的主键进行组合所形成的联合主键,如图3-7所示。
图3-7 多对多关系的示例
以上4种关系是领域模型有、数据库也有的4种传统关系。因此,在数据库设计时,直接将相应的关系转换成数据库设计即可。在数据库设计时还要将它们进一步细化。如在领域模型中,无论对象还是属性在命名时都是采用中文,这样有利于沟通与理解,但到了数据库设计时,就要将它们改为英文命名,或者汉语拼音首字母,同时还要确定它们的字段类型与是否为空等其他属性。