一、概念
Lazy Load:一个对象,它虽然不包含所需要的所有数据,但是知道怎么获取这些数据。
延迟加载貌似很简单,就是在数据需要时再从数据库获取,减少数据库的消耗。但这其中还是有不少技巧的。
二、实现延迟加载
实现Lazy Load主要有四种方法:延迟初始化、虚代理、值保持器和重影。
(1)延迟初始化(Lazy initialization)
1.1 概念
这个是最简单的方法。意思就是每次访问属性域都要先检查该域是否为空,如果为空,再获取这个域的值。这样必须做到所有对该域的访问,即使来自类的内部,都要通过获取方法来实现。
1.2 代码实现
class Supplier{ private $products; public function getProducts(){ if($products == null) $products = $Product->findForSupplier(); return $products; } }
1.3 使用时机
只有在域需要另外的数据库访问时才考虑使用延迟加载。
需要额外的调用,并且当使用主对象时所调用的数据没有用到的时候。
最适用于活动记录、表数据入口和行数据入口。
(2)虚代理(virtual proxy)
2.1 概念
实质为一对象,不包含任何东西,只有当它的一个方法被调用时,它才从数据库加载恰当的对象。
说简单点说是一个对象的代理对象,初始化时不加载对象,只有当代理对象被调用方法时才真正去加载。
2.2 代码实现
/** * 虚代理,只有在被访问成员时才调用闭包函数生成目标对象。 */ class VirtualProxy { private $holder = null; private $loader = null; /** * @param Closure $loader 生成被代理对象的闭包函数 */ public function __construct(Closure $loader) { $this->loader = $loader; } /** * 代理成员方法的调用 * * @param string $method * @param array $arguments * @throws BadMethodCallException * @return mixed */ public function __call($method, array $arguments = null) { $this->check(); if (!method_exists($this->holder, $method)) { throw new BadMethodCallException(); } return call_user_func_array( array(&$this->holder, $method), $arguments); } /** * 代理成员属性的读取 * * @param string $property * @throws ErrorException * @return mixed */ public function __get($property) { $this->check(); if (!isset($this->holder->$property)) { throw new ErrorException(); } return $this->holder->$property; } /** * 代理成员属性的赋值 * * @param string $property * @param mixed $value */ public function __set($property, $value) { $this->check(); $this->holder->$property = $value; } /** * 检查是否已经存在被代理对象,不存在则生成。 */ private function check() { if (null == $this->holder) { $loader = $this->loader; $this->holder = $loader(); } } } // 测试 $v = new VirtualProxy(function(){ echo 'Now, Loading', "\n"; $a = new ArrayObject(range(1,100)); $a->abc = 'a'; // 实际使用中,这里调用的是 DataMapper 的 findXXX 方法 // 返回的是领域对象集合 return $a; }); // 代理对象直接当作原对象访问 // 而此时构造方法传入的 callback 函数才被调用 // 从而实现加载对象操作的延迟 echo $v->abc . $v->offsetGet(50);
(3)值保持器(value holder)
3.1 概念
一个用来包装某个其他对象的对象,要想获取基对象,可以访问值保持器得到它的值,但是只有第一次访问值保持器时它真正从数据库读取数据。
(4)重影(ghost)
4.1 概念
部分状态下的真实对象,当从数据库加载对象的时候,它只包含其ID。当每次要访问某个域时,它就会加载其完全状态。把重影看成一个对象,它的每个域都是被一下子延迟初始化的,或者把它看成一个虚代理,对象本身就是它的虚代理。
4.2 代码实现
//继承要加载的对象 class DeferredEventCollection extends EventCollection { private $stmt; private $valueArray; private $run=false;//标识当前加载状态 //构造方法,不真正获取数据,只包含其$valueArray(ID) function __construct( Mapper $mapper, \PDOStatement $stmt_handle,array $valueArray ) { parent::__construct( null, $mapper ); $this->stmt = $stmt_handle; $this->valueArray = $valueArray; } //加载完全状态 function notifyAccess() { if ( ! $this->run ) { $this->stmt->execute( $this->valueArray ); $this->raw = $this->stmt->fetchAll(); $this->total = count( $this->raw ); } $this->run=true; } }
三、延迟加载的风险
1、继承常常会给延迟加载带来问题。如果要用重影,需要知道要创建什么类型的重影,如果没有正确加载数据,往往就很难说了。虚代理在静态数据类型语言中也会遇到同样的问题。
2、延迟加载容易导致产生超出需要的数据库访问。书中别名“波动加载”。如用延迟加载来填充一个集合,然后每次只访问其中一个元素。这样就会每访问一个对象就访问一次数据库,而不是一次把所需对象全部读出来。这种加载方式会严重影响系统的性能。当然,可以不使用延迟加载的集合,而使类集合本身成为延迟加载,加载类集合时一次加载全部内容。
四、小结
延迟加载很适合于面向方面(AOP)的程序设计。可以将延迟加载操作置于一个单独的方面,这样能独立改变延迟加载策略,领域开发者也不必处理延迟加载的问题了。
无论你是否在领域类中显式地添加延迟加载代码,延迟加载都是一个好习惯。除了类型安全之外,使用集合对象而非数组的好处是你可以使用延迟加载。
相关推荐
当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从而提高了服务器的性能。 3.Hibernate中怎样实现类之间的...
6.3.6 实现延迟加载 6.4 使用O/RM工具增强数据访问层 6.4.1 对象/关系映射器 6.4.2 使用O/RM工具创建数据访问层 6.5 是否应该使用存储过程 6.5.1 有关存储过程的传言 6.5.2 那么动态SQL呢 6.6 小结 ...
6.3.6 实现延迟加载 6.4 使用O/RM工具增强数据访问层 6.4.1 对象/关系映射器 6.4.2 使用O/RM工具创建数据访问层 6.5 是否应该使用存储过程 6.5.1 有关存储过程的传言 6.5.2 那么动态SQL呢 ...
此外,动态代理还可延迟加载对象、实现权限控制和结果缓存等功能。 在实际开发中,动态代理提供了很多好处。首先,它可以帮助我们实现代码重用,因为代理类可以为多个真实对象提供代理服务。其次,动态代理可以实现...
②延迟加载、 性能优化 ③HQL 查询、 条件查询、 SQL 查询 ④二级缓存与查询缓存 3、 Struts ①MVC 模式与 Struts 体系 4、 mybatis 5、 MVC 框架 6、 各框架对比与项目优化 7、 JPA ①EJB 三、 Java web 开发核心...
1.2.6. 加载并存储对象 1.3. 第二部分 - 关联映射 1.3.1. 映射Person类 1.3.2. 单向Set-based的关联 1.3.3. 使关联工作 1.3.4. 值类型的集合 1.3.5. 双向关联 1.3.6. 使双向连起来 1.4. 第三部分 - Event...
21.8 Dispose模式:强制对象清理资源 21.9 使用实现了Dispose模式的类型 21.10 C#的using语句 21.11 一个有趣的依赖性问题 21.12 手动监视和控制对象的生存期 21.13 对象复活 21.14 代 21.15 用于本地资源的...
21.8 Dispose模式:强制对象清理资源 21.9 使用实现了Dispose模式的类型 21.10 C#的using语句 21.11 一个有趣的依赖性问题 21.12 手动监视和控制对象的生存期 21.13 对象复活 21.14 代 21.15 用于本地资源的...
21.8 Dispose模式:强制对象清理资源 21.9 使用实现了Dispose模式的类型 21.10 C#的using语句 21.11 一个有趣的依赖性问题 21.12 手动监视和控制对象的生存期 21.13 对象复活 21.14 代 21.15 用于本地资源的...
21.8 Dispose模式:强制对象清理资源 21.9 使用实现了Dispose模式的类型 21.10 C#的using语句 21.11 一个有趣的依赖性问题 21.12 手动监视和控制对象的生存期 21.13 对象复活 21.14 代 21.15 用于本地资源的...
5. 对象/关系数据库映射基础(Basic O/R Mapping) 5.1. 映射定义(Mapping declaration) 5.1.1. XML名称空间 5.1.2. hibernate-mapping 5.1.3. class 5.1.4. id 5.1.4.1. (主键生成策略)generator 5.1.4.2. 高/...
5. 对象/关系数据库映射基础(Basic O/R Mapping) 5.1. 映射定义(Mapping declaration) 5.1.1. XML名称空间 5.1.2. hibernate-mapping 5.1.3. class 5.1.4. id 5.1.4.1. (主键生成策略)generator 5.1.4.2. 高/...
而且引入了全新的CBD(核心+行为+驱动)架构模式,旨在打造DIY框架 和AOP编程体验,让ThinkPHP能够在不同方面都能快速满足项目和应用的需求, 并且正式引入SAE、REST和Mongo支持。 使用ThinkPHP,你可以更方便和...
15.1.2 外部类之内创建内部类对象 322 15.1.3 外部类之外创建内部类对象 323 15.1.4 内部类与外部类之间的成员互访 324 15.1.5 内部类与外部类的预定义对象引用this 327 15.2 局部内部类 328 15.2.1 局部...
1.2.6. 加载并存储对象 1.3. 第二部分 - 关联映射 1.3.1. 映射Person类 1.3.2. 单向Set-based的关联 1.3.3. 使关联工作 1.3.4. 值类型的集合 1.3.5. 双向关联 1.3.6. 使双向连起来 1.4. 第三部分 - Event...
1.2.6. 加载并存储对象 1.3. 第二部分 - 关联映射 1.3.1. 映射Person类 1.3.2. 单向Set-based的关联 1.3.3. 使关联工作 1.3.4. 值类型的集合 1.3.5. 双向关联 1.3.6. 使双向连起来 1.4. 第三部分 - Event...
1.2.6. 加载并存储对象 1.3. 第二部分 - 关联映射 1.3.1. 映射Person类 1.3.2. 单向Set-based的关联 1.3.3. 使关联工作 1.3.4. 值类型的集合 1.3.5. 双向关联 1.3.6. 使双向连起来 1.4. 第三部分 - Event...
1.2.6. 加载并存储对象 1.3. 第二部分 - 关联映射 1.3.1. 映射Person类 1.3.2. 单向Set-based的关联 1.3.3. 使关联工作 1.3.4. 值类型的集合 1.3.5. 双向关联 1.3.6. 使双向连起来 1.4. 第三部分 - Event...