在开发网络应用时,创建模型是相当重要的一步。随着建模工具软件的发展,目前愈来愈多的软件采用双向相关模型建模,本文主要讨论的是双向相关模型与传统单向模型相比的优点。javascript
双向相关模型和单向模型相比主要的区别在于其动态的描述了模型之间的交互关系:传统单项模型仅仅关注当前模型的状态变化,并不马上考虑自身状态变化后对环境的影响;而双向相关模型则在当前模型状态的每一次变化以后,都马上通知被此变化影响到的其余模型也要改变,即每一次操做中咱们改变的并不是是单个模型而是整个环境,由于这些模型之间都是由一对多或多对一关系关联起来的。java
最方便生成双向相关模型的方法是借助于 PowerDesigner 工具。例如,若是咱们须要对这样一个场景进行建模:人(person)和角色(role),一我的能够拥有多个角色,同时多我的也能够拥有相同的角色。这是一个典型多对多关系,那么使用 PowerDesigner 建模的结果可以下所示:git
(Person 模型拥有主键 id 和名称 name,Role 模型拥有主键 id 和描述 description,PersonRole 模型拥有主键 id 和两个外键 personId 及 roleId,其中 personId 和 Person 的主键 id 关联,roleId 和 Role 的主键 id 关联。)算法
将以上模型生成 Java 代码后,咱们将获得 Person、Role、PersonRole 三个类,其中 PersonRole 用来描述 Person 和 Role 的对应关系,咱们能够称 Person 和 Role 是多对多关系。由于在现实中咱们的对象都是从数据库中装载获得,为了让这些在内存中地址不一样的对象能够得到业务逻辑上的“相等”或“不等”,咱们经过使用重定义模型对象的 hashCode()、equals() 以及使用 java.util.LinkedHashSet 做为集合实现形式的方法来实现(若是业务不关心集合中对象的顺序,可使用 java.util.HashSet 方式替代 java.util.LinkedHashSet ,这样能够进一步提高性能),咱们把以上特性包装到一个泛型类 PojoSupport 中并做为全部模型对象的基类,最终获得的代码为(如下代码是在 PowerDesigner 自动生成代码上作简单修改所得):数据库
package myPackage; public abstract class PojoSupport<T extends PojoSupport<T>> { abstract public Object getId(); @Override /* 从新定义hash方法 */ public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); return result; } @Override /* 从新定义equals方法 */ public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PojoSupport<?> other = (PojoSupport<?>) obj; if (getId() == null) { return false; } else if (!getId().equals(other.getId())) return false; return true; } }
package myPackage; public class Person extends PojoSupport<Person> { public Person(Integer id, String name) { this.id = id; this.name = name; } private Integer id; private java.lang.String name; private java.util.Collection<PersonRole> personRole; public java.util.Collection<PersonRole> getPersonRole() { if (personRole == null) { personRole = new java.util.LinkedHashSet<PersonRole>(); } return personRole; } public java.util.Iterator<PersonRole> getIteratorPersonRole() { if (personRole == null) { personRole = new java.util.LinkedHashSet<PersonRole>(); } return personRole.iterator(); } public void setPersonRole(java.util.Collection<PersonRole> newPersonRole) { removeAllPersonRole(); for (java.util.Iterator<PersonRole> iter = newPersonRole.iterator(); iter.hasNext();) { addPersonRole((PersonRole) iter.next()); } } public void addPersonRole(PersonRole newPersonRole) { if (newPersonRole == null) { return; } if (this.personRole == null) { this.personRole = new java.util.LinkedHashSet<PersonRole>(); } if (!this.personRole.contains(newPersonRole)) { this.personRole.add(newPersonRole); newPersonRole.setPerson(this); } else { for (PersonRole temp : this.personRole) { if (newPersonRole.equals(temp)) { if (temp != newPersonRole) { removePersonRole(temp); this.personRole.add(newPersonRole); newPersonRole.setPerson(this); } break; } } } } public void removePersonRole(PersonRole oldPersonRole) { if (oldPersonRole == null) { return; } if (this.personRole != null) { if (this.personRole.contains(oldPersonRole)) { for (PersonRole temp : this.personRole) { if (oldPersonRole.equals(temp)) { if (temp != oldPersonRole) { temp.setPerson((Person) null); } break; } } this.personRole.remove(oldPersonRole); oldPersonRole.setPerson((Person) null); } } } public void removeAllPersonRole() { if (personRole != null) { PersonRole oldPersonRole; for (java.util.Iterator<PersonRole> iter = getIteratorPersonRole(); iter.hasNext();) { oldPersonRole = (PersonRole) iter.next(); iter.remove(); oldPersonRole.setPerson((Person) null); } personRole.clear(); } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public java.lang.String getName() { return name; } public void setName(java.lang.String name) { this.name = name; } }
package myPackage; public class Role extends PojoSupport<Role> { public Role(Integer id, String description) { this.id = id; this.description = description; } private Integer id; private java.lang.String description; private java.util.Collection<PersonRole> personRole; public java.util.Collection<PersonRole> getPersonRole() { if (personRole == null) { personRole = new java.util.LinkedHashSet<PersonRole>(); } return personRole; } public java.util.Iterator<PersonRole> getIteratorPersonRole() { if (personRole == null) { personRole = new java.util.LinkedHashSet<PersonRole>(); } return personRole.iterator(); } public void setPersonRole(java.util.Collection<PersonRole> newPersonRole) { removeAllPersonRole(); for (java.util.Iterator<PersonRole> iter = newPersonRole.iterator(); iter.hasNext();) { addPersonRole((PersonRole) iter.next()); } } public void addPersonRole(PersonRole newPersonRole) { if (newPersonRole == null) { return; } if (this.personRole == null) { this.personRole = new java.util.LinkedHashSet<PersonRole>(); } if (!this.personRole.contains(newPersonRole)) { this.personRole.add(newPersonRole); newPersonRole.setRole(this); } else { for (PersonRole temp : this.personRole) { if (newPersonRole.equals(temp)) { if (temp != newPersonRole) { removePersonRole(temp); this.personRole.add(newPersonRole); newPersonRole.setRole(this); } break; } } } } public void removePersonRole(PersonRole oldPersonRole) { if (oldPersonRole == null) { return; } if (this.personRole != null) { if (this.personRole.contains(oldPersonRole)) { for (PersonRole temp : this.personRole) { if (oldPersonRole.equals(temp)) { if (temp != oldPersonRole) { temp.setRole((Role) null); } break; } } this.personRole.remove(oldPersonRole); oldPersonRole.setRole((Role) null); } } } public void removeAllPersonRole() { if (personRole != null) { PersonRole oldPersonRole; for (java.util.Iterator<PersonRole> iter = getIteratorPersonRole(); iter.hasNext();) { oldPersonRole = (PersonRole) iter.next(); iter.remove(); oldPersonRole.setRole((Role) null); } personRole.clear(); } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public java.lang.String getDescription() { return description; } public void setDescription(java.lang.String description) { this.description = description; } }
package myPackage; public class PersonRole extends PojoSupport<PersonRole> { private Integer id; private Person person; private Role role; public Person getPerson() { return person; } public void setPerson(Person newPerson) { if (this.person == null || this.person != newPerson) { if (this.person != null) { Person oldPerson = this.person; this.person = null; oldPerson.removePersonRole(this); } if (newPerson != null) { this.person = newPerson; this.person.addPersonRole(this); } } } public Role getRole() { return role; } public void setRole(Role newRole) { if (this.role == null || this.role != newRole) { if (this.role != null) { Role oldRole = this.role; this.role = null; oldRole.removePersonRole(this); } if (newRole != null) { this.role = newRole; this.role.addPersonRole(this); } } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
以上就是使用 Java 语言对一个多对多关系的双向相关实现。在这个实现中咱们使用了 java.util.LinkedHashSet 而非其它常见的集合类做为承载容器,这是由于 java.util.LinkedHashSet 能够经过重定义 equals 和 hashCode 方法简单高效的实现比较从数据库中查询出对象的“业务逻辑上的相等与不等”,同时它是基于链表实现所以元素排列有序,在实际开发软件时这些特性会给咱们带来极大的好处。编程
双向相关模型的优点json
模型的相关代码已经所有给出,咱们能够经过一个用例来测试这个模型的效果。咱们定义三我的员(甲、乙、丙)和两个角色(管理员、用户),其中甲是管理员,乙是用户,丙既是管理员也是用户。实现以下(为使说明足够简单清晰,咱们假设 Person 类拥有 new Person(Integer id, String name) 的构造函数,Role 类拥有 new Role(Integer id, String description) 的构造函数):网络
Person p1 = new Person(1, "甲"), p2 = new Person(2, "乙"), p3 = new Person(3, "丙"); Role r1 = new Role(1, "管理员"), r2 = new Role(2, "用户"); PersonRole p1r1 = new PersonRole(), p2r2 = new PersonRole(), p3r1 = new PersonRole(), p3r2 = new PersonRole();
如下代码经过 4 种不一样的方式实现了甲、乙、丙三我的员和管理员、用户两个角色之间的关联:mybatis
/* 姓名为甲的人员具备管理员角色 */ p1r1.setPerson(p1); p1r1.setRole(r1); /* 姓名为乙的人员具备用户角色 */ p2.addPersonRole(p2r2); p2r2.setRole(r2); /* 姓名为丙的人员具备管理员角色 */ p3r1.setPerson(p3); r1.addPersonRole(p3r1); /* 姓名为丙的人员也具备用户角色 */ p3.addPersonRole(p3r2); r2.addPersonRole(p3r2);
这说明双向相关模型中,能够经过调用不一样模型的不一样方法来获得同一个结果。而咱们能够选择最容易获得的对象来进行操做,这在不少状况下能够减小查询数据库的次数,同时使得维护业务数据一致性的成本变得很低。架构
假如咱们想达到对象关系之间的脱离,好比让用户甲再也不具备管理员角色,也有不止一种实现方法:
p1.removePersonRole(p1r1); r1.removePersonRole(p1r1); /* 或者是 */ p1r1.setPerson(null); p1r1.setRole(null); /* 或者是 remove 和 set(null) 的交叉组合 */
以上几种方式执行的结果是彻底相同的,都让用户甲(p1)和管理员(r1)脱离了关联。在实际开发中选择哪一种方式取决于咱们更容易获取哪一个对象。
在单向模型中咱们需手动添加对象变化后影响关联对象的代码,而双向相关模型的对象改变后会马上影响到关联对象,每一个对象都会马上发生相应变化,并且是在模型层面实现,在业务层面能够直接使用这些特性而彻底不须要增长任何代码,这对于咱们开发稳定且复杂的网络应用是十分有帮助的。
双向相关模型的优点不止体如今增删改上,在展现数据时也有独特的地方。仍以上面的用例来讲,丙(p3)既是管理员(r1)又是用户(r2),咱们把丙、管理员、用户都序列化为 json 看(这里使用了FastJson中间件来序列化,由于双向相关模型的对象中会包含循环引用,所以序列化时必须开启循环处理。同时还开启了 FastJson 的 prettyFormat 特性以使输出结果便于阅读):
用户“丙”序列化后的json为:
{ "id" : 3, "name" : "丙", "personRole" : [{ "person" : {"$ref" : "$"}, "role" : { "description" : "管理员", "id" : 1, "personRole" : [{"$ref" : "$.personRole[0]" }] } },{ "person" : {"$ref" : "$"}, "role" : { "description" : "用户", "id" : 2, "personRole" : [{"$ref" : "$.personRole[1]" }] } }] }
角色“管理员”序列化后的json为:
{ "description" : "管理员", "id" : 1, "personRole" : [{ "person" : { "id" : 3, "name" : "丙", "personRole" : [ {"$ref" : "$.personRole[0]" },{ "person" : { "$ref" : "$.personRole[0].person"}, "role" : { "description" : "用户", "id" : 2, "personRole" : [{"$ref" : "$.personRole[0].person. personRole[1]" }] } }] }, "role" : { "$ref" : "$"} }] }
角色“用户”序列化后的json为:
{ "description" : "用户", "id" : 2, "personRole" : [{ "person" : { "id" : 3, "name" : "丙", "personRole" : [{ "person" : {"$ref" : "$.personRole[0].person"}, "role" : { "description" : "管理员", "id" : 1, "personRole" : [{"$ref" : "$.personRole[0].person.personRole[0]"}] } },{"$ref" : "$.personRole[0]"}] }, "role" : {"$ref" : "$"} }] }
以上 json 中出现的 $ref 表示引用,$ 表示自身,这是 json 语法中的一种非官方约定表达方式,用来描述含有循环引用的对象的序列化,主流的 javascript 框架都具有解析这种 json 的功能,也能够经过 javascript 原生语法来实现这个功能。以用户“丙”的 json 为例,通过解析后获得的 personJson 有以下特性:
personJson.id = 3; personJson.name = "丙"; personJson.personRole [0].person = personJson; /* 代表对象是能够自引用的 */ personJson.personRole [0].role.description = "管理员"; personJson.personRole [0].role.id = 1; personJson.personRole [0].role.personRole [0] = personJson.personRole [0]; personJson.personRole [1].person = personJson; personJson.personRole [1].role.description = "用户"; personJson.personRole [1].role.id = 2; personJson.personRole [1].role.personRole [0] = personJson.personRole [1];
而若是以角色“管理员”为例,通过解析后获得的 roleJson 有以下特性:
roleJson.description = "管理员"; roleJson.id = 1; roleJson.personRole [0].person.id = 3; roleJson.personRole [0].person.name = "丙"; roleJson.personRole [0].person.personRole [0] = roleJson.personRole [0]; roleJson.personRole [0].person.personRole [1].person = roleJson.personRole [0].person roleJson.personRole [0].person.personRole[1].role.description = "用户"; roleJson.personRole [0].person.personRole [1].role.id = 2; roleJson.personRole [0].person.personRole [1].role.personRole [0] = roleJson.personRole [0].person.personRole [1] roleJson.personRole [0].role = roleJson; /* 代表对象是能够自引用的 */
而若是以角色“用户”为例,通过解析后获得的 roleJson 有以下特性:
roleJson.description = "用户"; roleJson.id = 2; roleJson.personRole [0].person.id = 3; roleJson.personRole [0].person.name = "丙"; roleJson.personRole [0].person.personRole [0].person = roleJson.personRole [0].person; roleJson.personRole [0].person.personRole[0].role.description = "管理员"; /* 由于“管理员”先于“用户”加入,因此管理员序列化后“管理员”排在“用户”前 */ roleJson.personRole [0].person.personRole [0].role.id = 1; roleJson.personRole [0].person.personRole [0].role.personRole [0] = roleJson.personRole [0].person.personRole [0]; roleJson.personRole [0].person.personRole [1] = roleJson.personRole [0]; roleJson.personRole [0].role = roleJson; /* 代表对象是能够自引用的 */
由上可见此三者序列化后的 json 形式包含的信息量是相同的,也就是说,在双向相关模型中咱们能够对最容易获取的对象进行序列化,它的 json 中会自动包含全部与它有关联的对象(以及关联对象的关联对象)的信息,而后对json进行各类处理而不用担忧丢失信息,所以咱们能够说,双向相关模型的对象能够方便的进行“聚合”,同时由于数据自引用(压缩)在传输中也节约了宝贵的带宽空间。
在现实中咱们能够对“多对一”关系进行自动装载,对“一对多”关系按须要进行手动装载,以免聚合的对象过于庞大,这也是经实践证实可行的方式。
双向相关模型的缺点
双向相关模型对比单向模型须要更多的代码,例如 addPersonRole、removePersonRole、removeAllPersonRole 这些方法在单向模型中不须要指定,但在双向相关模型中须要严格定义,而 setPersonRole、setRole 这样的 setter 方法实现起来也比单向模型更复杂,可是咱们能够经过建模工具来自动生成模型代码。然而,现实中的问题还不止如此。
以 Java 语言为例,双向相关模型的代码须要全部模型类同时编译,在目前的开发工具中不难作到,可是若是咱们使用 Maven 来管理项目,考虑到项目的可扩展性,把不一样的业务模块放在不一样的 Maven 模块中时,就会出现问题。由于 Maven 模块不支持循环依赖(即模块 A 依赖于 B 同时模块 B 也依赖于 A),虽然能够经过插件 build-helper-maven-plugin 来实现这种模块的同时编译,可是 Maven 自己并不鼓励这么作。在开发项目时为了构建不借助插件就能够编译的模块,咱们须要在双向相关模型中再引入接口。仍以以上三个模型为例,咱们新建两个包:packageA 和 packageB,在 packageA 中存放 Role 类和一些接口,在 packageB 中存放 Person 类、PersonRole 类和一些接口,packageA 中的类彻底不调用 packageB 中的类,而 packageB 中的类会调用 packageA 中的类,以此来模拟两个 Maven 模块的单向依赖关系。
packageA 中代码以下:
package myPackageA; public interface PersonRoleFace { void setRole(Role role); }
package myPackageA; public class Role extends PojoSupport<Role> { public Role(Integer id, String description) { this.id = id; this.description = description; } private Integer id; private java.lang.String description; private java.util.Collection<PersonRoleFace> personRole; public java.util.Collection<PersonRoleFace> getPersonRole() { if (personRole == null) personRole = new java.util.LinkedHashSet<PersonRoleFace>(); return personRole; } public java.util.Iterator<PersonRoleFace> getIteratorPersonRole() { if (personRole == null) personRole = new java.util.LinkedHashSet<PersonRoleFace>(); return personRole.iterator(); } public void setPersonRole(java.util.Collection<? extends PersonRoleFace> newPersonRole) { removeAllPersonRole(); for (java.util.Iterator<? extends PersonRoleFace> iter = newPersonRole.iterator(); iter.hasNext();) addPersonRole((PersonRoleFace) iter.next()); } public void addPersonRole(PersonRoleFace newPersonRole) { if (newPersonRole == null) return; if (this.personRole == null) this.personRole = new java.util.LinkedHashSet<PersonRoleFace>(); if (!this.personRole.contains(newPersonRole)) { this.personRole.add(newPersonRole); newPersonRole.setRole(this); } else { for (PersonRoleFace temp : this.personRole) { if (newPersonRole.equals(temp)) { if (temp != newPersonRole) { removePersonRole(temp); this.personRole.add(newPersonRole); newPersonRole.setRole(this); } break; } } } } public void removePersonRole(PersonRoleFace oldPersonRole) { if (oldPersonRole == null) return; if (this.personRole != null) if (this.personRole.contains(oldPersonRole)) { for (PersonRoleFace temp : this.personRole) { if (oldPersonRole.equals(temp)) { if (temp != oldPersonRole) { temp.setRole((Role) null); } break; } } this.personRole.remove(oldPersonRole); oldPersonRole.setRole((Role) null); } } public void removeAllPersonRole() { if (personRole != null) { PersonRoleFace oldPersonRole; for (java.util.Iterator<PersonRoleFace> iter = getIteratorPersonRole(); iter.hasNext();) { oldPersonRole = (PersonRoleFace) iter.next(); iter.remove(); oldPersonRole.setRole((Role) null); } personRole.clear(); } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public java.lang.String getDescription() { return description; } public void setDescription(java.lang.String description) { this.description = description; } }
packageB 中代码以下:
package myPackageB; public interface PersonRoleFace { void setPerson(Person person); }
package myPackageB; import myPackageA.Role; public class PersonRole extends PojoSupport<PersonRole> implements myPackageA.PersonRoleFace, myPackageB.PersonRoleFace { private Integer id; private Person person; private Role role; public Person getPerson() { return person; } public void setPerson(Person newPerson) { if (this.person == null || this.person != newPerson) { if (this.person != null) { Person oldPerson = this.person; this.person = null; oldPerson.removePersonRole(this); } if (newPerson != null) { this.person = newPerson; this.person.addPersonRole(this); } } } public Role getRole() { return role; } public void setRole(Role newRole) { if (this.role == null || this.role != newRole) { if (this.role != null) { Role oldRole = this.role; this.role = null; oldRole.removePersonRole(this); } if (newRole != null) { this.role = newRole; this.role.addPersonRole(this); } } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
package myPackageB; public class Person extends PojoSupport<Person> { private Integer id; private java.lang.String name; private java.util.Collection<PersonRoleFace> personRole; public java.util.Collection<? extends PersonRoleFace> getPersonRole() { if (personRole == null) personRole = new java.util.LinkedHashSet<PersonRoleFace>(); return personRole; } public java.util.Iterator<? extends PersonRoleFace> getIteratorPersonRole() { if (personRole == null) personRole = new java.util.LinkedHashSet<PersonRoleFace>(); return personRole.iterator(); } public void setPersonRole(java.util.Collection<? extends PersonRoleFace> newPersonRole) { removeAllPersonRole(); for (java.util.Iterator<? extends PersonRoleFace> iter = newPersonRole.iterator(); iter.hasNext();) addPersonRole((PersonRoleFace) iter.next()); } public void addPersonRole(PersonRoleFace newPersonRole) { if (newPersonRole == null) return; if (this.personRole == null) this.personRole = new java.util.LinkedHashSet<PersonRoleFace>(); if (!this.personRole.contains(newPersonRole)) { this.personRole.add(newPersonRole); newPersonRole.setPerson(this); } else { for (PersonRoleFace temp : this.personRole) { if (newPersonRole.equals(temp)) { if (temp != newPersonRole) { removePersonRole(temp); this.personRole.add(newPersonRole); newPersonRole.setPerson(this); } break; } } } } public void removePersonRole(PersonRoleFace oldPersonRole) { if (oldPersonRole == null) return; if (this.personRole != null) if (this.personRole.contains(oldPersonRole)) { for (PersonRoleFace temp : this.personRole) { if (oldPersonRole.equals(temp)) { if (temp != oldPersonRole) { temp.setPerson((Person) null); } break; } } this.personRole.remove(oldPersonRole); oldPersonRole.setPerson((Person) null); } } public void removeAllPersonRole() { if (personRole != null) { PersonRoleFace oldPersonRole; for (java.util.Iterator<? extends PersonRoleFace> iter = getIteratorPersonRole(); iter.hasNext();) { oldPersonRole = (PersonRoleFace) iter.next(); iter.remove(); oldPersonRole.setPerson((Person) null); } personRole.clear(); } } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public java.lang.String getName() { return name; } public void setName(java.lang.String name) { this.name = name; } }
(packageA 和 packageB 中都有 PojoSupport 类,和以前的彻底相同,此处再也不累述)
如今咱们能够把 myPackageA 包放在 Maven 模块 A 中,把 myPackageB 包放在 Maven 模块 B 中,PersonRole须要实现 myPackageA.PersonRoleFace 接口,所以B依赖A,这样就实现了项目使用双向相关模型建模同时又具有 Maven 易于扩展的优点。
总结
本文中的代码能够在 https://gitee.com/limeng32/biDirectPojo 中找到。双向相关模型是面向对象编程在发展的过程当中发现的一种新理论,它表明软件开发者对模型关系本质的更深刻理解。咱们都有这样的体会,先进的软件必以先进的算法为基础,而建模思想也是一种算法。固然,算法探索之路永无止境,但目前在B/S架构的软件领域,双向相关模型确实拥有优秀的表现,咱们能够放心的使用这种建模方式做为咱们愈来愈复杂的网络应用的基石。
彩蛋
本文是本人开源项目 flying (地址见 https://www.oschina.net/p/flying)在开发最新版本时须要用到的理论基础之一。由于这次 flying 新版本加入大量创新,预计不久后还会有另外一篇理论文章来描述新特性。顺带一提,由于 flying 每次要释出多个版原本适配不一样版的 mybatis,下一个版本将不在使用数字型名称,取而代之的是汉字 “初雪”,大概在北京迎来第一场雪时就能发布。至于为何是这么一个有诗意的名字,容我这里卖个关子,当您看到 flying-初雪 新特性时就会明白。