11.企业应用架构模式 --- 对象-关系行为模式

1.工作单元
	维护受业务事务影响的对象列表,并协调变化的写入和并发问题的解决。
	从数据库中存取数据时,记录所修改的内容是非常重要的;否则,那些改变的数据将不会被写回到数据库中。同样,必须插入创建的新对象和移除已删除的对象。
  可以在每次修改对象模型时对数据库进行相应的修改,但这样会产生大量规模很小的数据库调用,从而导致速度变慢。而且这样做还需要有一个对整个交互过程都开发的事务。
  如果存在一个贯穿多个请求的业务事务,这就是不合实际的。

  	工作单元记录在业务事务过程中对数据库有影响的所有变化。操作结束后,作为一种结果,工作单元了解所有需要对数据库做的改变。

  	1.运行机制
  		引发你去处理数据库的明显原因是变化的:创建新对象和更新或删除已经存在的对象。工作单元就是一个记录这些变化的对象。只要开始做一些可能会对数据库有影响的操作,
  	  就创建一个工作单元去记录这些变化。每当创建,改变或者删除一个对象时,就通知次工作单元。也可以让此工作单元知道所读过的对象,通过验证在整个事务处理过程中数据库
  	  中的所有对象都没有改变,从而检查不一致性。
  	  	工作单元的关键是在提交的时候,它决定要做什么。它打开一个事务,做所有的并发检查(使用悲观锁或者乐观锁)向数据库写入所在的修改。开发人员根本不用显式调用数据库
  	  更新方法。这样,他们就不用担心记录所修改的内容或者不必担心引用完整性如何影响他们的操作顺序。
  	  	为了做到这一点,工作单元需要知道它应该记录哪些对象。可以由调用者实现,也可以让发生变化的对象通知工作单元。
  	  	a) 用调用者注册的方式,用户如果改变了某个对象就必须将它注册到工作单元。任何没有注册的对象提交时都不会写入数据库。
  	  	b) 用对象注册的方式,调用者就不再负责注册。这里常用的技巧是把注册方法置于对象方法中。从数据库加载对象会将加载的对象注册为'干净的';setting方法会将要设置的对象
  	  注册为'脏'的。为了使得这种方法可行,工作单元要么被传递给对象,要么被放在一个众所周知的地方。即使进行对象注册,也要需要注意一些地方,这就是,对象开发者必须记住
  	  在适当的地方加入注册调用代码。
  	  	c) 另一种技术是工作单元控制器。在这里,工作单元控制所有数据库的读操作,一旦对象呗读取,就将它注册为'干净'的对象,而不是将对象标记为'脏'对象。工作单元在读操作的时候
  	  将产生一个拷贝,在提交时比较当前对象和拷贝对象,看对象是否发生了变化。虽然这加重了提交过程的负担,但是使得只对那些真正改变了的域进行有选择的更新;也避免了在对象域内执行
  	  注册调用。一个折中的方法是只拷贝改变了的对象。这需要注册,但是它支持有选择的更新,因此当读操作大大超过写操作时,它还会极大的降低拷贝操作的负担。

  	    工作单元的另一个用武之地是当数据库使用引用完整性时用它来保证更新顺序。大多数情况下,只需要保证数据库在事务提交时检查引用完整性即可,无需在每次sql调用时都检查。工作单元
  	  是确定更新次序最自然的地方。在小型系统中,可以用显式代码来实现这一点,该代码中包含了根据外键决定先写哪张表的系统。在较大应用中,最好用元数据指出按照什么样的顺序写数据库。
  	  	可以用相似的技术最大限度的减少死锁的可能性。如果每个事务都按照相同的顺序处理需要进行编辑的白鸥,就会大大降低死锁的风险。工作单元是保存固定写表顺序的理想地方,这样总可以用
  	  相同的顺序来访问表。
  	  	对象要能够找到它们当前的工作单元。对此,好的方法是用一张线程范围内的注册表。另一个方法在分发调用或者对象创建时,把工作单元传递给需要它的对象。每种方法都必须保证访问工作
  	  单元的线程不超过一个,否则会有意想不到的结果。
  	  	工作单元也是处理批量更新的方法之一。批量更新的思想是把若干sql命令作为一个单独的单元发送,这样可以在一次单独的远程调用中得到处理。在快速连续发送很多个更新,插入和删除命令
  	  时这种思想很重要。
  	  	工作单元不仅能作用于数据库,还可以作用于任何事务资源,所以也可以用它来调整消息队列和事务监控。

  	2.使用时机
  		工作单元解决的基本问题是记录操作过程的各种对象,以便知道为了使内存中的数据与数据库同步需要考虑哪些对象。
  		最简单的办法是,在修改一个对象时就显式的保存该对象。但是带来的问题可能使用数据库的调用比想象的多。要避免多重数据库的调用,可以把对数据库的更新操作放到最后。为了做到这一点,
  	  需要记录已经改变的所有对象。可以在代码中用变量来实现记录跟踪,但是一旦变量很多,变量很快难以管理。一般来说,变量与事务脚本运行得很好,但很难与领域模型一起使用。
  	  	把每个所改变的对象加上'脏'标记的做法要比把对象保存在变量中好。在事务处理完后,需要找出所有加了'脏'标记的对象并把它们写入数据库中。这项技术取决于寻找'脏'对象的难以程度。如果
  	  所有的'脏'对象都在一个单一层次结构上,就可以遍历该层次结构向数据库写入所有被改变了的对象。然后,跨越一个更一般的对象网络是比较困难的。

