SQL 語法好難背不起來?關聯式資料庫苦無對策?或許你該試試 ORM 函式庫!
你寫程式不用框架嗎?建議可以先用框架完成功能再回去練習原理細節喔!調整學習順序可以進展更順利。
對於關聯式資料庫苦手的你來說,
在後端的世界裡,雖然 NoSQL 形式的資料庫有一定的擁護者與確實適合的情框架構,但是就目前而言,「關聯式資料庫」幾乎還是所有網站的基石。所以對於一個後端的開發者來說,完全不了解 SQL 語法基本上就是職涯唯一死刑一般的問題。說是這麼說,其實現在的後端開發者很多情況都不真的需要對 SQL 語法有透徹的了解也可以開始開發,而且開發效率可能還更好。這是因為大多數的後端框架現在都已經包含了所謂的 ORM 類型的函式庫來幫助大家開發。
線上程式教學課程: 從零開始的SQL語法與資料庫設計 - 以MySQL來攻略
延伸閱讀:
「不是工程師」後端服務的根基,淺談SQL關連式資料庫 RDBMS
ORM 全名為物件關聯對映(Object Relational Mapping,簡稱ORM),簡單來說是一種程式設計技術與概念。
在我們解釋這項技術之前,請大家先想一下,純使用 SQL 所撈取的資料對程式設計上面有什麼問題呢?
一般來說,無論你用哪種後端架構,如果要撈出所有的 Post 文章。那你可能會這樣做。
因為 SQL 本質上就是資料塊,所以結果會得到一個 Array,裡面會包含所有的文章。資料會是 Array 裡面包著數個array。例如像下面這樣的內容。
["文章1", 文章1的內容", false],
["文章2", 文章2的內容", false],
.....如果有更多的話
]
其column 依序是 "title" 標題 , "content" 內容, "memberOnly" 會員專屬
這樣的內容使用上其實也不是真的那麼難用,一樣可以跑迴圈產生UI的列表。不過有時候你可以能會想要做一些程式判斷,比方說有一些文章他可能是會員才可以看的。
那你可能會寫出這樣的程式:
let title = post[0]
let content = post[1]
let memberOnly = post[2]
if (memberOnly) {
會員獨享...
} else {
禁止畫面...
}
}
當然,你也可能不宣告 title, content, memberOnly, 直接使用 post[0] 之類的寫法,但是基本上寫多了會很難維護。
想避開 0, 1, 2 等 index 魔術數字的話的話,可以使用不要使用 select * 而改成寫出每個column,並且預設一個 Key-value object 類似下面結構:
title: 0,
content: 1,
memberOnly: 2
}
這樣 index 可以改成 columns["title"] 來表示
如果程式語言包含 enum 則會更方便一些。
還有一種方式是從資料庫要 table 的 columns
sql.exec (" select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='tableName' ")
這樣會給你該 table 所有的 columns 名稱,一樣是包成一個 array,再自己去對應 index。
不過這樣的寫法其實不算很好,像這樣簡單的範例都看得出來每行宣告都佔了一些空間,再加上 if-else 判斷式,複雜一點的話這個 for 迴圈會變成非常大。你可能會想說 JavaScript 有語法糖,可以節省掉一些空間和提高可讀性,但是因為這邊不是專指 NodeJS 的情況,所以我就用每種語言大概都看得懂的寫法。
像這樣子的 let title = post[0] 某種程度上就是一種 mapping,一般像這樣子的內容如果沒辦法避免的話,通常我們會想把它藏起來,這樣至少商業邏輯的程式看起來就會比較乾淨。所以大概會變成這樣:
class Post {
constructor(rawData) {
this.columns = {
title: 0,
content: 1,
memberOnly: 2
}
this.memberOnly = rawData[ columns["memberOnly"] ]
.... 其他變數
}
.... 簡化版,畢竟其他物件導向語言有封裝,不用像JavaScript 那麼麻煩
}
for(let rawData in rawDataAry){
let post = new Post(rawData)
if ( post.memberOnly ) {
會員獨享...
} else {
禁止畫面...
}
}
像這樣的寫法其實很常見,也可能是沒在使用 ORM 函式庫的話第一件要做的事。不過如果你有使用 ORM 函式庫的話,通常第一步就是靠 cmd 幫你建立好這樣的 class。
以 Laravel 來說,你只要確定資料庫有對應的table,剩下的就是執行
這樣即可,連寫 $this->title = rawData[0]; 這樣的語法都不用。因為 ORM 會從資料庫抓他有哪些 column,或是在 Migration 的時候先建立一個靜態的schema 內容列表,只要是比較動態的程式語言,其實property 都是可以動態產生的,ORM 會自動幫你完成。寫得好的 ORM 系統幾乎可以幫你把所有常見的 SQL 語法利用應用程式語法組合出來。
所以上面範例完整來寫的話可能會是這樣:
1. rawDataAry = sql.exec(" select * from posts;")
2. class Post {
....
}
3. for(let rawData in rawDataAry){
let post = new Post(rawData)
if ( post.memberOnly ) {
會員獨享...
} else {
禁止畫面...
}
}
----------
有使用 ORM (用 PHP 語法示範,很誠實的告訴大家這是商業考量,畢竟我們有 Laravel 課程 )
1. $posts = Post::all(); // 幫你自動組出 select * from posts 並且執行。
2. foreach($posts as $post){
if ( $post->memberOnly ) {
會員獨享...
} else {
禁止畫面...
}
}
或許你可能會想說好像沒差太多,但是那是因為第一個版本有自製 Model 層。
如果都是直接用 key-value object 硬做的話可能就沒那麼好看與好維護了。所以在這層意義上,用 ORM 系統就有好處,至少每個開發者在專案內都被強迫要建立 Model 層的 Class。專案內部的一致性絕對是好維護的重要依據之一。
(即使是 前端 或 APP 接收 API,其實我們的課程也都會教大家建立一個簡單的 Model 層做容器,而非直接 kay-value 取值硬寫,有興趣可以參加我們的 React 或 iOS 課程暸解細節。)
除了上面這層因素外, ORM 系統他在語法組成上通常比新手要來得有經驗的多(都是高手加上長時間修正)。新手寫 SQL 很容易碰到的問題就是 SQL injection。這個問題主要是因為 SQL 語法本身就是個字串。所以如果開發者使用字串組成的方式且沒有驗證參數,那就有可能被別人塞有問題的內容,最後組出有問題的字串導致資安問題。像這樣的問題都已經發生已久,也有固定的方式來解決。如果你不使用 ORM 的話你就要自己留意,但是如果你使用有一直在維護的 ORM 函式庫的話,他們多半會有一定程式的防護。尤其是對團隊新手有所幫助,至少比較不容易搞砸。
$column1 = $_POST["column1"];
sql->exec(" insert into table_name (column1) values ($column1) ");
SQL injection 是一個大議題, 所以這邊不多說,不過最簡單的方式就是先檢查傳進來的值的內容,不要預設相信任何傳入的值他是合法的。
另外還有一個更顯而易見的原因就是類似物件導向方式的使用。
SQL join table 在網站撈取資料時幾乎是無法避免的方式。但是一般來說,Join table 所撈取到的資料為類似 Flatten 過的資料,也就是說你幾乎不會拿到像 JSON Object 這樣巢狀的資料,而是表單型的資料。
這樣的資料其實也不是真的有問題,只是人的直覺上很難去區分說每個 column 是從哪一個欄位取得的。比方說如果你拿到的是像下面這樣的內容:
id | name | price | color | size | brand |
1 | 上衣 | 100 | 藍色 | L | 夜市牌 |
2 | 褲子 | 2000 | 黑色 | M | 某名牌 |
實際上正規化後,通常會是至少包含以下幾個 table 表
product
color
size
brand
對於熟悉 SQL的人當然沒問題,但是對於從應用面或是物件導向來思考的話,其實 JSON 形式的想法會比較直覺。像是下面這種表示法:
{
id: 1,
name: "上衣",
price: 100,
color : { name: "藍色" },
size : { name: L },
brand : { name: "夜市牌"},
},
{
id: 2,
name: 褲子",
price: 2000,
color : { name: "黑色" },
size : { name: M },
brand : { name: "某名牌"},
},
]
所以取值的時候,就會是比較像這樣
product.color.name // 藍色
而不是原本的
product["color"] // 藍色
*以上是為了比較乾淨的表示,但實際上可能為
product [ columns["name"] ] 或是 product[0] 這種型式
如果對 product.color.name 這樣的方式不滿意,可以在 Model Color 的底下補上 color_name() 或是 read only 的 property color_name。
這樣你就可以寫成
看起來物件導向很多,進階的使用上也可以把繼承、封裝等概念引進 Model 的 class。這些都不是單個 SQL 指令可以輕鬆達成的。
此外,很多時候其實並不需要一次 join 到位,越複雜的 SQL 指令大多耗時越久,如果因此鎖住部分資料表而導致後面的指令也跟者排隊,整體效能會變差。對於新手來說使用 ORM 也可以直接避免掉這個問題,大部分的情況都會是比較簡單的 SQL 撈取資料再用應用層去組合,而非讓資料庫去組合。
(不過很多情況 ORM 效能低落也是這原因造成的,所以還是要看實際狀況下去調整語法。)
當然很多高手會想說他們自己寫的 class 也可以達成這些目的,根本不需要用厚重的 ORM 函式庫。但這邊就比較講到維護方面的概念,一般來說這些自訂的函式庫比較輕量,有可能讓應用程式整體效能比較好。但是一般積年累月補強過後的函式庫,其實很多後來加上去的都是安全機制,雖然必要性要各別函式庫判斷,但是對於新手而言護具全穿上總比裸奔來得好。其次就是文檔的完整性與真實世界測試過的次數也有差異。一般自訂的函式庫多半不會像 open source 熱門的函式庫那麼多人使用,所以比較難各種情況都驗證過。新人不熟悉 API 的使用情境也沒文檔說明,出問題的機會相對很大。
很多時候沒必要為了很刁專的效能需求而去整個重建。還不如從既有架構上再去做細微調整,各大 ORM 函式庫也都是有留後路給需要進階使用的開發者直接執行 SQL 的管道。有需要再這樣做即可。
不過最後還是提醒一下大家,各大 ORM 函式庫都有作者自己的偏好,有一些作者的想法不見得符合你的思維,反而讓自己在寫程式的時候異常挫折。例如完全學術導向或是效率至上的 ORM 函式庫可能會禁止你做一些 "反正規化" 的動作,這點好不好就見仁見智了。強迫自己學習與使用學術上正確的寫法,或是雖然有基本規範但還是可以讓你一定程度上的自由奔放的 ORM, 哪種適合你就還是要使用過才知道。
最後,如果你喜歡我們的文章,別忘了到我們的FB粉絲團按讚喔!!