1.表数据入口
充当数据库表访问入口的对象,一个实例处理表中所有的行。
表数据入口包含了用于访问单个表或视图的所有sql,如选择,插入,更新,删除等。其他代码调用它的方法来实现所有与数据库的交互。
1.运行机制
表数据接口很简单,一般包括几个从数据库中获取数据的查找方法以及更新,插入和删除方法。每个方法都将输入参数映射为一个sql调用并在数据库连接上执行该语句。
由于表数据如克用于数据读写,因此通常是无状态的。
表数据入口最富有技巧性的地方是它如何从查询返回信息。即使是一个根据ID的简单查询也会返回多个数据项。
一种方法是返回某种简单数据结构,如映射(map)。使用映射可以解决问题,但是必须将数据从来自数据库的数据集复制到映射中。用映射来传递数据不是一种好方法,因为它破坏
编译时检查,而且不是一个非常显式化的接口,这样当对映射中的内容拼写错误时会产生错误。
一种更好的方法是采用数据传输对象。虽然需要创建另外一个对象,但是该对象可能在其他地方很好用。
表数据入口可以和表模块一起良好的工作。
如果当前使用的是领域模型,可以用表数据入口返回适当的领域对象。
大多数使用表数据入口的时候,对数据库中的每一张表使用一个入口。但是对非常简单的情况,可以只用一个表数据入口处理所有表的全部方法。
2.使用时机
表数据入口可能是使用起来最简单的数据库接口模式,因为它能很好的映射到数据库或记录类型上。另外,它也为封装对数据源的精确访问逻辑提供了一种自然的方法。通常表数据
如克和领域模型很少一起使用,因为数据映射器更好的分离了领域模型和数据库。
表数据入口可以通表模块一起很好的使用,它产生一个记录集数据结构由表模块处理。
同行数据入口一样,表数据入口特别适用于事务脚本。二者之间的选择归结于如何处理多数据行。当结果集的表现便于事务脚本处理,更喜欢用表数据入口。
利用表数据入口封装数据库范文的好处之一是,相同的接口既可以用于sql操作数据库,又可以用于存储过程。存储过程自身的组织和表数据入口类似。通过这种方法,插入和更新存储
过程封装了实际的表结构。
2.行数据入口
充当数据源中单条记录入口的对象,每一行一个实例。
在内存对象中嵌入数据库访问代码可能会带来一些麻烦。首先,如果内存中的对象有自己的业务逻辑,增加数据库操作代码会增加复杂度,测试也很困难,因为如果内存中的对象和数据库绑定
在一起,所有数据库访问都会使得测试的运行速度变慢。
行数据入口提供了看起来像记录结构中记录的对象,但可以用编程语言的常规机制访问它。所有对数据源的访问细节都隐藏在这个接口之后。
1.运行机制
行数据入口是和单条记录及其相似的对象,如数据库中的一行。在该对象中数据库中的每一列变成了一个域。行数据入口一般能实现从数据源类型到内存中类型的任意转换,这种转换很简单。
行数据入口是每一数据行的良好接口,这种方法尤其适用于事务脚本。
使用行数据入口要面对的问题是,在哪里存放该模式的查找操作。可以选择静态查找方法,但是它不支持需要为不同数据源提供不同查找方法的多态。在此情况下有必要设置单独的查找方法对象,
这样关系数据库中的每一张表都有一个查找方法和一个入口来获取结果。
很难说清楚行数据入口和活动记录之间的区别。这个问题的关键要看是否存在任何领域逻辑。如果存在则是活动记录。行数据入口仅包含数据库访问逻辑而没有领域逻辑。
就像其他形式的表封装一样,行数据入口除了可以用于表外还可以用于视图或查询。
2.使用时机
选择行数据入口时通常分两步:第一步,是否真的需要入口;第二部,是使用行数据入口还是表数据入口。
在使用事务脚本的时候,经常使用行数据入口。这样能很好的分离数据库访问代码,并且也很容易被不同的事务脚本重用。
在使用领域模型的时候,不适用行数据入口。如果映射简单,活动记录在不增加额外代码的情况下也可以达到相同的效果。如果映射复杂,数据映射器更有效,因为它能很好的将领域对象和数据结构
解耦,领域对象不需要知道数据库层。当然,也可以用行数据入口把领域对象从数据结构屏蔽开来。如果要改变数据库结构但不想改变领域规则,那么采用行数据入口是一种更好的做法。但规模大的时候,
这样做会产生三种数据表示:一个在业务逻辑中,一个在行数据入口中,一个在数据库中。
行数据入口和数据映射器可以一起配合使用。不过,当行数据入口从元数据自动生成,而数据映射器由手动实现时,这种方法会很有效。
3.活动记录
一个对象,它包装数据库表或视图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。
对象既有数据又有行为。这些数据中大多数都是持久数据,并且需要保存到数据库。
1.运行机制
活动记录的本质是一个领域模型,这个领域模型中的类和基数据库中的记录结构十分吻合。每条活动记录负责向数据库保存数据,从数据库中加载数据以及处理作用于数据之上的领域逻辑。
活动记录的数据结构应该与数据库中的结构完全匹配:类的每个域对应表的每一列。按照sql接口提供数据的方式键入各个域 --- 此阶段不要做任何转换。
活动记录通常具有下列方法:
1.由sql结果集中的一行构造一个活动记录实例
2.为将来对表的插入构造一个新的实例
3.用静态方法查找来保证常用sql查询和返回活动记录
4.更新数据库并将活动记录中的数据插入数据库
5.获取或设置域
6.实现部分业务逻辑
getting 和 setting 方法可以做其他一些巧妙的事情,如把面向sql类型转换为较好理解的内存中类型。同样,访问一张相关表,获得方法能发挥适当的活动记录,甚至不需要数据结构中的
标识域(通过查找)也能做到这一点。
活动记录与行数据入口十分类型。二者的主要差别是行数据入口仅有数据访问而活动数据既有数据源逻辑又有领域逻辑。
由于活动记录和数据库间的紧耦合,在这个模式中常用静态查找方法。但也没有理由说明不能把查找方法分离为一个单独的类。
活动记录也可以像表一样用在视图和查询上。
2.使用时机
活动记录用于不太复杂的领域逻辑,如创建,读,更新和删除等。
在领域模型的初始设计中,主要是要从活动记录和数据映射器中选择其一。活动记录的优点是简单,活动记录容易创建,并且易于理解。其主要问题在于,仅当活动记录对象和数据库中表直接对应,
即二者为同构模式时,活动记录才比较有效。如果业务逻辑复杂,就会想到使用对象的直接关系,集合和继承等。它们都难以映射到活动记录,逐渐添加它们会显得很凌乱,这也是使用数据映射器的原因。
不用活动记录的另外一个原因是它要求对象的设计和数据库设计紧耦合,这使得项目中的进一步重构很困难。
如果正在使用事务脚本,并且已经开始体会到代码复制带来的痛苦,以及事务脚本常常带来的表和脚本更新困难,那么用活动记录是一个好的选择。
4.数据映射器
在保持对象和数据库(以及映射器本身)彼此独立的情况下在二者之间移动数据的一个映射器层。
对象和关系数据库用来组织数据的机制不同。对象的很多部分(如集合和继承)在关系数据库中不存在。当创建一个具有大量业务逻辑的对象模型时,有必要采用这些机制更好的组织它的数据和行为。如此一来,
便产生了不同的方案,也就是说对象方案和关系方案不相配。
我们仍然需要在两种方案之间传递数据,这种数据传递是一件很困难的事。如果内存对象知道关系数据库的结构,则其中一个改变往往影响到另外一个。
数据映射器是分离内存对象与数据库的一个软件层。其职责是在内存对象与数据库之间传递数据并保持它们彼此独立。有了数据映射器,内存对象甚至不需要知道数据库的存在;它们也不需要sql接口代码,当然
也不需要知道数据库方案。由于数据映射器是映射器的一种形式,因此数据映射器自身根本不为领域层所察觉。
1.运行机制
分离领域和数据源是数据映射器的主要功能。整个数据映射器层可以替换。
简单的数据映射器只是在域和域一一对应的基础上,将一张数据库表中的表映射为内存中意义对等的类。映射器需要各种各样的策略来处理那些转变为多个域的类,具有多表的类,带有继承的类等。
对于插入和修改,数据库映射层需要知道对象发生了哪些变化,哪些是新创建的,哪些是已经删除的。另外,还必须把整个工作负荷装配到事务框架中去。工作单元模式是将它们组织起来的好办法。
一个应用程序可以有一个或者几个数据映射器。如果映射器是硬编码的,最好为每个领域类或领域层次的根使用一个数据映射器。如果正在使用元数据映射,那么用一个映射器类就够了。在后一种情况下,
受限制的地方是查找方法。在大规模的应用中,如果仅用一个由许多查找方法的映射器,可能查找方法太多。因此有必要根据每个领域类或领域层次的超类分解这些方法。这样会得到很多小的查找类。但是
也便于开发者定位他所需的查找方法。
对于任意的数据库查找行为,为了保持从数据库中读取对象的一致性,需要使用标识映射。方法有2种:要么有表示映射的注册表,要么让每个方法持有一个表示映射。
1.处理查找方法
要使用一个对象,首先必须把它从数据库中读入。通常表现层通过加载某些初始化对象进行初始化。接着控制便转移到领域层,这时候代码主要通过对象间的关联从一个对象移动到另一个对象。如果
领域层有它所有需要的全部对象,或者在需要的时候使用延迟加载附加对象,则这种方法很有效。
偶尔也需要领域对象调用数据映射器上的查找方法。用一个好的延迟加载完全可以避免这一点。
2.把数据映射到领域对象的域
映射器需要访问领域对象中的域(属性)。这往往是个问题,因为需要一些公共方法支持领域逻辑不需要的映射器。这个问题没有见到的解决办法。可以在打包时将数据映射器与领域对象放在一起。比如
Java中,可以将二者打在同一个包中,这样,映射器的可见性就会低一点。但会导致更复杂的依赖关系,因为我们并不想让系统中知道领域对象的其他部分知道映射器。可以使用反射,反射可以绕过语言
的可见性原则。虽然这比较慢,但如果与错误的sql调用所用的耗时相比,可能也不算慢。或者使用共同方法,但要用一个状态与保护他们,以便当这些方法的使用超出数据库加载的上下文时抛出异常。
这样一来,按这种方式为它们命名,就不会把它们当做常规的获取方法和设置方法。
与之相关联的是创建对象时的问题。大体上有两种选择:一种选择是用丰富的构造函数的方式创建对象,则对象创建时至少带有其必要的数据。另外一种方法是创建一个空对象,然后向其中一如必备的对象。
通过丰富构造函数创建的方法存在的问题是必须注意循环引用。如果两个对象彼此互相引用,则每次要加载其中一个对象时,该对象将试图加载另一个对象,而后者反过来又试图读入第一个对象,如此反复直到
耗完所有的堆栈空间。要避免这一点需要由处理特例的代码,通过使用延迟加载。写这些特例代码显得非常凌乱,因此尽量不要使用它。创建空对象可以做到这一点。用无参构造函数创建一个空白对象,然后将它
直接插入标识映射中。这样如果循环,标识映射就会返回一个对象终止循环加载。
以这种方式使用空对象意味着,可能需要为那些对象一经加载就不可改变的值提供一些设置函数。命名黄钻换结合某些状态检查保护方法使用,可以完善这一点。另外也可以使用反射加载数据。
3.基于元数据的映射
需要做的决策之一是存储如何将领域对象中的域映射到数据库列的信息。最简单,往往也是最好用的方法是用显式代码实现它,这要求为每个领域对象提供一个映射器类。映射器通过赋值实现映射,并用域(通常
是常量字符串)保存访问数据库的sql语句。另外一种方法是用元数据映射,这种方法把元数据作为数据存储在类或单独的文件中。元数据的最大优点是映射器的所有变化通过数据处理,而不用更多的源代码,也不用
代码生成或反射程序。
2.使用时机
使用数据映射器的主要时机是数据库方案和对象模型需要彼此独立演变的时候。最常见的情况是和领域模型一起使用。数据映射器的主要优点是无论是在设计阶段,开发阶段,还是测试阶段,在领域模型上操作时可以
不考虑数据库。领域对象对数据的结构一无所知,因为所有这些对应关系都由数据映射器完成。
这在编码的时候是有用的,因为可以在不清楚领域对象在数据库是如何存储的情况下理解和操作领域对象。可以修改领域对象或数据库而不必改动另外一方。
https://juejin.im/post/5c544bf4f265da2d8e70bbaa