# 高級模型
[上一頁](# "上一頁")[下一頁](# "下一頁")
高級模型提供了更多的查詢功能和模型增強功能,利用了模型類的擴展機制實現。如果需要使用高級模型的下面這些功能,記得需要繼承Think\Model\AdvModel類或者采用動態模型。
~~~
namespace Home\Model;
use Think\Model\AdvModel;
class UserModel extends AdvModel{
}
~~~
我們下面的示例都假設UserModel類繼承自Think\Model\AdvModel類。
### 字段過濾
基礎模型類內置有數據自動完成功能,可以對字段進行過濾,但是必須通過Create方法調用才能生效。高級模型類的字段過濾功能卻可以不受create方法的調用限制,可以在模型里面定義各個字段的過濾機制,包括寫入過濾和讀取過濾。
字段過濾的設置方式只需要在Model類里面添加 `$_filter`屬性,并且加入過濾因子,格式如下:
~~~
protected $_filter = array(
'過濾的字段'=>array('寫入過濾規則','讀取過濾規則',是否傳入整個數據對象),
)
~~~
過濾的規則是一個函數,如果設置傳入整個數據對象,那么函數的參數就是整個數據對象,默認是傳入數據對象中該字段的值。
舉例說明,例如我們需要在發表文章的時候對文章內容進行安全過濾,并且希望在讀取的時候進行截取前面255個字符,那么可以設置:
~~~
protected $_filter = array(
'content'=>array('contentWriteFilter','contentReadFilter'),
)
~~~
其中,contentWriteFilter是自定義的對字符串進行安全過濾的函數,而contentReadFilter是自定義的一個對內容進行截取的函數。通常我們可以在項目的公共函數文件里面定義這些函數。
### 序列化字段
序列化字段是新版推出的新功能,可以用簡單的數據表字段完成復雜的表單數據存儲,尤其是動態的表單數據字段。要使用序列化字段的功能,只需要在模型中定義serializeField屬性,定義格式如下:
~~~
protected $serializeField = array(
'info' => array('name', 'email', 'address'),
);
~~~
Info是數據表中的實際存在的字段,保存到其中的值是name、email和address三個表單字段的序列化結果。序列化字段功能可以在數據寫入的時候進行自動序列化,并且在讀出數據表的時候自動反序列化,這一切都無需手動進行。
下面還是是User數據表為例,假設其中并不存在name、email和address字段,但是設計了一個文本類型的info字段,那么下面的代碼是可行的:
~~~
$User = D("User"); // 實例化User對象
// 然后直接給數據對象賦值
$User->name = 'ThinkPHP';
$User->email = 'ThinkPHP@gmail.com';
$User->address = '上海徐匯區';
// 把數據對象添加到數據庫 name email和address會自動序列化后保存到info字段
$User->add();
查詢用戶數據信息
$User->find(8);
// 查詢結果會自動把info字段的值反序列化后生成name、email和address屬性
// 輸出序列化字段
echo $User->name;
echo $User->email;
echo $User->address;
~~~
### 文本字段
ThinkPHP支持數據模型中的個別字段采用文本方式存儲,這些字段就稱為文本字段,通常可以用于某些Text或者Blob字段,或者是經常更新的數據表字段。
要使用文本字段非常簡單,只要在模型里面定義blobFields屬性就行了。例如,我們需要對Blog模型的content字段使用文本字段,那么就可以使用下面的定義:
~~~
Protected $blobFields = array('content');
~~~
系統在查詢和寫入數據庫的時候會自動檢測文本字段,并且支持多個字段的定義。
> 需要注意的是:對于定義的文本字段并不需要數據庫有對應的字段,完全是另外的。而且,暫時不支持對文本字段的搜索功能。
### 只讀字段
只讀字段用來保護某些特殊的字段值不被更改,這個字段的值一旦寫入,就無法更改。要使用只讀字段的功能,我們只需要在模型中定義readonlyField屬性
~~~
protected $readonlyField = array('name', 'email');
~~~
例如,上面定義了當前模型的name和email字段為只讀字段,不允許被更改。也就是說當執行save方法之前會自動過濾到只讀字段的值,避免更新到數據庫。
下面舉個例子說明下:
~~~
$User = D("User"); // 實例化User對象
$User->find(8);
// 更改某些字段的值
$User->name = 'TOPThink';
$User->email = 'Topthink@gmail.com';
$User->address = '上海靜安區';
// 保存更改后的用戶數據
$User->save();
~~~
事實上,由于我們對name和email字段設置了只讀,因此只有address字段的值被更新了,而name和email的值仍然還是更新之前的值。
### 悲觀鎖和樂觀鎖
業務邏輯的實現過程中,往往需要保證數據訪問的排他性。如在金融系統的日終結算處理中,我們希望針對某個時間點的數據進行處理,而不希望在結算進行過程中(可能是幾秒種,也可能是幾個小時),數據再發生變化。此時,我們就需要通過一些機制來保證這些數據在某個操作過程中不會被外界修改,這樣的機制,在這里,也就是所謂的 “ 鎖 ” ,即給我們選定的目標數據上鎖,使其無法被其他程序修改。 ThinkPHP支持兩種鎖機制:即通常所說的 “ 悲觀鎖( Pessimistic Locking ) ”和 “ 樂觀鎖( Optimistic Locking ) ” 。
### 悲觀鎖( Pessimistic Locking )
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。 通常是使用for update子句來實現悲觀鎖機制。
ThinkPHP支持悲觀鎖機制,默認情況下,是關閉悲觀鎖功能的,要在查詢和更新的時候啟用悲觀鎖功能,可以通過使用之前提到的查詢鎖定方法,例如:
~~~
$User->lock(true)->save($data);// 使用悲觀鎖功能
~~~
### 樂觀鎖( Optimistic Locking )
相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制。悲觀鎖大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。 如一個金融系統,當某個操作員讀取用戶的數據,并在讀出的用戶數據的基礎上進行修改時(如更改用戶帳戶余額),如果采用悲觀鎖機制,也就意味著整個操作過程中(從操作員讀出數據、開始修改直至提交修改結果的全過程,甚至還包括操作員中途去煮咖啡的時間),數據庫記錄始終處于加鎖狀態,可以想見,如果面對幾百上千個并發,這樣的情況將導致怎樣的后果。樂觀鎖機制在一定程度上解決了這個問題。樂觀鎖,大多是基于數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。
ThinkPHP也可以支持樂觀鎖機制,要啟用樂觀鎖,只需要繼承高級模型類并定義模型的optimLock屬性,并且在數據表字段里面增加相應的字段就可以自動啟用樂觀鎖機制了。默認的optimLock屬性是lock_version,也就是說如果要在User表里面啟用樂觀鎖機制,只需要在User表里面增加lock_version字段,如果有已經存在的其它字段作為樂觀鎖用途,可以修改模型類的optimLock屬性即可。如果存在optimLock屬性對應的字段,但是需要臨時關閉樂觀鎖機制,把optimLock屬性設置為false就可以了。
### 延遲更新
我們經常需要給某些數據表添加一些需要經常更新的統計字段,例如用戶的積分、文件的下載次數等等,而當這些數據更新的頻率比較頻繁的時候,數據庫的壓力也隨之增大不少,我們可以利用高級模型的延遲更新功能緩解。
延遲更新功能是指我們可以給統計字段的更新設置一個延遲時間,在這個時間段內所有的更新會被累積緩存起來,然后定時地統一更新數據庫。這比較適合某個字段經常需要遞增或者遞減,并且對實時性要求沒有那么嚴格的情況。我們先來看遞增的情況,如果我們需要給會員累積積分,可以使用
~~~
$User = D("User"); // 實例化User對象
$User->where('id=3')->setInc("score",10);// 用戶的積分加10
$User->where('id=3')->setInc("score",30);// 用戶的積分加30
~~~
上面的操作更新了兩次用戶積分,并且都實時保存到數據庫如果我們使用延遲更新方法,例如下面對用戶的積分延遲更新60秒
~~~
$User->where('id=3')->setLazyInc("score",10,60);
$User->where('id=3')->setLazyInc("score",30,60);
$User->where('id=3')->setLazyInc("score",10,60);
~~~
那么60秒內執行的所有積分更新操作都會被延遲,實際會在60秒后統一更新積分到數據庫,而不是每次都更新數據庫。臨時積分會被累積并緩存起來,最后到了延遲更新時間,再統一更新。相當于在60秒后執行了:
~~~
$User->where('id=3')->setInc("score",50);
~~~
效果是等效。區別在于用戶數據庫中的積分不是實時的。同樣,還可以使用setLazyDec進行延遲更新操作。
### 數據分表
對于大數據量的應用,經常會對數據進行分表,有些情況是可以利用數據庫的分區功能,但并不是所有的數據庫或者版本都支持,因此我們可以利用ThinkPHP內置的數據分表功能來實現。幫助我們更方便的進行數據的分表和讀取操作。
和數據庫分區功能不同,內置的數據分表功能需要根據分表規則手動創建相應的數據表。
在需要分表的模型中定義partition屬性即可。
~~~
protected $partition = array(
'field' => 'name',// 要分表的字段 通常數據會根據某個字段的值按照規則進行分表
'type' => 'md5',// 分表的規則 包括id year mod md5 函數 和首字母
'expr' => 'name',// 分表輔助表達式 可選 配合不同的分表規則
'num' => 'name',// 分表的數目 可選 實際分表的數量
);
~~~
定義好了分表屬性后,我們就可以來進行CURD操作了,唯一不同的是,獲取當前的數據表不再使用getTableName方法,而是使用getPartitionTableName方法,而且必須傳入當前的數據。然后根據數據分析應該實際操作哪個數據表。因此,分表的字段值必須存在于傳入的數據中,否則會進行聯合查詢。
### 返回類型
系統默認的數據庫查詢返回的是數組,我們可以給單個數據設置返回類型,以滿足特殊情況的需要,例如:
~~~
$User = M("User"); // 實例化User對象
// 返回結果是一個數組數據
$data = $User->find(6);
// 返回結果是一個stdClass對象
$data = $User->returnResult($data, "object");
// 還可以返回自定義的類
$data = $User->returnResult($data, "User");
~~~
返回自定義的User類,類的架構方法的參數是傳入的數據。例如:
~~~
Class User {
public function __construct($data){
// 對$data數據進行處理
}
}
~~~
[上一頁](# "上一頁")[下一頁](# "下一頁")
- 序言
- 基礎
- 獲取ThinkPHP
- 環境要求
- 目錄結構
- 入口文件
- 自動生成
- 模塊
- 控制器
- 開發規范
- 配置
- 配置格式
- 配置加載
- 讀取配置
- 動態配置
- 擴展配置
- 批量配置
- 架構
- 模塊化設計
- URL模式
- 多層MVC
- CBD模式
- 命名空間
- 自動加載
- 應用模式
- 項目編譯
- 系統流程
- 路由
- 路由定義
- 規則路由
- 正則路由
- 靜態路由
- 閉包支持
- 實例說明
- 控制器
- 控制器定義
- 前置和后置操作
- Action參數綁定
- 偽靜態
- URL大小寫
- URL生成
- AJAX返回
- 跳轉和重定向
- 輸入變量
- 請求類型
- 空操作
- 空控制器
- 操作綁定到類
- 模型
- 模型定義
- 模型實例化
- 字段定義
- 連接數據庫
- 切換數據庫
- 分布式數據庫支持
- 連貫操作
- WHERE
- TABLE
- ALIAS
- DATA
- FIELD
- ORDER
- LIMIT
- PAGE
- GROUP
- HAVING
- JOIN
- UNION
- DISTINCT
- LOCK
- CACHE
- COMMENT
- RELATION
- 命名范圍
- CURD操作
- 數據創建
- 數據寫入
- 數據讀取
- 數據更新
- 數據刪除
- ActiveRecord
- 字段映射
- 查詢語言
- 查詢方式
- 表達式查詢
- 快捷查詢
- 區間查詢
- 組合查詢
- 統計查詢
- SQL查詢
- 動態查詢
- 子查詢
- 自動驗證
- 自動完成
- 參數綁定
- 虛擬模型
- 模型分層
- 視圖模型
- 關聯模型
- 高級模型
- Mongo模型
- 視圖
- 模板定義
- 模板主題
- 模板賦值
- 模板渲染
- 獲取模板地址
- 獲取內容
- 模板引擎
- 模板
- 變量輸出
- 系統變量
- 使用函數
- 默認值輸出
- 使用運算符
- 標簽庫
- 模板繼承
- 修改定界符
- 三元運算
- 包含文件
- 內置標簽
- Volist標簽
- Foreach標簽
- For標簽
- Switch標簽
- 比較標簽
- 范圍判斷標簽
- IF標簽
- Present標簽
- Empty標簽
- Defined標簽
- Assign標簽
- Define標簽
- 標簽嵌套
- import標簽
- 使用PHP代碼
- 原樣輸出
- 模板注釋
- 模板布局
- 模板替換
- 調試
- 調試模式
- 異常處理
- 日志記錄
- 頁面Trace
- Trace方法
- 變量調試
- 性能調試
- 錯誤調試
- 模型調試
- 緩存
- 數據緩存
- 快速緩存
- 查詢緩存
- SQL解析緩存
- 靜態緩存
- 安全
- 輸入過濾
- 表單合法性檢測
- 表單令牌
- 防止SQL注入
- 目錄安全文件
- 保護模板文件
- 上傳安全
- 防止XSS攻擊
- 其他安全建議
- 擴展
- 類庫擴展
- 驅動擴展
- 緩存驅動
- 數據庫驅動
- 日志驅動
- Session驅動
- 存儲驅動
- 模板引擎驅動
- 標簽庫驅動
- 行為擴展
- 標簽擴展
- Widget擴展
- 應用模式
- 部署
- PATH_INFO支持
- URL重寫
- 模塊部署
- 域名部署
- 入口綁定
- 替換入口
- 專題
- SESSION支持
- Cookie支持
- 多語言支持
- 數據分頁
- 文件上傳
- 驗證碼
- 圖像處理
- RESTFul
- RPC
- SAE
- IP獲取和定位
- 附錄
- 常量參考
- 配置參考
- 升級指導
- 更新日志
- 鳴謝
- 關于