Golang下的ORM框架gorm的介紹和使用

前言:gorm是Golang語言中一款性能極好的ORM庫,對開發人員相對是比較友好的。固然還有另一個xorm庫也是比較出名的,感興趣的也能夠看看這個庫,接下來主要介紹下gorm庫的一些基本使用。mysql

GORM介紹和快速入門

功能概覽git

  • 全功能ORM(無限接近)
  • 關聯(Has One, Has Many, Belongs To, Many To Many, 多態)
  • 鉤子函數Hook(在建立/保存/更新/刪除/查找以前或以後)
  • 預加載
  • 事務
  • 複合主鍵
  • SQL 生成器
  • 數據庫自動遷移
  • 自定義日誌
  • 可擴展性, 可基於 GORM 回調編寫插件
  • 全部功能都被測試覆蓋
  • 開發者友好

安裝github

咱們都知道,在golang中須要使用一些驅動包來對指定數據庫進行操做,好比MySQL須要使用github.com/go-sql-driver/mysql庫,而Sqlite須要使用github.com/mattn/go-sqlite3庫來支持,不過好在gorm框架中對各個驅動包進行了簡單包裝,可讓咱們在寫程序時能夠更方便的管理驅動庫.golang

支持的數據庫以及導入路徑以下:web

  • mysql: github.com/jinzhu/gorm/dialects/mysql
  • postgres: github.com/jinzhu/gorm/dialects/postgres
  • sqlite: github.com/jinzhu/gorm/dialects/sqlite
  • sqlserver: github.com/jinzhu/gorm/dialects/mssql

注意:gorm框架只是簡單封裝了數據庫的驅動包,在安裝時仍須要下載原始的驅動包sql

# 因爲在項目中使用mysql比較多,這裏使用mysql進行數據存儲
$ go get -u github.com/jinzhu/gorm
$ go get -u github.com/go-sql-driver/mysql

複製代碼

快速入門docker

# 使用docker快速建立一個本地可鏈接的mysql實例
$ docker run -itd -e MYSQL_ROOT_PASSWORD='bgbiao.top' --name go-orm-mysql -p 13306:3306 mysql:5.6

# 登錄mysql並建立一個測試庫
$ docker exec -it go-orm-mysql mysql -uroot -pbgbiao.top
....
mysql> create database test_api;
Query OK, 1 row affected (0.00 sec)

mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test_api |
+--------------------+
4 rows in set (0.00 sec)