2.标识映射
	通过在映射中保存每个已经加载的对象,确保每个对象只加载一次。当要访问对象的时候,通过映射来查钊它们。

	1.运行机制
		标识映射最基本的思想是使用一系列映射,这些映射包含了从数据库读出的对象。在简单情况下,使用同构方案时,就要为数据库中的每张表建立一个映射。当从数据库加载对象时,首先
	  要检查映射。如果映射中存在与要加载对象相一致的对象,返回该对象。如果不存在,访问数据库,加载对象的同时把它们写入映射以备将来引用。还有大量的实现策略需要考虑。同意,由于
	  标识映射与并发管理交互,还应该考虑使用乐观离线锁。
	  	1.键选择
	  		首先要考虑的问题就是映射的键。最自然的就是选择相应数据库表的主键。如果键只有一列且不可改变则这种方法比较有效。一个代理主键非常适合这个用途,因为它可以把它作为映射
	  	  的键。这些键通常是一个简单的数据类型,所以比较行为能高效运行。
	  	2.显式的还是通用的
	  		必须确定把标识映射设计为显式还是通用的。显式的标识映射为每一种需要的对象提供不同的方法,例如,findPerson(1)。通用的映射为所有的接收对象提供统一的方法,而用一个
	  	  参数指出所需的对象类型,例如,find("Person",1)。最明显的好处是可以用通用和可重用对象支持通用映射很容易就能构造一个可重复使用的注册表,它为各种各样的对象服务,而且
	  	  增加新的映射时并不需要更新。
	  	    然后,还是推荐显式的标识映射。首先,在一种强数据类型的语言中用显式数据映射能够做编译检查。但是,其优点远不止于此,它还拥有显式接口的其他全部优点:比较容易看到哪些
	  	  映射是可用的,它们怎么被调用。虽然这意味着每增加一个映射就要加入一个方法,但对显式化的优点来说也不过是一个小的负担。
	  	3.数量
	  		标识映射的数量选择范围可以从一个类对应一个映射到整个会话对应的一个映射。只有当数据库有唯一键时才选用整个会话使用一个映射。一旦建立标识映射,其好处在于只有一个地方可以
	  	  访问,也不会出现关于继承的严重错误。
	  	  	如果用多映射,最自然的方法是每个类或每个表对应对应一个映射,如果数据库方案和对象模型是相同的,这种方法很适用。如果二者不同,通常来说使映射基于对象比基于表容易一些,
	  	  因为对象并不需要真正知道映射的复杂关系。
	  	4.把标识映射放到哪里
	  		标识映射必须放在容易找到的地方。也可以把它们捆绑到当前进程的上下文中。要确保每个会话都有自己的实例,并独立于任何其他会话的实例。因此,要把标识映射放到某个特定会话的
	  	  对象中。如果正在使用工作单元,则把标识映射放到工作单元中。到目前为止,工作单元是防止标识映射最理想的地方,因为工作单元负责记录进出数据库的数据。如果没有使用工作单元,
	  	  最好的位置是捆绑到会话的注册表。

	2.使用时机
		一般来说,用一个标识映射来管理所有修改了数据库读出对象。主要是不希望出现两个内存对象对应同一条数据库记录的情形---在这种情形下可能会因为2次对记录的修改不一致而使数据库
	  映射混乱。标识映射的另一个用途是作为数据库表读取操作的高速缓存。也就是说,不需要每次访问某数据都去访问数据库。
	  	不变对象可能用不着标识映射。如果不会改变一个对象,那么也就不必担心修改异常。既然值对象是不变的,自然就不需要对值对象使用标识映射。尽管如此,标识映射仍然有其优点,
	  最重要的优点就是高速缓存的性能高;另外一个优点是它有助于防止使用相等测试的错误形式,如Java中不能覆盖==,因此常常出现错误的相等测试。
	    对依赖映射不需要建立标识映射。因为关联对象的存在受父对象的控制,没有必要用映射保存标识。

