數據庫之記錄集AR

雖然Yii的DAO可以完成所有的數據庫操作,我們還是要花90%的時間來寫一些CRUD的操作。如果這些操作跟SQL語句混合使用,將會變得非常難以維護。爲了解決這些困難,我們可以使用記錄集Active Record。

AR是一個流行的對象關係集的技術。每個AR類代表一個數據庫的表(視圖),這些表的字段就對應AR的屬性,一個AR的實例,就代表數據表中的記錄。常用的CRUD操作,一般都採用AR來實現,所以,我們可以用一種更加面向對象的方法來獲取數據。例如,我們可以用以下的代碼來實現一個記錄的插入:

  1. $post=new Post;  
  2. $post->title='sample post';  
  3. $post->content='post body content';  
  4. $post->save(); 

接下來我們將介紹如何創建AR,並用他來執行CRUD操作。

下一節中,我們會爲大家展示如何用AR處理關係數據庫。現在先簡單的說,我們用以下的表作爲我們的演示使用。

  1. CREATE TABLE tbl post (  
  2.     id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,  
  3.     title VARCHAR(128) NOT NULL,  
  4.     content TEXT NOT NULL,  
  5.     create time INTEGER NOT NULL  
  6. ); 

注意:AR並不能用來完全解決數據庫相關的操作,他最好是用來作爲數據庫表的模型類,用來做CRUD的簡單操作,而不死執行復雜的SQL語句。如果是需要複雜的SQL,那麼用DAO來操作。

1.1 建立數據庫連接

AR是基於數據庫連接來進行數據庫相關的操作的。默認情況下,我們假設db是這個應用的數據庫連接組件。下面展示一個配置文件作爲例子

  1. return array(  
  2.     'components'=>array(  
  3.         'db'=>array(  
  4.             'class'=>'system.db.CDbConnection',  
  5.             'connectionString'=>'sqlite:path/to/dbfile',  
  6.             // turn on schema caching to improve performance  
  7.             // 'schemaCachingDuration'=>3600,  
  8.         ),  
  9.     ),  
  10. ); 

上例中,如果是數據庫結構不經常改變,則啓用'schemaCachingDuration' 會使得數據庫更高效。

支持AR的數據庫系統有:(注意,不是所有數據庫都支持啊)

●  MySQL 4.1 or later
●  PostgreSQL 7.3 or later
●  SQLite 2 and 3
●  Microsoft SQL Server 2000 or later
●  Oracle

如果你不想用db作爲數據庫句柄,或者是要連接多個數據庫,那麼需要重寫CActiveRecord::getDbConnection()。CActiveRecord類是所有AR類的基類。

Tips:有兩個方法讓AR操作多個數據庫。如果這些數據庫的架構是不一樣的(我的理解是不同的數據庫系統),那麼就必須去建立一個AR的基類,實現不同的getDbConnection()方法;而如果都是相同的數據庫系統,動態的去改變靜態變量CActiveRecord::db就是最好的方法了。

1.2 定義AR類