# 運行一個簡單示例
$ cat gorm-mysql-example.go
package main
import (
"fmt"
"time"

"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)


// 定義一個數據模型(user表)
// 列名是字段名的蛇形小寫(PassWd->pass_word)
type User struct {
Id uint `gorm:"AUTO_INCREMENT"`
Name string `gorm:"size:50"`
Age int `gorm:"size:3"`
Birthday *time.Time
Email string `gorm:"type:varchar(50);unique_index"`
PassWord string `gorm:"type:varchar(25)"`
}

var db *gorm.DB

func main() {
db,err := gorm.Open("mysql","root:bgbiao.top@(127.0.0.1:13306)/test_api?charset=utf8&parseTime=True&loc=Local")
if err != nil {
fmt.Errorf("建立數據庫鏈接失敗:%v",err)

}

defer db.Close()

// 自動遷移數據結構(table schema)
// 注意:在gorm中,默認的表名都是結構體名稱的複數形式,好比User結構體默認建立的表爲users
// db.SingularTable(true) 能夠取消表名的複數形式,使得表名和結構體名稱一致
db.AutoMigrate(&User{})


// 添加惟一索引
db.Model(&User{}).AddUniqueIndex("name_email", "id", "name","email")

// 插入記錄
db.Create(&User{Name:"bgbiao",Age:18,Email:"bgbiao@bgbiao.top"})
db.Create(&User{Name:"xxb",Age:18,Email:"xxb@bgbiao.top"})

var user User
var users []User
// 查看插入後的所有元素
fmt.Printf("插入後元素:\n")
db.Find(&users)
fmt.Println(users)

// 查詢一條記錄
db.First(&user,"name = ?","bgbiao")
fmt.Println("查看查詢記錄:",user)

// 更新記錄(基於查出來的數據進行更新)
db.Model(&user).Update("name","biaoge")
fmt.Println("更新後的記錄:",user)

// 刪除記錄
db.Delete(&user)

// 查看所有記錄
fmt.Println("查看所有記錄:")

db.Find(&users)
fmt.Println(users)
}

# 運行gorm實例
$ go run gorm-mysql-example.go
插入後元素:
[{1 bgbiao 18 <nil> bgbiao@bgbiao.top } {2 xxb 18 <nil> xxb@bgbiao.top }]
查看查詢記錄: {1 bgbiao 18 <nil> bgbiao@bgbiao.top }
更新後的記錄: {1 biaoge 18 <nil> bgbiao@bgbiao.top }
查看所有記錄:
[{2 xxb 18 <nil> xxb@bgbiao.top }]


複製代碼

GORM經常使用的功能函數

自動遷移數據庫

注意: 使用自動遷移模式將保持表的更新,可是不會更新索引以及現有列的類型或刪除未使用的列c#

// 同時遷移多個模型
db.AutoMigrate(&User{}, &Product{}, &Order{})

// 建立表時增長相關參數
// 好比修改表的字符類型CHARSET=utf8
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
複製代碼

檢查表api

// 檢查模型是否存在
db.HasTable(&User{})

// 檢查表是否存在
db.HasTable("users")

複製代碼

增、刪、改表結構

注意:在實際企業的生產環境中,一般數據庫級別的變動操做,都須要轉換成sql交由DBA兄弟幫忙進行線上庫表變動,所以不論使用自動遷移,仍是手動建立表,都是在開發環境階段的

// 使用模型建立
db.CreateTable(&User{})

// 增長參數建立
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})

// 刪除表
db.DropTable(&User{})
db.DropTable("users")

// 模型和表名的混搭
db.DropTableIfExists(&User{}, "products")


// 修改列(修改字段類型)
db.Model(&User{}).ModifyColumn("description", "text")

// 刪除列
db.Model(&User{}).DropColumn("description")

// 指定表名建立表
db.Table("deleted_users").CreateTable(&User{})

// 指定表名查詢
var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)

複製代碼

索引和約束

// 添加外鍵
// 1st param : 外鍵字段
// 2nd param : 外鍵表(字段)
// 3rd param : ONDELETE
// 4th param : ONUPDATE
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

// 單個索引
db.Model(&User{}).AddIndex("idx_user_name", "name")

// 多字段索引
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")

// 添加惟一索引(一般使用多個字段來惟一標識一條記錄)
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "id","email")


// 刪除索引
db.Model(&User{}).RemoveIndex("idx_user_name")


複製代碼

GORM模型注意事項

gorm.Model結構體

其實在gorm官方文檔的示例中,會默認在模型的屬性中增長一個gorm.Model的屬性,該屬性的原始結構以下:

// 官方示例模型
type User struct {
gorm.Model
Name string
.....
}

// gorm.Model結構體
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
複製代碼

很明顯,當咱們在用戶自定義的模型中增長gorm.Model時,會自動爲咱們的表增長id,created_at,updated_at,deleted_at四個字段。

同時須要注意的是,當咱們的模型中有CreatedAt,UpdatedAt,DeletedAt屬性而且類型爲time.Time或者*time.Time類型時,當有數據操做時,會自動更新對應的時間。

因此,在定義模型時,能夠根據實際的需求來決定是否引入gorm.Model結構

另外須要注意的是: 全部字段的零值, 好比0, '', false 或者其它零值都不會保存到數據庫內,但會使用他們的默認值,所以一些非必須字段,可使用DEFAULT的tag字段來聲明列的默認值。

gorm模型支持的tags

注意:gorm支持一些常見的tags來自定義模型字段的擴展信息

結構體標記 描述
column 列明(默認是字段的蛇形小寫)
type 數據類型
size 列長度
PRIMARY_KEY 聲明主鍵
UNIQUE 聲明惟一
DEFAULT 指定默認值
PRECISION 聲明列精度
NOT NULL 將列指定爲非 NULL
AUTO_INCREMENT 聲明自增列
INDEX 建立具備或不帶名稱的索引, 若是多個索引同名則建立複合索引
UNIQUE_INDEX 建立惟一索引
EMBEDDED 將結構設置爲嵌入
EMBEDDED_PREFIX 設置嵌入結構的前綴
- 忽略此字段

