데이터 소스 아키텍처 모드의 테이블 데이터 입력, 데이터 소스 아키텍처 모드의 행, 데이터 입력 데이터 소스 아키텍처 모드의 활동 기록이 앞서 소개되었습니다. 이 세 가지 데이터 소스 아키텍처 모드에 비해 데이터는 다음과 같습니다. 매퍼는 "크고 고급스럽습니다".
1. 개념
데이터 매퍼: 개체와 데이터베이스(및 매퍼 자체)를 서로 독립적으로 유지하면서 데이터를 이동하는 매핑입니다. 개념은 항상 추상적입니다. 간단히 말해서 데이터 매퍼는 데이터를 객체에 매핑하는 클래스 데이터입니다.
2. 데이터 매퍼를 사용하는 이유는 무엇인가요?
데이터 매퍼는 처음 세 가지 모드보다 구현하기가 더 복잡한데 왜 사용합니까?
객체 간의 조직적 관계는 관계형 데이터베이스의 테이블과 다릅니다. 데이터베이스 테이블은 행과 열로 구성된 그리드로 볼 수 있습니다. 테이블의 행은 외래 키를 통해 다른 테이블(동일한 테이블이라도)의 행과 관련될 수 있으며 개체의 구성 관계는 더 복잡합니다. 객체는 다른 객체를 포함할 수 있습니다. 서로 다른 데이터 구조는 동일한 객체를 서로 다른 방식으로 구성할 수 있습니다.
객체 데이터베이스와 관계형 데이터베이스 간의 이러한 차이를 "객체-관계형 임피던스 불일치" 또는 "임피던스 불일치"라고 합니다.
데이터 매퍼는 객체와 관계형 데이터베이스 간의 데이터 변환을 담당하여 도메인 모델에서 데이터베이스 작업을 효과적으로 숨기고 데이터베이스 변환 시 불가피한 변경 사항을 관리합니다.
3. 데이터 매퍼의 간단한 구현
PHP 코드
<?php //领域抽象类 abstract class DomainObject { private $id = -1; function __construct( $id=null ) { if ( is_null( $id ) ) { $this->markNew(); } else { $this->id = $id; } } function getId( ) { return $this->id; } static function getCollection( $type ) { //这里通过一个工广生成此对象对应的数组数据对象 return HelperFactory::getCollection( $type ); } function collection() { return self::getCollection( get_class( $this ) ); } function finder() { return self::getFinder( get_class( $this ) ); } static function getFinder( $type ) { //这里通过一个工厂生成此对象对应的map对象 return HelperFactory::getFinder( $type ); } function setId( $id ) { $this->id = $id; } function __clone() { $this->id = -1; } } //场所类 class Venue extends DomainObject { private $name; private $spaces; function __construct( $id=null, $name=null ) { $this->name = $name; parent::__construct( $id ); } function setSpaces( SpaceCollection $spaces ) { $this->spaces = $spaces; } function getSpaces() { if ( ! isset( $this->spaces ) ) { //创建对应的SpaceMapper对象 $finder = self::getFinder( 'Space' ); $this->spaces = $finder->findByVenue( $this->getId() ); //$this->spaces = self::getCollection("Space"); } return $this->spaces; } function addSpace( Space $space ) { $this->getSpaces()->add( $space ); $space->setVenue( $this ); } function setName( $name_s ) { $this->name = $name_s; } function getName( ) { return $this->name; } static function findAll() { $finder = self::getFinder( __CLASS__ ); return $finder->findAll(); } static function find( $id ) { $finder = self::getFinder( __CLASS__ ); return $finder->find( $id ); } } abstract class Mapper{ protected static $PDO; function __construct() { if ( ! isset(self::$PDO) ) { //此处可加缓存 self::$PDO = new PDO( $dsn ); self::$PDO->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } } private function getFromMap( $id ) { //从内存取出此$id的DomainObject对象 } private function addToMap( DomainObject $obj ) { //将此DomainObject对象加入到内存 } function find( $id ) { $old = $this->getFromMap( $id ); if ( $old ) { return $old; } $this->selectstmt()->execute( array( $id ) ); $array = $this->selectstmt()->fetch( ); $this->selectstmt()->closeCursor( ); if ( ! is_array( $array ) ) { return null; } if ( ! isset( $array['id'] ) ) { return null; } $object = $this->createObject( $array ); return $object; } function findAll( ) { $this->selectAllStmt()->execute( array() ); return $this->getCollection( $this->selectAllStmt()->fetchAll( PDO::FETCH_ASSOC ) ); } function createObject( $array ) { $old = $this->getFromMap( $array['id']); if ( $old ) { return $old; } $obj = $this->doCreateObject( $array ); $this->addToMap( $obj ); return $obj; } function insert( DomainObject $obj ) { $this->doInsert( $obj ); $this->addToMap( $obj ); } protected abstract function getCollection( array $raw ); protected abstract function doCreateObject( array $array ); protected abstract function doInsert( DomainObject $object ); protected abstract function targetClass(); protected abstract function selectStmt( ); protected abstract function selectAllStmt( ); } class VenueMapper extends Mapper { function __construct() { parent::__construct(); $this->selectAllStmt = self::$PDO->prepare( "SELECT * FROM venue"); $this->selectStmt = self::$PDO->prepare( "SELECT * FROM venue WHERE id=?"); $this->updateStmt = self::$PDO->prepare( "UPDATE venue SET name=?, id=? WHERE id=?"); $this->insertStmt = self::$PDO->prepare( "INSERT into venue ( name ) values( ? )"); } function getCollection( array $raw ) { //这里简单起见用个对象数组 $ret = array(); foreach ($raw as $value) { $ret[] = $this->createObject($value); } return $ret; } protected function doCreateObject( array $array ) { $obj = new Venue( $array['id'] ); $obj->setname( $array['name'] ); //$space_mapper = new SpaceMapper(); //$space_collection = $space_mapper->findByVenue( $array['id'] ); //$obj->setSpaces( $space_collection ); return $obj; } protected function targetClass() { return "Venue"; } protected function doInsert( DomainObject $object ) { $values = array( $object->getname() ); $this->insertStmt->execute( $values ); $id = self::$PDO->lastInsertId(); $object->setId( $id ); } function update( DomainObject $object ) { $values = array( $object->getname(), $object->getid(), $object->getId() ); $this->updateStmt->execute( $values ); } function selectStmt() { return $this->selectStmt; } function selectAllStmt() { return $this->selectAllStmt; } } //client代码 $venue = new venue(); $venue->setName("XXXXXXX"); //插入一条数据 $mapper = new VenueMapper(); $mapper->insert($venue); //获取刚插入的数据 $venueInfo = $mapper->find($venue->getId()); //修改数据 $venue->setName('OOOOOOOOOOO'); $mapper->update($venue); ?>
이 코드는 일부 보조 클래스를 생략하고 기본 도메인 개체와 데이터 매퍼를 유지합니다. 데이터 매퍼 패턴의 가장 강력한 점은 도메인 계층과 데이터베이스 작업 간의 결합을 제거한다는 것입니다. 매퍼 개체는 배후에서 작동하며 다양한 개체 관계형 매핑에 적용될 수 있습니다. 이것이 가져오는 것은 많은 수의 특정 매퍼 클래스를 생성해야 한다는 것입니다. 하지만 이제는 프로그램을 통해 프레임워크가 자동으로 생성될 수 있습니다.
4. 사용 시기
데이터베이스 매퍼는 데이터베이스 스키마와 객체 모델이 서로 독립적으로 발전해야 할 때 주로 사용됩니다. 가장 일반적인 것은 물론 도메인 패턴과 함께 사용됩니다. 설계 단계, 개발 단계 또는 테스트 단계에서 데이터 매퍼는 도메인 모델에서 작업할 때 데이터베이스를 고려할 필요가 없습니다. 이러한 모든 대응은 데이터 매퍼에 의해 수행되므로 도메인 개체는 데이터베이스 구조에 대해 아무것도 모릅니다.
물론 데이터 매퍼에는 새로운 레이어가 도입되므로 이러한 패턴을 사용하기 위한 전제 조건은 비즈니스 로직의 복잡성이며, 단순하다면 필요하지 않습니다.
도메인 모델 없이는 데이터 매퍼를 사용하지 않습니다. 그런데 데이터 매퍼 없이 도메인 모델을 사용할 수 있나요? 도메인 모델이 단순하고 데이터베이스가 도메인 모델 개발자의 통제하에 있는 경우 도메인 개체가 활동 기록을 사용하여 데이터베이스에 직접 액세스하는 것이 합리적입니다.
완전한 데이터베이스 매핑 레이어를 생성할 필요는 없습니다. 이러한 데이터 매퍼를 만드는 것은 복잡합니다. 대부분의 경우 직접 생성하는 것보다 오픈소스 데이터베이스 매핑 레이어를 사용하는 것이 좋습니다