---
# System prepended metadata

title: gorm
tags: [Go]

---

# gorm
https://juejin.cn/post/7198357438840406071



https://blog.csdn.net/jingghe/article/details/121642665


find 解析
https://juejin.cn/post/7287131174266994745

## v2 vs v1

v2 链式调用一定要记得最后用 .Error 去拿错误。v1 有些方法返回 (DB, error)，v2 都改成只返回 *gorm.DB，你从它身上取 Error。

## 踩坑

## 源碼解讀
https://zhuanlan.zhihu.com/p/663707360
https://liudon.com/posts/how-gorm-generates-sql/
## log 顯示
![](https://hackmd.io/_uploads/rkTQ733cn.png)


## 自定義數據

## 自定義序列化
![](https://hackmd.io/_uploads/HJAYfm2_h.png)
實現scan 跟value

雖然一樣是scan 跟value但參數不一樣

記住 他是到gorm前
自定義數據是 gorm 到db

## uuid
如果使用db的uuid產生
![](https://hackmd.io/_uploads/SkEOzKiN3.png)

這邊tag要加上default 不然他不會吃到 我也不知道為什麼
![](https://hackmd.io/_uploads/H1e6zYjVh.png)

## strcut 轉 strcut
![](https://i.imgur.com/DpjxFJx.png)


## 讀寫分離
![](https://i.imgur.com/3efNGBa.png)


## connect
如果用sql.Open在去連線的話
要注意sql.Open那邊有沒有close
因為gorm會自動處理close的部分


## 只要欄位不用struct
```
func (t *TicketRepo) GetSellTimeRange(ctx context.Context, tx *gorm.DB, idList []int64) (sellStartAt, sellEndAt sql.NullTime, err error) {
    if tx == nil {
        tx = t.Orm()
    }

    query := `
        SELECT MIN(sell_start_at) as sell_start_at, 
               MAX(sell_end_at) as sell_end_at
        FROM tickets 
        WHERE id IN (?)`

    row := tx.WithContext(ctx).Raw(query, idList).Row()
    if err := row.Scan(&sellStartAt, &sellEndAt); err != nil {
        return sql.NullTime{}, sql.NullTime{}, err
    }

    return sellStartAt, sellEndAt, nil
}
```


## 表達式
```
db.Clauses(clause.OrderBy{
  Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},
}).Find(&User{})
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
```
可以自己用建立 Clauses 裡面可以去找對應的orderBy或其他的
然後在用expr去寫raw sql

### 条件表达式
如果你想构建一些查询 helper，你可以让 struct 实现 clause.Expression 接口：

![](https://i.imgur.com/NzqyzfQ.png)
上面json操作等於 like只能用下面方法喔
![](https://i.imgur.com/sQKGg1f.png)


## hook 可以傳入值
![](https://i.imgur.com/OjGc3kR.png)

可以像是你紀錄log用
讀取用InstanceGet
![](https://i.imgur.com/W1aKmqP.png)
![](https://i.imgur.com/xKjPAgk.png)


## 指定model
![](https://i.imgur.com/ecF15gx.png)

## first find
因為你傳入point了 所以他可以直接用struct的table name
function 不用再指定對應的model
![](https://i.imgur.com/U0yaBus.png)

以上是指用model指定或 直接find first的傳入

如果是用table要注意 
不能用first or find 他們是一定要指定struct
![](https://i.imgur.com/JdQ0fYN.png)

當然你也可以用匿名的 但記得first or find 一定要struct
![](https://i.imgur.com/YDzHJgi.png)


## chain
![](https://i.imgur.com/39Lzu1b.png)
如果做任何where都會getInstan 
所以只要將它賦值 之後一直連接條件就不用重新賦值了

如果要新的就newSession 但建議直接傳入的db 去做就好
當然也可以newDB 比較是subQuery所以這種比較少用到


## where
query with struct 只會用非零
![](https://i.imgur.com/7PAi5JY.png)
但struct可以用指定欄位 可以用到非零
![](https://i.imgur.com/Z5ARKt3.png)

### 差異
find找不到可以用.RecordNotFound判斷
first會丟出errRecordNotFound error
![](https://i.imgur.com/5hNKpAp.png)
![](https://i.imgur.com/R0ld9YB.png)

## scan vs find
http://foreversmart.cc/go/the-difference-of-gorm-scan-and-find/

### scan vs  pluck
Scan 和 Pluck 都可以用來從結果中讀取單個欄位，但是用法略有不同：

Scan 將查詢結果映射到一個結構體或一個變量中。如果查詢返回多行結果，只有第一行會被掃描到，其它行會被忽略。如果查詢沒有結果，則掃描結構體或變量的值將保持不變。

Pluck 將查詢結果映射到一個切片中。切片的元素類型必須是和查詢結果中該欄位相同的類型。如果查詢沒有結果，則切片將保持為空切片。

簡單來說，如果你只需要查詢結果中的一個欄位，而不需要其他欄位，那麼可以使用 Pluck 或 Scan。如果你需要查詢結果中的多個欄位，或者需要將結果映射到一個結構體中，那麼應該使用 Scan。

## limit
通过 -1 消除 Offset 条件
## select
可以
![](https://i.imgur.com/B1i6CVZ.png)


## update vs updates
update的model一定要有id

db.Model(&user)

## clause
![](https://i.imgur.com/dAuyU5X.png)

## gorm添加数据库排他锁，for update
https://blog.csdn.net/juzipidemimi/article/details/104502385

## 批量插入
![](https://i.imgur.com/kgUVSQO.png)

## save vs update
Save函數將為不存在的實體創建主鍵以及更新字段，而 Update只會更新字段。

## updates
![](https://i.imgur.com/tv1h4Ew.png)
![](https://i.imgur.com/Oq5NBH3.png)
小心zero-value
## gorm
https://learnku.com/docs/gorm/v2/v2_release_note/9756

### PostgreSQL 
https://myapollo.com.tw/zh-tw/golang-gorm-postgresql-jsonb/

### 嵌入结构体
![](https://i.imgur.com/P6inUE5.png)
对于正常的结构体字段，你也可以通过标签 embedded 将其嵌入
![](https://i.imgur.com/3qMXWlP.png)
并且，您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀，例如
![](https://i.imgur.com/a9PQ7up.png)

### 使用 SQL 表达式、Context Valuer 创建记录
https://learnku.com/docs/gorm/v2/create/9732

### 默认值
![](https://i.imgur.com/ydQfonC.png)


**注意 像 0、''、false 等零值，不会将这些字段定义的默认值保存到数据库。您需要使用指针类型或 Scanner/Valuer 来避免这个问题，例如：**
![](https://i.imgur.com/AmN8925.png)

## orderBy
![](https://i.imgur.com/ad58lw1.png)


### zero value
当通过结构体进行查询时，GORM将会只通过非零值字段查询，这意味着如果你的字段值为0，''， false 或者其他 零值时，将不会被用于构建查询条件
![](https://i.imgur.com/zSvMPsz.png)
![](https://i.imgur.com/PlDbJmv.png)


### first, last ....
First、Last 方法会根据主键查找到第一个、最后一个记录， 它仅在通过 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键，则按第一个字段排序，例如：
![](https://i.imgur.com/IxdE0oZ.png)

![](https://i.imgur.com/vzthmTF.png)
注意如果放入實例化 就會按照第一字段排序

## firOrCreate, FirstOrInit

### Assign vs Attrs
Assign 算是另外條件 laravel updateOrcreate的感覺 可看下面用到的updateOrCreate
當你沒找到 要insert的欄位

![](https://i.imgur.com/GqHkVxK.png)

Attrs -> 沒找到 加上條件create
        -> 找到 根據條件更新
        
Assign -> 沒找到 加上條件create
        -> 找到。加上條件更新

## 关联创建
![](https://i.imgur.com/bGkzzEg.png)

根据 map 创建记录时，association 不会被调用，且主键也不会自动填充

## updateOrCreate
https://juejin.cn/post/7155840164504272933


https://stackoverflow.com/questions/39333102/how-to-create-or-update-a-record-with-gorm
![](https://i.imgur.com/OuGIC0K.png)
![](https://i.imgur.com/RtZ1XQQ.png)
這種也是

需要根據唯一鍵來進行判斷，那就可以使用第一種做法
比較合理 因為他是補捉衝突的情況

 Assign 函式，該函式只會更新傳入參數不為零值的欄位

## 自定义数据类型
json rawMessage or 
pq.StringArray
datatypes.JSON(可以看他怎實現的 參考)

![](https://i.imgur.com/u28Ng2p.png)
![](https://i.imgur.com/JcDglHt.png)
自定義也能用hook喔
![](https://i.imgur.com/goUKMaK.png)


https://github.com/go-gorm/datatypes
很多可以參考

記住 這些都要升級版本
![](https://i.imgur.com/WTum9lf.png)


## Upsert 及冲突
![](https://i.imgur.com/cbqXzJ3.png)
都用AssignmentColumns 第一個postgres沒有支援

## 原始碼分析
https://cloud.tencent.com/developer/article/1881967
https://jiajunhuang.com/articles/2019_03_19-gorm.md.html

### query 
https://segmentfault.com/a/1190000019490869

## 表达式
![](https://i.imgur.com/A63hzXR.png)


## tag
![](https://i.imgur.com/XqIjRJo.png)
![](https://i.imgur.com/vrlxJim.png)

## Associations
### Associations
model可以直接放id, 只能id
![](https://i.imgur.com/PItHLBP.png)
搭配where condition （這裡使用 where 的條件會套用到 categories 上）
![](https://i.imgur.com/25IWHW6.png)

### Replace Association


## create_at and update_at
![](https://i.imgur.com/BQcUWMo.png)

## callback
https://juejin.cn/post/7097999127300014093


https://eddycjy.gitbook.io/golang/di-3-ke-gin/gorm-callback
![](https://i.imgur.com/08HqH0x.png)
作法有變 看文黨

## 分頁

## clause

https://blog.csdn.net/dorlolo/article/details/127238144

![](https://hackmd.io/_uploads/HyCXosJFh.png)
特殊可自訂
![](https://hackmd.io/_uploads/SJeSookKn.png)
在對應的基本cluase 上面加上對應的modifier


所以對於
![](https://hackmd.io/_uploads/SJMCoikt2.png)
你如果是基本的cluase interface你就可以直接傳入
如果是modifyStamement

可以用軟刪除跟hint
hint目前支援三個
![](https://hackmd.io/_uploads/SJTh3skKh.png)

## join
![](https://hackmd.io/_uploads/HJseFn1K3.png)

## distinct
![](https://hackmd.io/_uploads/r1X_9nkYn.png)

## select

![](https://hackmd.io/_uploads/ByOdn2yK3.png)

## or
![Uploading file..._c45t7e6ng]()

複雜可以用stuct不然看很累

## where
![](https://hackmd.io/_uploads/Sy5ZT3kKh.png)
struct一定要指定欄位 不然零值不會被查詢

## 軟刪
![](https://hackmd.io/_uploads/BygyC31Y2.png)
![](https://hackmd.io/_uploads/Hyd_g6kKn.png)
https://blog.csdn.net/dorlolo/article/details/127238144
![](https://hackmd.io/_uploads/HJ7oWTyFh.png)



詳解
https://juejin.cn/post/7151565238259679240
非常詳細
他是直接在sql上改變
跟用callback不一樣

callback是直接註冊到hook上

flag
![](https://hackmd.io/_uploads/SJbpXa1th.png)

## 刪除
![](https://hackmd.io/_uploads/HkJfma1Fh.png)

## time
https://studygolang.com/articles/35739

###### tags: `Go`