注意: gorm也支持一些關聯的結構體標籤,好比外鍵,關聯外鍵,等操做,一般在複雜的企業環境中,建議在庫表設計時將相關表都設計成孤立表,具體的關聯邏輯由業務層去實現(可能增長了開發的成本,不過當業務發展比較複雜時,這樣作無疑是方便後期作擴展和優化的)

詳細的CRUD接口

建立

插入記錄

// 至關於insert into users(name,age,brithday) values("BGBiao",18,time.Now())
user := User{Name: "BGBiao", Age: 18, Birthday: time.Now()}
// 主鍵爲空返回`true`
db.NewRecord(user)
db.Create(&user)
// 建立`user`後返回`false`
db.NewRecord(user)

複製代碼

在Hooks中設置字段值

注意: 一般咱們在設計模型時,有一些原始字段,但願在初始化模型後就擁有記錄,此時可使用hooks來插入初始記錄

若是想再BeforeCreatehook中修改字段的值,可使用scope.SetColumn:

func (user *User) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.New())
return nil
}

複製代碼

建立擴展選項

// 爲Instert語句添加擴展SQL選項
// insert into produce(name,code) values("name","code") on conflict;
db.Set("gorm:insert_option", "ON CONFLICT").Create(&product)

複製代碼

查詢

基本查詢

// 根據主鍵查詢第一條記錄
// SELECT * FROM users ORDER BY id LIMIT 1;
db.First(&user)


// 隨機獲取一條記錄
// SELECT * FROM users LIMIT 1;
db.Take(&user)


// 根據主鍵查詢最後一條記錄
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
db.Last(&user)


// 查詢全部的記錄
// SELECT * FROM users;
db.Find(&users)


// 查詢指定的某條記錄(僅當主鍵爲整型時可用)
// SELECT * FROM users WHERE id = 10;
db.First(&user, 10)


複製代碼

結構體方式查詢

// 結構體方式
// select * from users where name = 'bgbiao.top'
db.Where(&User{Name: "bgbiao.top", Age: 20}).First(&user)

// Map方式
// select * from users where name = 'bgbiao.top' and age = 20;
db.Where(map[string]interface{}{"name": "bgbiao.top", "age": 20}).Find(&users)

// 主鍵的切片
// select * from users where id in (20,21,22);
db.Where([]int64{20, 21, 22}).Find(&users)

複製代碼

Where條件查詢

注意: 使用了Where()方法,基本上就是基本的sql語法

// 使用條件獲取一條記錄 First()方法
db.Where("name = ?", "bgbiao.top").First(&user)

// 獲取所有記錄 Find()方法
db.Where("name = ?", "jinzhu").Find(&users)

// 不等於
db.Where("name <> ?", "jinzhu").Find(&users)

// IN
db.Where("name IN (?)", []string{"jinzhu", "bgbiao.top"}).Find(&users)

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)

// Time
// select * from users where updated_at > '2020-03-06 00:00:00'
db.Where("updated_at > ?", lastWeek).Find(&users)

// BETWEEN
// select * from users where created_at between '2020-03-06 00:00:00' and '2020-03-14 00:00:00'
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

複製代碼

Not條件

// select * from users where name != 'bgbiao.top';
db.Not("name", "jinzhu").First(&user)

// Not In
// select * from users where name not in ("jinzhu","bgbiao.top");
db.Not("name", []string{"jinzhu", "bgbiao.top"}).Find(&users)

// 主鍵不在slice中的查詢
// select * from users where id not in (1,2,3)
db.Not([]int64{1,2,3}).First(&user)

// select * from users;
db.Not([]int64{}).First(&user)

// 原生SQL
// select * from users where not(name = 'bgbiao.top');
db.Not("name = ?", "bgbiao.top").First(&user)

