在大型Web項目中ORM有著舉足輕重的作用,非常考驗架構(gòu)師的設(shè)計水平,我見過的失敗項目大部分都是ORM模塊出問題導(dǎo)致的。最近在重構(gòu)一個大型項目,借此機(jī)會和大家聊聊ORM。
ORM(Object Relational Mapping)對象關(guān)系映射,是一種程序技術(shù),用于實現(xiàn)面向?qū)ο缶幊陶Z言里不同類型系統(tǒng)的數(shù)據(jù)之間的轉(zhuǎn)換,簡單點說就是將數(shù)據(jù)庫里面的一條數(shù)據(jù)映射成一個對象,要對某條數(shù)據(jù)增刪改查時直接操作對應(yīng)的對象即可。這樣帶來的好處是不言而喻的,比如要insert一條記錄,原始的做法是這樣:
INSERT INTO `user` (`id`, `account`, `password`)
VALUES (1, 'it2048', '123456');
這樣做會有一些問題:
- 手寫SQL很費時,遇到幾十上百個字段的表,一句insert要耗費半天精力。
- 每次都要看著數(shù)據(jù)庫客戶端,不然屬性名稱沒法寫。
- 容易把字段的類型弄錯,varchar類型的屬性傳入了int。
- 容易寫出SQL注入漏洞。
為了解決這些問題,ORM順勢而生,使用ORM之后的代碼如下:
<?php
$model = new User();
$model->id = 1;
$model->account = 'it2048';
$model->password = '123456';
$model->save();
對比一下會發(fā)現(xiàn),使用ORM之后上面那些問題都迎刃而解,接下來看看他是如何實現(xiàn)的。
比如MySQL里面的User表如下:
| id | account | password |
|---|---|---|
| 1 | it2048 | 123456 |
對應(yīng)的ORM如下:
<?php
class User extends Model{
protected $id;
protected $account;
protected $password;
public function setAccount($account){
$this->account = $account;
}
public function getAccount($account){
return $this->account;
}
}
需要插入一條記錄只需要new一個User類,然后操作User對象給屬性賦值,最后調(diào)用save()方法將User對象轉(zhuǎn)換成insert語存儲到MySQL。大部分操作都可以在父類Model中封裝,比如save()方法,這就是ActiveRecord(ORM的一種思想)的實現(xiàn)方式。
一. ORM的兩種實現(xiàn)哲學(xué)
我們將ORM的思想拆分之后會發(fā)現(xiàn)它就兩個功能。
數(shù)據(jù)操作 - 對數(shù)據(jù)對象做變更,就是我們常說的業(yè)務(wù)邏輯。
數(shù)據(jù)持久化 - 將數(shù)據(jù)落地,比如存儲到MySQL,MongoDB等不同的數(shù)據(jù)庫。
有兩個功能,就有了吵架的理由了。于是大家分成了兩派,一派認(rèn)為應(yīng)該把兩個功能合在一起,簡單方便,易上手,名字都想好了就叫 ActiveRecord。另一派認(rèn)為兩個功能必須分開,擴(kuò)展靈活,逼格高,名字也想好了就叫 Data Mappers。兩派高手吵了很久,Talk is cheap,Show me the code ,咱們github見。
二. ActiveRecord
從面向?qū)ο蟮慕嵌葋碚f,將數(shù)據(jù)操作與數(shù)據(jù)持久化兩個功能放一起違反了單一功能原則?;仡櫼幌率裁词菃我还δ茉瓌t?每個類都應(yīng)該有一個單一的功能,并且該功能應(yīng)該由這個類完全封裝起來。比如業(yè)務(wù)邏輯和存儲邏輯是兩個獨立的模塊,兩者在功能上不依賴,如果把兩個完全獨立的功能封裝在一起會導(dǎo)致代碼耦合,這也是面向?qū)ο蟪绦蛟O(shè)計時要規(guī)避的。
話雖然這么說,但規(guī)定是死的,人是活的,在實際項目中又不一樣了。ActiveRecord在實際項目中風(fēng)馳電掣,發(fā)展迅猛,主流的編程框架基本都選擇它作為ORM。用ActiveRecord ORM的PHP框架有Laravel, Yii, CodeIgniter, CakePHP等。其他語言用的有 Ruby on Rails,Django等。
ActiveRecord上手非常快,業(yè)務(wù)邏輯和持久化邏輯在一個對象里一起解決,封裝越好的框架持久化邏輯對編程人員越透明,程序員甚至不用知道底層數(shù)據(jù)庫使用的是MySQL還是MongoDB。
看一個調(diào)用實例:
<?php
$model = new User();
$model->id = 1;
$model->account = 'it2048';
$model->password = '123456';
$model->save();
對 $model 屬性的修改屬于業(yè)務(wù)邏輯,調(diào)用save()方法屬于持久化邏輯。使用者完全不用關(guān)心save()方法執(zhí)行后數(shù)據(jù)是存儲到MySQL還是MongoDB,在開發(fā)過程中可以將精力全部放到業(yè)務(wù)邏輯,開發(fā)速度非???。
三. Data Mappers
從面向?qū)ο蟮慕嵌葋碚f,將數(shù)據(jù)操作與數(shù)據(jù)持久化兩個功能分開符合單一功能原則。這樣設(shè)計出來的代碼低耦合,擴(kuò)展性強(qiáng),性能有保證。對于理論派開發(fā)者來說Data Mappers肯定是首選。
但是在實際項目中Data Mappers的發(fā)展并不好,主要是出活慢。簡單點說就是一個對象可以解決的事情,現(xiàn)在不得不用兩個對象來解決,其中還有一個是全局對象(持久化邏輯)。對于代碼的封裝來說,全局對象的初始化和傳遞是大問題。初始化需要依賴框架,傳遞需要顯示傳遞。這就導(dǎo)致我們封裝的package不通用,只能在特定框架下傳遞特定對象才能使用。另一個問題是擴(kuò)展性強(qiáng)就要求有大量的參數(shù)配置,開發(fā)者需要在代碼層面關(guān)心具體用哪個數(shù)據(jù)庫,怎樣使用SQL語句性能好等,對開發(fā)者要求較高。
Data Mappers帶來的好處主要體現(xiàn)在后期,比如需要優(yōu)化性能,我們可以將一次請求中的所有SQL批量執(zhí)行,這些SQL統(tǒng)一放在全局持久化對象中,很方便就能實現(xiàn)批量處理操作。這在ActiveRecord中很難做到。拿到持久化對象之后對數(shù)據(jù)的干預(yù)也會非常方便,例如MySQL表中的字段類型從枚舉變成了int,在ActiveRecord中你需要查找所有代碼,將該字段修正。而Data Mappers只需要在持久化對象中做個替換。
看一個調(diào)用實例:
<?php
$model = new User();
$model->setId(1);
$model->setAccount('it2048');
$model->setPassword('123456');
$entityManager = EntityManager::create($connection, $config);
$entityManager->persist($model);
//flush通常在請求結(jié)束后執(zhí)行
$entityManager->flush();
$model 對象屬性的修改屬于業(yè)務(wù)邏輯,$entityManager對象涵括持久化邏輯。通常$entityManager對象是全局的,達(dá)到統(tǒng)一管理數(shù)據(jù)的目的。flush()與save()方法類似,但flush()是對$entityManager中所有數(shù)據(jù)的存儲,一般在請求結(jié)束時調(diào)用。
使用Data Mappers的框架數(shù)量相比ActiveRecord要少很多,主要有Java Hibernate,PHP Doctrine,SQLAlchemy in Python,EntityFramework for Microsoft .NET。
Data Mappers極大的增強(qiáng)了項目在ORM模塊的擴(kuò)展性,對在ORM模塊踩過坑的開發(fā)者來說是一劑良藥,但是良藥苦口。
四. 如何選擇ORM
上面把ActiveRecord和Data Mappers都介紹清楚了,選擇哪一個需要根據(jù)實際業(yè)務(wù)需求來。如果是我的話,我會更多的考慮當(dāng)前公司的發(fā)展情況,如果公司處于發(fā)展期,業(yè)務(wù)需求多,那肯定選擇ActiveRecord,保證高產(chǎn)出最重要。如果公司處于技術(shù)沉淀期,比如開始還技術(shù)債,那就選擇Data Mappers,一是可以沉淀很多技術(shù),二是能將項目的性能與擴(kuò)展性提升。
一般項目初期會選擇ActiveRecord,如果項目比較成功,有一天發(fā)現(xiàn)ActiveRecord優(yōu)化起來很吃力,要改造它的時候想到有Data Mappers,然后從ActiveRecord過度到Data Mappers,完成項目優(yōu)化。這也是程序員正常的成長路徑。架構(gòu)被程序員開發(fā),同樣也會幫助程序員成長。
五. 參考文檔
https://www.thoughtfulcode.com/orm-active-record-vs-data-mapper/
六. 最后
最近遇到很多"洗稿"的人,而且這些人的粉絲還非常多,更惡心的是這些人洗稿之后還反過來舉報原創(chuàng)抄襲。對于原創(chuàng)來說打擊太大了,我寫文章不為掙錢,覺得不錯的點個喜歡就是對作者最大的安慰。