# 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`