// struct方式查詢
// select * from users where name != 'bgbiao.top'
db.Not(User{Name: "bgbiao.top"}).First(&user)
複製代碼

Or條件

// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)

// Struct 方式
// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'bgbiao.top';
db.Where("name = 'jinzhu'").Or(User{Name: "bgbiao.top"}).Find(&users)

// Map 方式
// SELECT * FROM users WHERE name = 'jinzhu' OR name = 'bgbiao.top';
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "bgbiao.top"}).Find(&users)

複製代碼

FirstOrCreate

獲取匹配的第一條記錄, 不然根據給定的條件建立一個新的記錄(僅支持 struct 和 map 條件)

// 未找到,就插入記錄
if select * from users where name = 'non_existing') is null; insert into users(name) values("non_existing")
db.FirstOrCreate(&user, User{Name: "non_existing"})

// 找到
// select * from users where name = 'bgbiao.top'
db.Where(User{Name: "bgbiao.top"}).FirstOrCreate(&user)

## attrs參數:若是記錄未找到,將使用參數建立 struct 和記錄.
// 未找到(將條件前置,並將拆入數據填充到Attrs方法中)
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)

// 找到
db.Where(User{Name: "bgbiao.top"}).Attrs(User{Age: 30}).FirstOrCreate(&user)

## assgin參數: 無論記錄是否找到,都將參數賦值給 struct 並保存至數據庫
db.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)


複製代碼

子查詢

*gorm.expr 子查詢

// SELECT * FROM "orders"  WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders"  WHERE (state = 'paid')));
db.Where("amount > ?", DB.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").QueryExpr()).Find(&orders)

複製代碼

字段查詢

一般狀況下,咱們只想選擇幾個字段進行查詢,指定你想從數據庫中檢索出的字段,默認會選擇所有字段。

// SELECT name, age FROM users;
db.Select("name, age").Find(&users)

// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)

// SELECT COALESCE(age,'42') FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
複製代碼

排序(Order)

// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc, name").Find(&users)

// 多字段排序
// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc").Order("name").Find(&users)

// 覆蓋排序
//
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
複製代碼

限制輸出

// SELECT * FROM users LIMIT 3;
db.Limit(3).Find(&users)

// -1 取消 Limit 條件
// SELECT * FROM users LIMIT 10;
// SELECT * FROM users;
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
複製代碼

統計count

// SELECT count(*) from USERS WHERE name = 'jinzhu' OR name = 'bgbiao.top';
db.Where("name = ?", "jinzhu").Or("name = ?", "bgbiao.top").Find(&users).Count(&count)

// select count(*) from users where name = 'bgbiao.top'
db.Model(&User{}).Where("name = ?", "bgbiao.top").Count(&count)

// SELECT count(*) FROM deleted_users;
db.Table("deleted_users").Count(&count)

// SELECT count( distinct(name) ) FROM deleted_users;
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
複製代碼

分組(grouo&having)

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
...
}

rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
...
}

type Result struct {
Date time.Time
Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
複製代碼

鏈接查詢

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
...
}

db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// 多鏈接及參數
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
複製代碼

pluck查詢

Pluck,查詢 model 中的一個列做爲切片,若是您想要查詢多個列,您應該使用Scan

var ages []int64
db.Find(&users).Pluck("age", &ages)

var names []string
db.Model(&User{}).Pluck("name", &names)

db.Table("deleted_users").Pluck("name", &names)

複製代碼

scan掃描

type Result struct {
Name string
Age int
}

var result Result
db.Table("users").Select("name, age").Where("name = ?", "Antonio").Scan(&result)

// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)

複製代碼

更新

更新全部字段

注意:save會更新全部字段,及時沒有賦值

db.First(&user)

user.Name = "bgbiao.top"
user.Age = 100
// update users set name = 'bgbiao.top',age=100 where id = user.id
db.Save(&user)


複製代碼

更新修改字段

使用UpdateUpdates方法

// 更新單個屬性,若是它有變化
// update users set name = 'hello' where id = user.id
db.Model(&user).Update("name", "hello")

// 根據給定的條件更新單個屬性
// update users set name = 'hello' where active = true
db.Model(&user).Where("active = ?", true).Update("name", "hello")

