在开发网络应用时,建立模型是至关重要的一步。随着建模工具软件的发展,目前越来越多的软件采用双向相关模型建模,本文主要讨论的是双向相关模型与传统单向模型相比的优势。
双向相关模型和单向模型相比主要的区别在于其动态的描述了模型之间的交互关系:传统单项模型仅仅关注当前模型的状态变化,并不立刻考虑自身状态变化后对环境的影响;而双向相关模型则在当前模型状态的每一次变化之后,都立刻通知被此变化影响到的其他模型也要改变,即每一次操作中我们改变的并非是单个模型而是整个环境,因为这些模型之间都是由一对多或多对一关系关联起来的。
最方便生成双向相关模型的方法是借助于 PowerDesigner 工具。例如,如果我们需要对这样一个场景进行建模:人(person)和角色(role),一个人可以拥有多个角色,同时多个人也可以拥有相同的角色。这是一个典型多对多关系,那么使用 PowerDesigner 建模的结果可如下所示:
(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 方法简单高效的实现比较从数据库中查询出对象的“业务逻辑上的相等与不等”,同时它是基于链表实现因此元素排列有序,在实际开发软件时这些特性会给我们带来极大的好处。
双向相关模型的优势
模型的相关代码已经全部给出,我们可以通过一个用例来测试这个模型的效果。我们定义三个人员(甲、乙、丙)和两个角色(管理员、用户),其中甲是管理员,乙是用户,丙既是管理员也是用户。实现如下(为使说明足够简单清晰,我们假设 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 种不同的方式实现了甲、乙、丙三个人员和管理员、用户两个角色之间的关联:
/* 姓名为甲的人员具有管理员角色 */
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-初雪 新特性时就会明白。