延迟加载
延迟加载是一个用于避免过多数据库查询的极为重要的机制,大部分web应用都需要注意对大量数据的操作和查询,所以延迟加载还是很有必要学习的。
在之前我们的建立的数据表和例子可以知道,每个Classroom对象可能包含多个Student对象,而每个Student对象又有可能和更多的Score类进行关联。那么,当从数据库中取出一个Classroom对象的时候,Classroom对象同时请求数据库取出关联的Student对象,同样Student也会关联到更多的Score对象。如果我们只需要展示出所有classroom数据表中的数据,那么取出的Student对象和Score对象根本就是用不到的数据,而且这些操作代价很高。
那么看看StudentMapper中生成一个Student对象的时候的代码:
protected function createObject(array $data) {
$student = new Student($data['id']);
$student->setName($data['name']);
// setClassroom()
$cMapper = PersistanceFactory::getFactory('demo\domain\Classroom')->getMapper();
$classroom = $cMapper->findById($data['cid']);
$student->setClassroom($classroom);
// setScores
$scoreMapper = PersistanceFactory::getFactory('demo\domain\Score')->getMapper();
$scores = $scoreMapper->findByStudentId($data['id']);
$student->setScores($scores);
return $student;
}
一个Student对象的生成就查询了相关联的Score数据(findByStudentId()方法),如果相关联的数据量很多的话,效率也就低了。
SocreMapper中的findByStudentId方法代码:
public function findByStudentId($sid) {
self::$selectByStudentIdStmt->execute(array($sid));
$raws = self::$selectByStudentIdStmt->fetchAll(\PDO::FETCH_ASSOC);
return $this->getCollection($raws);
}
可以看到findByStudentId()立即对数据库进行查询了,而不管需不需要用到这些数据。
findByStudentId()返回的是Collection,那么我们可以对Collection进行操作,让这个Collection只有在我们访问它里面的数据时,Collection自己去执行数据库查询并保存在Colletion自己的属性中。如果我们不去访问Collection中的数据,那么Colletion里面就是空的(没有数据)。
DeferredScoreCollection代码:
class DeferredScoreCollection extends ScoreCollection {
private $stmt;
private $valueArray;
private $run = false;
public function __construct(Mapper $mapper, \PDOStatement $stmtHandle, array $valueArray ) {
parent::__construct(null, $mapper);
$this->stmt = $stmtHandle;
$this->valueArray = $valueArray;
}
/**
* 重写Colletion的notifyAccess()方法
*/
protected function notifyAccess() {
if (!$this->run) {
$this->stmt->execute($this->valueArray);
$this->raws = $this->stmt->fetchAll();
$this->total = count($this->raws);
}
$this->run = true;
}
}
DeferredScoreCollection继承自Colletion。在上面的代码中可以看到,
只有客户端代码需要查询到Colletion中的代码时才去查询数据库(notifyAccess())。
修改成延迟加载后的SocreMapper中的findByStudentId方法:
public function findByStudentId($sid) {
return new DeferredScoreCollection($this, self::$selectByStudentIdStmt, array($sid));
}
修改前后的findByStudentId方法返回的Colletion不一样而已,后者实现了延迟加载,只有当用到的时候才去数据库中获取数据。
领域对象工厂
Mapper的功能很灵活和强大,但又造成了包含了太多的功能,这也一定程度地加大了它的复杂性。那么我们可以分解Mapper,把它里面的功能拆分出来由其它类来实现。我们可以使用细粒度的模式重新提取出Mapper中的部分功能,然后再由它们构成一个完整的Mapper。
首先我们可以提出Mapper中的createObject方法,让领域对象工厂(DomainObject Factory)来实现。
下面为类图:
领域对象只有一个核心功能:创建领域对象。
DomainObjectFactory的代码:
namespace demo\mapper;
require_once 'demo/domain/DomainObject.php';
require_once 'demo/domain/Classroom.php';
require_once 'demo/domain/Student.php';
require_once 'demo/domain/Score.php';
require_once 'demo/domain/ObjectWatcher.php';
require_once 'demo/mapper/PersistanceFactory.php';
use demo\domain\DomainObject;
use demo\domain\Classroom;
use demo\domain\Student;
use demo\domain\Score;
use demo\domain\ObjectWatcher;
/**
*
* @author happen
*/
abstract class DomainObjectFactory {
public abstract function createObject(array $data);
protected function getFromMap($class, $id) {
return ObjectWatcher::exists($class, $id);
}
protected function addToMap(DomainObject $obj) {
return ObjectWatcher::add($obj );
}
}
class ClassroomObjectFactory extends DomainObjectFactory {
public function createObject(array $data) {
$old = $this->getFromMap('demo\domain\Classroom', $array['id']);
if ( $old ) {
return $old;
}
$classroom = new Classroom($data['id']);
$classroom->setName($data['name']);
// 关联的student集合
$stuMapper = PersistanceFactory::getFactory('demo\domain\Student')->getMapper();
$classroom->setStudents($stuMapper->findByClassroomId($data['id']));
// 加入到ObjectWatcher
$this->addToMap($classroom);
$classroom->markClean();
return $classroom;
}
}
class StudentObjectFactory extends DomainObjectFactory {
public function createObject(array $data) {
$old = $this->getFromMap('demo\domain\Student', $array['id']);
if ( $old ) {
return $old;
}
$student = new Student($data['id']);
$student->setName($data['name']);
// setClassroom()
$cMapper = PersistanceFactory::getFactory('demo\domain\Classroom')->getMapper();
$classroom = $cMapper->findById($data['cid']);
$student->setClassroom($classroom);
// setScores
$scoreMapper = PersistanceFactory::getFactory('demo\domain\Score')->getMapper();
$scores = $scoreMapper->findByStudentId($data['id']);
$student->setScores($scores);
// 加入到ObjectWatcher
$this->addToMap($student);
$student->markClean();
return $student;
}
}
class ScoreObjectFactory extends DomainObjectFactory {
public function createObject(array $data) {
$old = $this->getFromMap('demo\domain\Score', $array['id']);
if ( $old ) {
return $old;
}
$score = new Score($data['id']);
$score->setScore($data['score']);
$score->setCourseName($data['course_name']);
// setStudent
$stuMapper = PersistanceFactory::getFactory('demo\domain\Student')->getMapper();
$stuObj = $stuMapper->findById($data['sid']);
$score->setStudent($stuObj);
// 加入到ObjectWatcher
$this->addToMap($score);
$score->markClean();
return $score;
}
}
DomainObjectFactory可以代替Mapper中的createObject和子类的doCreateObject方法,其它需要生成领域对象的地方都可以使用它来进行。领域对象工厂消除了数据库原始数据与对象字段数据之间的耦合。可以在createObject方法中执行任意调整,这个过程对于调用者来说是透明的,只要提供可用的数据即可。而且它能够更加容易地用在测试中。