要訪問一個數據庫的表,我們首先要定義一個類,這個類繼承於CActiveRecort。每個AR類代表一個數據庫的表,一個AR對象,則代表數據表中的一條記錄。以下代碼最簡潔的展示瞭如何讓一個AR代表tbl_post表:

  1. class Post extends CActiveRecord  
  2. {  
  3.     public static function model($className= CLASS )  
  4.     {  
  5.         return parent::model($className);  
  6.     }  
  7.     public function tableName()  
  8.     {  
  9.         return 'tbl_post';  
  10.     }  

提示:因爲AR類經常會被很多地方用到,所以,我們可以一次引入整個目錄。例如,我們所有的AR都是放在protected/models,我們在配置工程的時候就可以:

  1. return array(  
  2.     'import'=>array(  
  3.         'application.models.*',  
  4.     ),  
  5. ); 

默認情況下,AR類的名稱就是數據庫中的表明。如果不一樣的話,就重載tableName()。

TIPS:如果使用了表前綴,那麼上例中的tableName()就應該按照下列的實現:

  1. public function tableName()  
  2. {  
  3.     return 'ffpostgg';  

數據表中的字段值,可以通過AR的實例屬性來訪問,例如:

  1. $post=new Post;  
  2. $post->title='a sample post'

雖然我們從來沒有顯示的在post中聲明title這個屬性,我們還是可以使用。這是因爲title是表tbl_post的一個字段,CActiveRecord通過PHP的_get()這個神奇的方法,讓他變成了AR的屬性。如果我們嘗試訪問一個不存在的字段時,會拋出異常。

AR是基於一個表的好的主鍵設計。如果表中不存在主鍵,那麼就要求我們重載primaryKey()這個辦法,來指定一個主鍵。

  1. public function primaryKey()  
  2. {  
  3.     return 'id';  
  4.     // For composite primary key, return an array like the following  
  5.     // return array('pk1', 'pk2');  

1.3 創建記錄

要想在中插入一行記錄,我們就要新建一個AR的對象。設置了相對應字段的屬性值之後,調用save()的方法,就插入到數據庫中了。

  1. $post=new Post;  
  2. $post->title='sample post';  
  3. $post->content='content for the sample post';  
  4. $post->create time=time();  
  5. $post->save(); 

如果主鍵是自動增長的,那麼在保存之後,會自動加上該主鍵,並且數值自動增加。上例中,id的屬性就會是新的記錄集的ID,雖然我們改變他。

如果在表中的某些字段,已經有默認值了,那麼在插入時,AR的這些屬性就會自動賦予默認值。如果想要更改這些默認值,就需要顯示的在代碼中設置AR的屬性。

在記錄被保存到數據庫之前,屬性可以被設置爲CDbExpression類型的值。例如,爲了保存Mysql返回的now()值,我們可以用以下的代碼實現:

  1. $post=new Post;  
  2. $post->create_time=new CDbExpression('NOW()');  
  3. // $post->create_time='NOW()'; will not work because  
  4. // 'NOW()' will be treated as a string  
  5. $post->save(); 

TIPS:AR雖然讓我們可以不用寫整片的SQL語句,但是有時候還是想看看數據庫到底執行了什麼語句。我們可以在Yii的logging feature中開啓。例如,我們在項目工程配置中,開啓CWebLogRoute,我們就可以在網頁端看到所執行的SQL。我們也可以設置CDbConnection::enableParamLogging,這樣還可以看到具體的參數值。

1.4 讀取記錄

要讀取數據表的數據,我們調用以下的其中一種:

  1. // find the first row satisfying the specified condition  
  2. $post=Post::model()->find($condition,$params);  
  3. // find the row with the specified primary key  
  4. $post=Post::model()->findByPk($postID,$condition,$params);  
  5. // find the row with the specified attribute values  
  6. $post=Post::model()->findByAttributes($attributes,$condition,$params);  
  7. // find the first row using the specified SQL statement  
  8. $post=Post::model()->findBySql($sql,$params); 

上例中,我們調用Post::model()的find方法,注意,對每個AR類來說,一個靜態的model方法是必須的。這個靜態方法,返回一個AR實例,用來訪問類級別的方法。

如果find方法找到了匹配條件的記錄,就返回一個Post的實例。然後我們就可以像訪問我們普通類的屬性那樣訪問數據庫的值了,例如$post->title。

如果沒有找到匹配的記錄,find會返回一個空值。

調用find方法的時候,我們用$condition 和$params作爲查詢的條件。例如:

  1. // find the row with postID=10  
  2. $post=Post::model()->find('postID=:postID'array(':postID'=>10)); 

我們也可以用$condition 作複雜的查詢。當然了,複雜的就不是一句話了,我們用CDbCriteria的實例作爲$condition ,然後我們就可以設定更多的條件了:

  1. $criteria=new CDbCriteria;  
  2. $criteria->select='title'// only select the 'title' column  
  3. $criteria->condition='postID=:postID';  
  4. $criteria->params=array(':postID'=>10);  
  5. $post=Post::model()->find($criteria); // $params is not needed 

我們用CDbCriteria作爲查詢條件以後,$params就不需要了。

還有一個替換CDbCriteria的方法是像find提供一個數組參數,這個數組參數中包含查詢條件以及對應的值。放例子,不解釋

  1. $post=Post::model()->find(array(  
  2.     'select'=>'title',  
  3.     'condition'=>'postID=:postID',  
  4.     'params'=>array(':postID'=>10),  
  5. )); 

TIPS:如果查詢條件只是某字段的值等於特定的值,可以使用 findByAttributes()

如果符合查詢條件的記錄很多,我們可以通過調用findAll這些方法一次性全部讀取。

  1. // find all rows satisfying the specified condition  
  2. $posts=Post::model()->findAll($condition,$params);  
  3. // find all rows with the specified primary keys  
  4. $posts=Post::model()->findAllByPk($postIDs,$condition,$params);  
  5. // find all rows with the specified attribute values  
  6. $posts=Post::model()->findAllByAttributes($attributes,$condition,$params);  
  7. // find all rows using the specified SQL statement  
  8. $posts=Post::model()->findAllBySql($sql,$params); 

如果沒有符合查詢條件的記錄,那麼以上方法就會放回一個空的數組。這跟find不同的是,find會返回NULL。

除了之前提到的find以及findAll這些方法,以下的方法也是很好用的:

  1. // get the number of rows satisfying the specified condition  
  2. $n=Post::model()->count($condition,$params);  
  3. // get the number of rows using the specified SQL statement  
  4. $n=Post::model()->countBySql($sql,$params);  
  5. // check if there is at least a row satisfying the specified condition  
  6. $exists=Post::model()->exists($condition,$params); 

1.5 更新記錄

當AR實例的屬性被字段值賦值後,我們可以更改這些屬性,可以將這些更新過的屬性值保存到數據庫中。

  1. $post=Post::model()->findByPk(10);  
  2. $post->title='new post title';  
  3. $post->save(); // save the change to database 

正如我們看到的,不論是更新還是插入新數據,我們都是調用save這個方法。如果AR的對象是new出來的,調用save就是執行插入操作;如果對象是從find或者是findAll得到的,調用save的時候就是更新操作。事實上,我們可以調用CActiveRecord::isNewRecord來判斷AR對象是新的與否。

我們也可以在沒有裝載數據的情況下,更新一條或者是多條記錄。AR提供了下列便捷的類方法:

  1. // update the rows matching the specified condition  
  2. Post::model()->updateAll($attributes,$condition,$params);  
  3. // update the rows matching the specified condition and primary key(s)  
  4. Post::model()->updateByPk($pk,$attributes,$condition,$params);  
  5. // update counter columns in the rows satisfying the specified conditions  
  6. Post::model()->updateCounters($counters,$condition,$params); 

$attributes 是一組字段名;
condition 是更新的條件;
$params  參數值;

1.6 刪除數據

我們可以用下面的方法來刪除數據庫記錄

  1. $post=Post::model()->findByPk(10); // assuming there is a post whose ID is 10  
  2. $post->delete(); // delete the row from the database table 

注意!!!刪除之後,AR的屬性什麼的都沒變,但是數據庫記錄沒啦!!!

下面的這些方法可以不需要加載數據而直接刪除:

  1. // delete the rows matching the specified condition  
  2. Post::model()->deleteAll($condition,$params);  
  3. // delete the rows matching the specified condition and primary key(s)  
  4. Post::model()->deleteByPk($pk,$condition,$params); 

1.7 數據驗證

在更新或者是插入數據的時候,經常需要根據一些規則來驗證字段值師傅符合規範。當這些數據是由終端用戶提供的時候,這種校驗尤爲重要。正常情況下,我們是完全不信任任何用戶輸入信息的。

AR在用戶提交save之後,會自動校驗數據。校驗規則是AR裏的rules()方法設定的。至於具體的規則設定,參考之前就行了。以下是一個典型的工作流:

  1. if($post->save())  
  2. {  
  3.     // data is valid and is successfully inserted/updated  
  4. }  
  5. else 
  6. {  
  7.     // data is invalid. call getErrors() to retrieve error messages  

當更新或者是插入的值,是從終端用戶輸入的,那麼我們要給AR的屬性賦值:

  1. $post->title=$ POST['title'];  
  2. $post->content=$ POST['content'];  
  3. $post->save(); 

如果是多個字段一起更新,那麼可以採用之前提到的集體賦值:

  1. // assume $ POST['Post'] is an array of column values indexed by column names  
  2. $post->attributes=$ POST['Post'];  
  3. $post->save(); 

1.8 數據對比

就像表記錄一樣,AR的對象也跟主鍵一樣的,有唯一ID。所以,如果比較兩個AR對象,如果他們屬於同一個AR類,我們僅僅比較他們的主鍵。一個簡單的方法是調用CActiveRecord::equals()。 但是:

TIPS:跟其他框架不同,Yii支持聯合主鍵。聯合組建在Yii裏面是一個數組的形式。

1.9 自定義

AR類提供了一些虛函數,用來給子類重載,自定義工作流:

● beforeValidate and afterValidate: 在驗證之前(之後)調用
● beforeSave and afterSave:
● beforeDelete and afterDelete:
● afterConstruct:
● beforeFind:
● afterFind:

1.10 AR中使用事務

每個AR對象都包含了一個叫做dbConnection的屬性,也就是CDbConnection的句柄。所以在我們用AR的時候,可以使用DAO的事務功能。

  1. $model=Post::model();  
  2. $transaction=$model->dbConnection->beginTransaction();  
  3. try  
  4. {  
  5.     // find and save are two steps which may be intervened by another request  
  6.     // we therefore use a transaction to ensure consistency and integrity  
  7.     $post=$model->findByPk(10);  
  8.     $post->title='new post title';  
  9.     $post->save();  
  10.     $transaction->commit();  
  11. }  
  12. catch(Exception $e)  
  13. {  
  14.     $transaction->rollBack();  

1.11 命名空間

一個命名空間意味着一個命名的查詢條件(query criteria),可以用來連接其他的命名空間,運用於AR查詢。

命名空間主要是在CActiveRecord::scopes()裏聲明,格式爲name-criteria。下面的代碼定義了兩個命名空間:published 和 recently。

  1. class Post extends CActiveRecord  
  2. {  
  3.     ......  
  4.     public function scopes()  
  5.     {  
  6.         return array(  
  7.             'published'=>array(  
  8.                 'condition'=>'status=1',  
  9.             ),  
  10.             'recently'=>array(  
  11.                 'order'=>'create_time DESC',  
  12.                 'limit'=>5,  
  13.             ),  
  14.         );  
  15.     }  

每個命名空間可以用來作爲CDbCriteria的實例。比如說,recently這個命名空間,指定了order的屬性是create_time DESC,limit的屬性值是5;轉換成查詢條件就是返回最近的5條記錄。

命名空間一般是用於作爲find方法的變體。許多命名空間可能會被關聯起來,得到一個更多篩選條件的結果。例如,查找最近發佈的文章,我們可以用以下的方法:

  1. $posts=Post::model()->published()->recently()->findAll(); 

一般來說了,命名空間的右邊都有一個find的方法,每個命名空間都提供一個查詢規則。作用就好比在查詢語句中添加一串過濾器。

參數化命名空間

命名空間也可以參數化。例如,我們想定製recently這個命名空間裏的文章數量。我們不在CActiveRecord::scopes裏修改方法,換一種做法,我們要定義一個與這個命名空間同名的方法

  1. public function recently($limit=5)  
  2. {  
  3.     $this->getDbCriteria()->mergeWith(array(  
  4.         'order'=>'create time DESC',  
  5.         'limit'=>$limit,  
  6.     ));  
  7.     return $this;  

然後我們就可以通過以下的方法來獲取最近的3篇文章(而不是5篇)

  1. $posts=Post::model()->published()->recently(3)->findAll(); 

如果我們不指定參數是3,那麼就回去返回默認的5篇。

默認空間

一個模型類可以有一個默認的空間,可以用來做任何查詢。例如,一個多語言的網頁,只想顯示用戶當前語言的那部分內容。由於可能會有很多關於這個網頁內容的查詢,我們通過定義一個默認空間來解決該問題。我們重載CActiveRecord::defaultScope這個方法,

  1. class Content extends CActiveRecord  
  2. {  
  3.     public function defaultScope()  
  4.     {  
  5.         return array(  
  6.             'condition'=>"language='".Yii::app()->language."'",  
  7.         );  
  8.     }  

所以,當下面的方法被調用的時候,就會自動調用上面的默認空間:

  1. $contents=Content::model()->findAll(); 

注意!默認空間以及命名空間只支持SELECT的查詢動作,不支持INSERT, UPDATE and DELETE。同樣的,當聲明一個空間(默認或者命名的),AR類不能用於當前的數據庫操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章