延遲加載
延遲加載是一個用於避免過多數據庫查詢的極爲重要的機制,大部分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方法中執行任意調整,這個過程對於調用者來說是透明的,只要提供可用的數據即可。而且它能夠更加容易地用在測試中。