3.延迟加载
	一个对象,它虽然不包含所需要的所有数据,但是知道怎么获取这些数据。
	为了把数据从数据库加载到内存中,设计专门的对象会使加载更加方便。这样在加载所需对象的同时也可以把与之相关的对象加载到内存中。这会使得开发人员进行加载更加容易,否则必须
  显式加载所有需要的对象。
  	然后,如果由这个逻辑得出结论,就会出现这种情况:加载一个对象会引起大量相关对象的加载。当真正需要的对象只有几个的时候,这样做会损害系统的性能。
  	延迟加载会暂时中止这个加载过程,方法是在对象结构中设立一个标志以便使需要的数据在用到时才被加载。

  	1.运作机制
  		主要有4种实现延迟加载的方法:延迟初始化,虚代理,值保持器和重影。
  		a) 延迟初始化
  			是最简单的方法。它的基本思想是每次访问属性域都要先检查该域是否为空。如果为空,在返回域值之前计算出这个域的值。为了实现这一点必须确保这个域是自封装的,也就是说所有
  		  对该域的访问,即使来自类的内部,都要通过获取方法来实现。
  		  	用null来标记一个还没有加载的域也很有效,除了null是域中的合法取值的情况。在这种情况下,需要用其他的符号来标记还没有被加载的域,或者需要对空域使用特殊情况。
  		  	使用延迟初始化很简单,但是它往往会在对象和数据库间加强依赖关系。正因为如此,它最适用于活动记录,表数据入口和行数据入口。如果用数据映射器,需要一个附加的间接层,
  		  这可以通过虚代理获得。
  		b) 虚代理
  			虚代理是这样一个对象,它看起来是应该在域中的一个对象,但实际上它并不包含任何东西。只有当它的一个方法被调用的时候,它才从数据库加载恰当的对象。虚代理的优点是它
  		  看上去完全就是需要放在那里的对象。但其缺点也就是在于它并不真的是那个对象,所以很容易就会陷入令人恼火的问题。另外,同一个实对象可以有很多虚代理。所有这些代理会有
  		  不同的对象特性,然后它们代表同一个概念上的对象。至少必须覆盖相等方法并记得用它而不是标识方法进行相等测试。如果没有它,将会遇到某些很难追踪的漏洞。
  		  	在某些环境中会出现另外一个问题:必须创建大量的虚代理,甚至要为每个正在代理的类创建一个虚代理。在动态数据类型语言中经常可以避免这个问题,但在静态类型语言中,
  		  问题往往变得糟糕。
  		c) 值保持器
  			对领域类,可以通过使用值保持器避免这些问题。它是一个用来包装某个其他对象的对象。要想获取基对象,可以访问值保持器得到它的值,但是只有第一次访问值保持器时它
  		  真正从数据库读取数据。值保持器的缺点是类需要知道它的存在,而且将丧失强数据类型显式性。可以通过确保值保持器从未被传出它自己的类来避免标识问题。
  		d) 重影
  			重影(ghost)是部分状态下的真实对象。当从数据库加载对象的时候,它只包含其ID。当每次要访问某个域时,它就会加载器完全状态。把重影看成一个对象,它的每个域都是
  		  被一下子延迟初始化的,或者把它看成一个虚代理,对象本身就是它的虚代理。当然,在一次访问中,并不需要加载全部数据;可以把经常一起使用的分为一组,从而把数据分成不同的
  		  组。如果使用重影,可以直接把它放到它的标识中。这种方式可以维护标识和避免所有在读数据时出现的循环引用带来的问题。

  		  	虚代理或重影不必完全没有数据。如果它们包含某些需要快速获取或经常用到的数据,在加载代理或重影时加载这些数据是很有意义的(有时称为轻对象)。
  		  	继承常常会给延迟加载带来问题。如果要用重影,需要知道要创建什么类型的重影,如果没有正确加载数据,往往就很难说了。虚代理在静态数据类型中也会遇到问题。
  		  	延迟加载的另一个风险是它容易导致产生超出需要的数据库访问。这种波动加载是一个很好的例子。

    2.使用时机
    	什么时候使用延迟加载完全取决于加载一个对象时需要从数据库读取多少数据和数据库调用的次数。通常没必要对和对象的剩余部分处于同一行中的域使用延迟加载,因为
      大多数情况下在一次调用中读取剩下的数据不会有更多开销,即使数据域很大。
        从性能角度来说,取决于什么时候想取回数据。通常比较好的方法是在一次调用过程中取回所有需要的数据,这样可以随取随用,尤其是在这些数据对应于同一用户界面一次
      交互时。使用延迟加载的最佳时机是,需要额外的调用,并且当使用助对象时所调用的数据没有用到的时候。

https://juejin.im/post/5c55d393518825626b76c784

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值