Gorm使用彙總
- 資料
- 安裝
- 資料庫配置
- 資料庫連接配接
-
- sqlite3
- mysql
- 模型定義
-
- tag:gorm
- 表名
- 基礎操作
-
- 新增
-
- NewRecord主鍵檢查 & Create
- 查詢
-
- First:查詢第一條記錄
- Last:查詢最後一條記錄
- First(... , pk):根據主鍵查詢記錄
- Where(...) 條件查詢條件
- FirstOrInit() & Attrs() & Assign()
- FirstOrCreate() & Attrs() & Assign()
- Select查詢字段定義
- Scan
- Scopes 動态條件添加
- 修改
-
- Save
- Update & Updates & Omit
- 删除
- 回調使用(Callback)
-
- Callback預設定義
- Callback事務保證
- 異常處理
- 事務
- 日志
- 參考
資料
比較好的Gorm中文文檔 https://jasperxu.com/gorm-zh/,本文基于該資料進行整理,彙總最基本的Gorm入門使用内容,陸續補充。
安裝
go get -u github.com/jinzhu/gorm
資料庫配置
//資料庫配置資訊
func options() {
// 全局禁用表名複數
// 如果設定為true,`User`的預設表名為`user`,使用`TableName`設定的表名不受影響
DB.SingularTable(true)
//自動遷移模式将保持更新到最新。
//警告:自動遷移僅僅會建立表,缺少列和索引,并且不會改變現有列的類型或删除未使用的列以保護資料。
DB.AutoMigrate(&User{})
//列印日志,預設是false
DB.LogMode(true)
//連接配接池
DB.DB().SetMaxIdleConns(10)
DB.DB().SetMaxOpenConns(100)
}
資料庫連接配接
sqlite3
使用
sqlite
資料庫來快速連接配接
package main
import (
"encoding/json"
"fmt"
"github.com/jinzhu/gorm"
//這裡是gorm封裝的資料庫驅動
_ "github.com/jinzhu/gorm/dialects/sqlite"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func initSqlite3Db() {
// 初始化
var err error
DB, err = gorm.Open("sqlite3", "test.db")
// 檢查錯誤
if err != nil {
panic(err)
}
}
mysql
func initMysqlDb() {
// 初始化
var err error
DB, err = gorm.Open("mysql", "root:[email protected](127.0.0.1:3306)/testdb?charset=utf8")
// 檢查錯誤
if err != nil {
panic(err)
}
}
模型定義
tag:gorm
通過
tag
标記
gorm
來建立在資料庫中字段的限制和屬性配置
type User struct {
Id int64 `gorm:"primary_key;AUTO_INCREMENT"` //設定為primary_key主鍵,AUTO_INCREMENT自增
UserId string `gorm:"index:idx_user_id;not null` //設定普通索引,索引名稱為idx_user_id,not null不能為空
UserName string `gorm:"type:varchar(64);not null"` //設定為varchar類型,長度為64,not null不能為空
Age int
Phone int `gorm:"unique_index:uk_phone"` //設定唯一索引,索引名為uk_phone
CreateTime time.Time `gorm:"not null"`
UpdateTime time.Time `gorm:"not null"`
UserRemark string `gorm:"column:remark;default:'預設'"`
IgnoreMe int `gorm:"-"` // 忽略這個字段
}
gorm定義 | 資料庫字段限制 |
---|---|
| 字段設定為主鍵 |
| 字段設定為自增 |
| 字段長度設定為20 |
| 字段設定普通索引,名稱為idx_user_id |
| 設定字段為非空 |
| 設定字段為varchar類型,長度為64 |
| 設定資料庫字段名為remark |
| 忽略此字段,不在表中建立該字段 |
| 設定字段的預設值 |
注意:不一定是最全的示例,陸續補充
表名
在
gorm
中,表名建立是以複數存在的,當建立表之前設定以下選項可以禁用表名複數
// 全局禁用表名複數
// 如果設定為true,`User`的預設表名為`user`,使用`TableName`設定的表名不受影響
DB.SingularTable(true)
或者在每個
model
結構體中聲明
TableName
方法,若存在該方法
gorm
會優先采用這種方式來配置表名
type User struct {} // 預設表名是`users`
// 設定User的表名為`user`
func (User) TableName() string {
return "user"
}
基礎操作
新增
NewRecord主鍵檢查 & Create
//Create
func CreateUser() {
user := &User{
UserId: "USER_001",
UserName: "zhangsan",
Age: 10,
Phone: 19901020305,
CreateTime: time.Now(),
UpdateTime: time.Now(),
UserRemark: "備注",
}
//1、NewRecord會檢查主鍵是否存在
boolBefore := DB.NewRecord(user)
//主鍵存在傳回true;否則傳回false 這裡傳回true,因為主鍵ID是自動生成這裡ID是空的不存在
fmt.Println("boolBefore => ", boolBefore)
//2、Create
err := DB.Create(user).Error
if err != nil {
fmt.Printf("CreateUser error %v \n", err)
}
//這裡會傳回主鍵ID
fmt.Println("user => ", toJson(user))
//3、NewRecord會檢查主鍵是否存在
boolAfter := DB.NewRecord(user)
//主鍵存在傳回true;否則傳回false 這裡傳回false,因為主鍵ID已經存在了
fmt.Println("boolAfter => ", boolAfter)
//這裡有完整資料傳回,包含主鍵ID
fmt.Println("user => ", toJson(user))
}
列印日志如下:
boolBefore => true
user => {"Id":1,"UserId":"USER_001","UserName":"zhangsan","Age":10,"Phone":19901020301,"CreateTime":"2021-06-01T13:33:02.237956+08:00","UpdateTime":"2021-06-01T13:33:02.237956+08:00","UserRemark":"預設","IgnoreMe":0}
boolAfter => false
user => {"Id":1,"UserId":"USER_001","UserName":"zhangsan","Age":10,"Phone":19901020301,"CreateTime":"2021-06-01T13:33:02.237956+08:00","UpdateTime":"2021-06-01T13:33:02.237956+08:00","UserRemark":"預設","IgnoreMe":0}
表資料如下:
sqlite> select * from user;
1|USER_001|zhangsan|10|19901020301|2021-06-01 13:33:02.237956+08:00|2021-06-01 13:33:02.237956+08:00|預設
查詢
First:查詢第一條記錄
func FindFirstRecord() {
result := &User{}
// SELECT * FROM user ORDER BY id LIMIT 1;
err:=DB.First(result).Error
if err != nil {
fmt.Printf("FindFirstRecord error %v",err)
return
}
fmt.Printf(toJson(result))
}
日志輸出如下:
Last:查詢最後一條記錄
func FindLastRecord() {
result := &User{}
// SELECT * FROM users ORDER BY id DESC LIMIT 1
err:=DB.Last(result).Error
if err != nil {
fmt.Printf("FindLastRecord error %v",err)
return
}
fmt.Printf(toJson(result))
}
日志輸出如下:
First(… , pk):根據主鍵查詢記錄
func FindRecordByPK() {
result := &User{}
// SELECT * FROM users WHERE id = 2
err:=DB.First(&User{},2).Find(result).Error
if err != nil {
fmt.Printf("FindRecordByPK error %v",err)
return
}
fmt.Printf(toJson(result))
}
日志輸出如下:
Where(…) 條件查詢條件
關于條件查詢相對來說比較簡單,這裡不贅述可以參考 https://jasperxu.com/gorm-zh/crud.html#q
FirstOrInit() & Attrs() & Assign()
擷取第一個比對的記錄,或者使用給定的條件初始化一個新的記錄傳回。
func FirstOrInit() {
user := &User{
UserId: "USER_001",
UserName: "User",
Age: 10,
Phone: 19901020305,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
// 查詢 SELECT * FROM users WHERE user_name="guanjian" 該記錄存在
err := DB.Attrs(&User{UserName: "attrs"}).
// 這裡無論是否找到都強制指派
Assign(&User{UserRemark: "強制修改"}).
// 沒有這條記錄則填補,初始化的填充邏輯是 Attrs > Where (沒有Attrs則使用Where條件)
// 如果有則傳回查到的第一條比對的記錄
FirstOrInit(user, User{UserName: "guanjian"}).Error
if err != nil {
fmt.Printf("FirstOrInit error %v", err)
return
}
fmt.Printf(toJson(user))
}
方法如果查不到結果不會落庫,隻是會初始化
FirstOrInit
中的
FirstOrInit(obj)
對象的值,且将Where條件中的值一并初始化出來
obj
FirstOrCreate() & Attrs() & Assign()
和
FirstOrInit
類似,不同的是它會進行落庫操作。擷取第一個比對的記錄,或者使用給定的條件進行落庫建立。
func FirstOrCreate() {
user := &User{
UserId: "USER_001",
UserName: "guanjian",
Age: 10,
Phone: 19901020310,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
// 查詢 SELECT * FROM users WHERE id = 10
// 沒有這條記錄則按照user來初始化user進行填補
// 如果有則傳回查到的第一條比對的記錄
err := DB.FirstOrCreate(user, User{Id: 10}).Error
if err != nil {
fmt.Printf("FirstOrInit error %v", err)
return
}
fmt.Printf(toJson(user))
}
方法如果查不到結果會落庫,也會初始化
FirstOrCreate
中的
FirstOrCreate(obj)
對象的值,且将Where條件中的值一并初始化出來
obj
Select查詢字段定義
這裡隻查詢
user_name,age
字段傳回,其餘字段不會查詢到值傳回
func Select() {
user := &User{}
err := DB.Select("user_name,age").Find(user).Error
if err != nil {
fmt.Printf("Select error %v", err)
return
}
fmt.Println("隻查詢user_name,age字段", toJson(user))
}
Scan
func Scan() {
user := &User{}
err := DB.Table("user").Select([]string{"user_name","age"}).Scan(user).Error
if err != nil {
fmt.Printf("Scan error %v", err)
return
}
fmt.Println("user => ", toJson(user))
}
Scopes 動态條件添加
實作方法形如
func(db *gorm.DB) * gorm.DB
則可以配合方法
Scopes
進行動态條件的添加,編碼更清晰友好
//這裡比對create_time查詢條件
func WithCreateTimeLessThanNow(db *gorm.DB) *gorm.DB {
return db.Where("create_time < ?", time.Now())
}
//這裡比對age查詢條件
func WithAgeGreaterThan0(db *gorm.DB) *gorm.DB {
return db.Where("age > ?", 0)
}
func Scopes() {
user := &User{}
//查詢條件動态拼接
err := DB.Scopes(WithAgeGreaterThan0,WithCreateTimeLessThanNow).Find(user).Error
if err != nil {
fmt.Printf("Scopes error %v", err)
return
}
fmt.Println("user => ", toJson(user))
}
修改
Save
儲存已存在的資料,觸發更新邏輯(update);儲存不存在的資料,直接插入(insert)
//儲存存在的資料,觸發更新邏輯
func SaveExist() {
user := &User{
Id: 1,
}
//查詢id=9999的資料,是不存在的
user.UserName = "saveNewUserName"
//按照主鍵做Update操作
err := DB.Save(user).Error
if err != nil {
fmt.Printf("Save error %v", err)
return
}
fmt.Println("save user => ", toJson(user))
}
//儲存不存在的資料,直接插入
func SaveNoExist() {
user := &User{
Id: 9999,
Phone: 19902029492,
}
//查詢id=9999的資料,是不存在的
user.UserName = "saveNewUserName"
//按照主鍵做Update操作
err := DB.Save(user).Error
if err != nil {
fmt.Printf("Save error %v", err)
return
}
fmt.Println("save user => ", toJson(user))
}
Update & Updates & Omit
func Update() {
user := &User{
Id: 9999,
}
err := DB.Model(user).Update("user_name", "this is 9999").Error
if err != nil {
fmt.Printf("Update error %v", err)
return
}
}
更新字段效果如下:
使用
Updates
方法可以批量更新多個字段
func Updates() {
//查詢字段
whereUser := &User{
Id: 9999,
}
//批量更新字段
updateUser := &User{
UserName: "This is Updates",
Age: 9292,
UserRemark: "Updates",
}
err := DB.Model(whereUser).Updates(updateUser).Error
if err != nil {
fmt.Printf("Updates error %v", err)
return
}
}
批量更新字段效果如下:
使用
Omit
方法可以忽略該字段的更新
func UpdateOmit() {
//查詢字段
whereUser := &User{
Id: 1,
}
//批量更新字段
updateUser := &User{
UserName: "This is Updates",
Age: 6666,
UserRemark: "1 -> Update",
}
err := DB.Model(whereUser).
//忽略user_name字段的更新
Omit("user_name").
Updates(updateUser).Error
if err != nil {
fmt.Printf("Updates error %v", err)
return
}
}
以上更新操作将執行模型的
BeforeUpdate, AfterUpdate
方法,更新其UpdatedAt時間戳,在更新時儲存它的Associations,如果不想調用它們,可以使用UpdateColumn, UpdateColumns
非零值更新問題 為了避免誤操作或髒資料對全表進行更新,需要配置
必填條件進行限制
WHERE
使用
RowsAffected
可以傳回目前更新的行數
func RowsAffected() {
//查詢字段
whereUser := &User{
Id: 9999,
}
//批量更新字段
updateUser := &User{
UserName: "This is Updates",
Age: 9292,
UserRemark: "Updates",
}
//RowsAffected傳回目前更新的行數
count := DB.Model(whereUser).Updates(updateUser).RowsAffected
fmt.Printf("Updates count %v", count)
}
删除
一般使用資料庫邏輯删除,這部分可參考 https://jasperxu.com/gorm-zh/crud.html#d
回調使用(Callback)
在建立,更新,查詢,删除時将被調用,如果任何回調傳回錯誤,gorm将停止未來操作并復原所有更改。
Callback預設定義
在
gorm
源碼中可以看到預設的
callback
注冊,這裡着重關注
callback_create.go、callback_delete.go、callback_query.go、callback_update.go
即可,其實整個
gorm
的執行流程都是基于
callback
來實作整個執行流程的。
下面舉例
callback_create.go
檔案的
init()
方法,按照從上到下的順序,注冊了
callback
函數,每個
callbackName
(如:gorm:create)都可以作為回調的插入點使用,配合
Before、After
可以實作前置插入、後置插入等,其他
callback
類似這裡不再贅述
// Define callbacks for creating
func init() {
DefaultCallback.Create().Register("gorm:begin_transaction", beginTransactionCallback)
DefaultCallback.Create().Register("gorm:before_create", beforeCreateCallback)
DefaultCallback.Create().Register("gorm:save_before_associations", saveBeforeAssociationsCallback)
DefaultCallback.Create().Register("gorm:update_time_stamp", updateTimeStampForCreateCallback)
DefaultCallback.Create().Register("gorm:create", createCallback)
DefaultCallback.Create().Register("gorm:force_reload_after_create", forceReloadAfterCreateCallback)
DefaultCallback.Create().Register("gorm:save_after_associations", saveAfterAssociationsCallback)
DefaultCallback.Create().Register("gorm:after_create", afterCreateCallback)
DefaultCallback.Create().Register("gorm:commit_or_rollback_transaction", commitOrRollbackTransactionCallback)
}
Callback事務保證
檢視
gorm
源碼,諸如CRUD的方法如
Create()、Update()、Find()、Delete()
等内部都進行了callback方法等調用,callback内部代碼如下:
func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope {
//這裡進行了異常捕獲
defer func() {
if err := recover(); err != nil {
if db, ok := scope.db.db.(sqlTx); ok {
//異常則對事務進行復原
db.Rollback()
}
panic(err)
}
}()
for _, f := range funcs {
(*f)(scope)
if scope.skipLeft {
break
}
}
return scope
}
下面來看一個
callback
中産生panic異常,資料庫復原的示例,如下:
func Callback() {
//注冊在gorm:create節點之後執行函數
DB.Callback().Create().
//在CreateUser()函數方法,即DB執行之後再執行
After("gorm:create").
Register("gorm:AfterCreate", func(scope *gorm.Scope) {
fmt.Println("AfterCreate")
//這裡抛出異常,會被callCallbacks的recover方法捕獲
panic("AfterCreate error")
})
//由于上面callback已經panic,這裡會復原
CreateUser()
}
異常處理
可以對
RecordNotFound()、Error、GetErrors()
來處理記錄不存在、異常、多個異常等情況
func Error() {
//RecordNotFound
bool := DB.First(&User{Id: 9999}).RecordNotFound()
fmt.Println("RecordNotFound => ", bool)
//Error
err := DB.Create(&User{}).Error
fmt.Println("err => ", err)
errs := DB.Create(&User{}).GetErrors()
fmt.Println("errs => ", errs)
}
事務
注意,一旦你在一個事務中,使用
tx
作為資料庫句柄,如果使用
gorm.DB
操作,是不受事務管控的,這裡需要注意!
func Tx() {
//開啟事務
tx := DB.Begin()
//執行方法1 會執行成功
err1 := tx.Create(&User{
UserId: "USER_002",
UserName: "zhangsan",
Age: 0,
Phone: 17701020304,
CreateTime: time.Now(),
UpdateTime: time.Now(),
UserRemark: "",
IgnoreMe: 0,
}).Error
if err1 != nil {
tx.Rollback()
fmt.Println("=== Create 1 error ===")
}
//執行方法2 會執行失敗,有Phone唯一主鍵限制
err2 := tx.Create(&User{
UserId: "USER_002",
UserName: "zhangsan",
Age: 0,
Phone: 17701020304,
CreateTime: time.Now(),
UpdateTime: time.Now(),
UserRemark: "",
IgnoreMe: 0,
}).Error
if err2 != nil {
tx.Rollback()
fmt.Println("=== Create 2 error ===")
}
tx.Commit()
}
日志
// 啟用Logger,顯示詳細日志
db.LogMode(true)
// 禁用日志記錄器,不顯示任何日志
db.LogMode(false)
// 調試單個操作,顯示此操作的詳細日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})
參考
https://jasperxu.com/gorm-zh/