讨论一种低耦合的数据持久化方案
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/81/ )。
在软件开发过程中,进行持久化操作是十分常见的,甚至很多后端程序员谑称自己为CRUD工程师。
传统层式方案
通常,我们进行数据持久化时,会使用Service、Dao、XML(例如MyBati中配置的XML)这三层结构。然后,对象在这三层结构中流动,如下图所示。
向下流动便完成了对象写入数据库的操作,向上流动便完成了对象从数据库读出的操作。
这样的持久化方案十分简单且通用,因此得到了非常广泛的应用。但是也存在以下几个问题:
首先是,耦合严重。一个对象和它的Service、Dao、XML都是存在耦合的,其耦合点很多。
其次是使用复杂,例如要完成一个对象的持久化,则需要先找到它的Service然后调用。在进行包含多种对象的批量操作时,找到某个对象的Service的过程将变得十分复杂,往往需要按照一定的命名规则进行反射。
例如,对不同类型对象的集合进行持久化,则如下所示:
for(Object obj : objectSet) {
// 找到该对象对应的Service
ObjService objService = findServie(obj);
// 调用其Service提供的持久化功能
objService.insert(obj);
}
findServie方法将会是一个较为复杂的方法。
第三,其不太符合面向对象原则。因为,持久化这件事情,应该是某个对象自身的行为,没有必要依赖外部的其它类完成。
对象行为式方案
我们可以尝试将对象的持久化作为对象自身固有的行为。
例如,在对象中设置一个saveToDb()方法。只要调用某个对象的saveToDb()方法,则将该对象持久化。按照这种方式,如果对不同类型对象的集合进行持久化,则如下所示:
for(Object obj : objectSet) {
// 持久化该对象
obj.saveToDb();
}
显然,操作变得更简单。
更重要的是,saveToDb()方法是对象自己的行为,与toString()等方法一样,即如下图所示。
这更符合面向对象的思想。
当存在大量对象时,这一方案带来的便利性更加凸显。例如存在下图所示的一棵树。
则每个节点的持久化方法可以这样写:
save() {
this.saveToDb();
for(Node node: this.children) {
node.save();
}
}
然后调用根节点的save()方法便可以依次触发各个对象节点持久化,逐级触发,一气呵成。
而不同对象节点的saveToDb()可以自由掌控,按照不同的规划放入不同的表中。
对象行为式方案中的查询
为了实现单个对象的查询,我们可以设置一个queryfromDb()方法,表示从数据库读取对象的数据。但是,读取数据之前,则必须要事先写定一些索引信息。例如下面的方法表示以id=5和name="易哥"为索引,从数据库中查询出指定的User对象。
User user = new User();
user.setId(5);
user.setName("易哥");
// 以上面的既定信息为索引,去数据库查询一个满足条件的User对象
user.queryfromDb();
如果查询的类型比较多,可以演化出queryFromDbById()、queryFromDbByName()、queryFromDbByIdAndName()等诸多版本。但是总体而言,这比传统层式方案要更清晰,也没有增加复杂度。
真正让对象行为式方案稍显疑惑的是批量查询,不过也有具体的解决方案。
如果存在多个age=18的User对象,则下面的查询就无法进行。
User user = new User();
user.setId(18);
user.queryfromDb();
因为,queryfromDb()这一行为可能会得到多个对象,因此,它不是在属于一个特定对象的行为,而是属于一类对象的行为。
一类对象,就是类啊!
因此,当查询出的结果是多个User时,这个queryfromDb()方法应该属于这个类,而不是具体对象。因此,它是一个类方法。
所以:
- 查询单一对象是对象方法
- 查询批量对象是类方法
但是考虑到单一对象查询也可能出现查询不到的情况,即查询结果为null。因此,将所有查询操作都作为类方法是合理的。
同理,删除方法也作为类方法。因为,删除也可能是批量的。
对象行为式方案的落地
在具体实施时,我们建议只保留saveToDb()、queryfromDb()、delFromDb()这三类方法。
其中,具体实施如下:
- 对于查询方法,使用类方法
queryfromDb(),可以演化出众多版本,例如queryFromDbByAge()、queryFromDbByName()、queryFromDbById()等。 - 对于删除方法,使用类方法
delFromDb(),可以演化出众多版本,例如delFromDbByAge()、delFromDbByName()、delFromDbById()等。 - 对于新增操作,创建完对象后,调用对象方法
saveToDb()。 - 对于修改方法,查询完对象,修改结束后,调用对象方法
saveToDb()。
在上述方案中,存在以下的问题,不严重但也值得总结:
- 更新方案效率稍低,因为其将更新拆分为了一个查询加一个修改
但总体而言,该方案要比传统层式方案更好,尤其是在更新操作较少的系统中。并且在面对大量对象持久化需求时,它能极大地提升项目整体的清晰度。
这种对象行为式方式,我们已经在开发中进行了运用,并收到了良好的效果。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。