7天用Go動手寫/從零實現ORM框架GeeORM

極客兔兔發表於2020-10-16

在這裡插入圖片描述

0 目錄

1 談談 ORM 框架

物件關係對映(Object Relational Mapping,簡稱ORM)是通過使用描述物件和資料庫之間對映的後設資料,將面嚮物件語言程式中的物件自動持久化到關聯式資料庫中。

那物件和資料庫是如何對映的呢?

資料庫物件導向的程式語言
表(table)類(class/struct)
記錄(record, row)物件 (object)
欄位(field, column)物件屬性(attribute)

舉一個具體的例子,來理解 ORM。

CREATE TABLE `User` (`Name` text, `Age` integer);
INSERT INTO `User` (`Name`, `Age`) VALUES ("Tom", 18);
SELECT * FROM `User`;

第一條 SQL 語句,在資料庫中建立了表 User,並且定義了 2 個欄位 NameAge;第二條 SQL 語句往表中新增了一條記錄;最後一條語句返回表中的所有記錄。

假如我們使用了 ORM 框架,可以這麼寫:

type User struct {
    Name string
    Age  int
}

orm.CreateTable(&User{})
orm.Save(&User{"Tom", 18})
var users []User
orm.Find(&users)

ORM 框架相當於物件和資料庫中間的一個橋樑,藉助 ORM 可以避免寫繁瑣的 SQL 語言,僅僅通過操作具體的物件,就能夠完成對關係型資料庫的操作。

那如何實現一個 ORM 框架呢?

  • CreateTable 方法需要從引數 &User{} 得到對應的結構體的名稱 User 作為表名,成員變數 Name, Age 作為列名,同時還需要知道成員變數對應的型別。
  • Save 方法則需要知道每個成員變數的值。
  • Find 方法僅從傳入的空切片 &[]User,得到對應的結構體名也就是表名 User,並從資料庫中取到所有的記錄,將其轉換成 User 物件,新增到切片中。

如果這些方法只接受 User 型別的引數,那是很容易實現的。但是 ORM 框架是通用的,也就是說可以將任意合法的物件轉換成資料庫中的表和記錄。例如:

type Account struct {
    Username string
    Password string
}

orm.CreateTable(&Account{})

這就面臨了一個很重要的問題:如何根據任意型別的指標,得到其對應的結構體的資訊。這涉及到了 Go 語言的反射機制(reflect),通過反射,可以獲取到物件對應的結構體名稱,成員變數、方法等資訊,例如:

typ := reflect.Indirect(reflect.ValueOf(&Account{})).Type()
fmt.Println(typ.Name()) // Account

for i := 0; i < typ.NumField(); i++ {
    field := typ.Field(i)
    fmt.Println(field.Name) // Username Password
}
  • reflect.ValueOf() 獲取指標對應的反射值。
  • reflect.Indirect() 獲取指標指向的物件的反射值。
  • (reflect.Type).Name() 返回類名(字串)。
  • (reflect.Type).Field(i) 獲取第 i 個成員變數。

除了物件和表結構/記錄的對映以外,設計 ORM 框架還需要關注什麼問題呢?

1)MySQL,PostgreSQL,SQLite 等資料庫的 SQL 語句是有區別的,ORM 框架如何在開發者不感知的情況下適配多種資料庫?

2)如何物件的欄位發生改變,資料庫表結構能夠自動更新,即是否支援資料庫自動遷移(migrate)?

3)資料庫支援的功能很多,例如事務(transaction),ORM 框架能實現哪些?

4)…

2 關於 GeeORM

資料庫的特性非常多,簡單的增刪查改使用 ORM 替代 SQL 語句是沒有問題的,但是也有很多特性難以用 ORM 替代,比如複雜的多表關聯查詢,ORM 也可能支援,但是基於效能的考慮,開發者自己寫 SQL 語句很可能更高效。

因此,設計實現一個 ORM 框架,就需要給功能特性排優先順序了。

Go 語言中使用比較廣泛 ORM 框架是 gormxorm。除了基礎的功能,比如表的操作,記錄的增刪查改,gorm 還實現了關聯關係(一對一、一對多等),回撥外掛等;xorm 實現了讀寫分離(支援配置多個資料庫),資料同步,匯入匯出等。

gorm 正在徹底重構 v1 版本,短期內看不到釋出 v2 的可能。相比於 gorm-v1,xorm 在設計上更清晰。GeeORM 的設計主要參考了 xorm,一些細節上的實現參考了 gorm。GeeORM 的目的主要是瞭解 ORM 框架設計的原理,具體實現上魯棒性做得不夠,一些複雜的特性,例如 gorm 的關聯關係,xorm 的讀寫分離沒有實現。目前支援的特性有:

  • 表的建立、刪除、遷移。
  • 記錄的增刪查改,查詢條件的鏈式操作。
  • 單一主鍵的設定(primary key)。
  • 鉤子(在建立/更新/刪除/查詢之前或之後)
  • 事務(transaction)。

GeeORM 分7天實現,每天完成的部分都是可以獨立執行和測試的,就像搭積木一樣,一個個獨立的特性組合在一起就是最終的 ORM 框架。每天的程式碼在 100 行左右,同時配有較為完備的單元測試用例。

附 推薦閱讀

原文地址: 7天用Go從零實現ORM框架GeeORM - 極客兔兔
知乎專欄: Go語言 - 極客兔兔
關注微博: @極客兔兔

相關文章