// 使用 map 更新多個屬性,只會更新其中有變化的屬性
// update users set name = 'hello',age=18,actived=false where id = user.id
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

// 使用 struct 更新多個屬性,只會更新其中有變化且爲非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})

// 警告:當使用 struct 更新時,GORM只會更新那些非零值的字段
// 對於下面的操做,不會發生任何更新,"", 0, false 都是其類型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})
複製代碼

更新選定字段

若是想要更新或者忽略某些字段,能夠先使用SelectOmit

// update users set name = 'hello' where id = user.id;
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})

// Omit()方法用來忽略字段
// update users set age=18,actived=false where id = user.id
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
複製代碼

無hook更新

上面的更新操做會會自動運行 model 的BeforeUpdate,AfterUpdate方法,來更新一些相似UpdatedAt的字段在更新時保存其 Associations, 若是你不想調用這些方法,你可使用UpdateColumn,UpdateColumns

// 更新單個屬性,相似於 `Update`
// update users set name = 'hello' where id = user.id;
db.Model(&user).UpdateColumn("name", "hello")

// 更新多個屬性,相似於 `Updates`
// update users set name = 'hello',age=18 where id = user.id;
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})

複製代碼

批量更新

注意: 使用struct實例更新時,只會更新非零值字段,弱項更新所有字段,建議使用map

// update users set name = 'hello',age=18 where id in (10,11)
db.Table("users").Where("id IN (?)", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})

// 使用 struct 更新時,只會更新非零值字段,若想更新全部字段,請使用map[string]interface{}
db.Model(User{}).Updates(User{Name: "hello", Age: 18})

// 使用 `RowsAffected` 獲取更新記錄總數
db.Model(User{}).Updates(User{Name: "hello", Age: 18}).RowsAffected
複製代碼

使用SQL計算表達式

// update products set price = price*2+100 where id = product.id
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))

// update products set price = price*2+100 where id = product.id;
DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})

// update products set quantity = quantity-1 where id = product.id;
DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))

// update products set quantity = quantity -1 where id = product.id and quantity > 1
DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
複製代碼

刪除

刪除記錄

注意: 刪除記錄時,請確保主鍵字段有值,GORM 會經過主鍵去刪除記錄,若是主鍵爲空,GORM 會刪除該 model 的全部記錄。

// 刪除現有記錄
// delete from emails where id = email.id;
db.Delete(&email)

// 爲刪除 SQL 添加額外的 SQL 操做
// delete from emails where id = email.id OPTION (OPTIMIZE FOR UNKNOWN)
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)

複製代碼

批量刪除

// delete from emails where email like '%jinzhu%'
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})

// 同上
db.Delete(Email{}, "email LIKE ?", "%jinzhu%")

複製代碼

軟刪除

若是一個model有DeletedAt字段,他將自動得到軟刪除的功能!

當調用Delete方法時, 記錄不會真正的從數據庫中被刪除,只會將DeletedAt字段的值會被設置爲當前時間。

在以前,咱們可能會使用isDelete之類的字段來標記記錄刪除,不過在gorm中內置了DeletedAt字段,而且有相關hook來保證軟刪除。

// UPDATE users SET deleted_at="2020-03-13 10:23" WHERE id = user.id;
db.Delete(&user)

// 批量刪除
// 軟刪除的批量刪除其實就是把deleted_at改爲當前時間,而且在查詢時沒法查到,因此底層用的是update的sql
db.Where("age = ?", 20).Delete(&User{})

// 查詢記錄時會忽略被軟刪除的記錄
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Where("age = 20").Find(&user)

// Unscoped 方法能夠查詢被軟刪除的記錄
// SELECT * FROM users WHERE age = 20;
db.Unscoped().Where("age = 20").Find(&users)

複製代碼

物理刪除

注意: 使用Unscoped().Delete()方法纔是真正執行sql中的delete語句.

// Unscoped 方法能夠物理刪除記錄
// DELETE FROM orders WHERE id=10;
db.Unscoped().Delete(&order)

複製代碼

知識星球
知識星球
公衆號
公衆號

本文使用 mdnice 排版

相關文章
相關標籤/搜索