前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GORM V2 几个最实用的功能和升级注意事项

GORM V2 几个最实用的功能和升级注意事项

作者头像
KevinYan
发布2024-06-26 12:38:20
1080
发布2024-06-26 12:38:20
举报
文章被收录于专栏:网管叨bi叨网管叨bi叨

最近在自己在开发个人的新项目,这个项目预计未来几个月后能跟大家见面,项目搭建的过程中遇到了ORM版本选择的问题,经过自己仔细斟酌还是选择了GORM的 V2版作为项目的ORM框架,这个抉择过程其实就是说服自己不使用的V1的一个心里斗争。

因为这几年在公司做的项目都是使用的GORM的V1版本,如果选择V1的话我只要把以前总结的那些代码拿过来改改就能用了,但是因为两个原因还是选择了使用GORM V2,下面我先重点说一下这两个原因,再介绍几个使用V2版本时大家写代码需要注意的破坏性更新。

V2 支持在日志中增加追踪信息

说实话这个是我选择升级到V2的一个主要原因, 良好的基础框架是一个项目成功的必备因素,GORM V1版本开发的Logger接口中我们是没有办法把请求上下文传递进去的。

在使用GORM的时候,如果我们想把GORM产生的日志记录到项目统一的应用日志中的时,需要自己去实现GORM提供的logger 接口。

V1版本的GROM的 logger 接口长这个样子,仅仅提供了一个Print方法,Print方法的参数都是 create、updates、query 这些方法的回调中传递过去的,我们并没有办法传递Context。

代码语言:javascript
复制
type logger interface {
 Print(v ...interface{})
}

那么你想用常规方法把请求的traceId 记录到GORM 生成的日志中是完全不可能实现的,只能借助一些非常规的方法,比如引入一个GLS开源库,每个请求唯一的traceid、spanid 这些都放到gls里,记日志的时候再从GLS里把这些信息拿出来记录到日志中去。

一般都不推荐引入GLS,实际上性能影响不明显,之前有些服务请求量最大2000QPS的时候也没出现过瓶颈。不过现在Github上star最多的那个GLS库在 Go 1.20 版本以后已经不能用了,不然我们之前公司的那些项目用的Go版本就不会卡在1.19啦 笑死╮(╯▽╰)╭

在GORM V2 中它新增了以下Logger 接口:

代码语言:javascript
复制
type Interface interface {  
   LogMode(LogLevel) Interface  
   Info(context.Context, string, ...interface{})  
   Warn(context.Context, string, ...interface{})  
   Error(context.Context, string, ...interface{})  
   Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)  
}

每个方法都有应用的上下文Context参数传递进来,还专门提供了Trace方法让我们实现,供我们实现查询的SQL语句和消耗时间的记录。

当我们自己实现好GORM的Logger后,在GORM创建连接的时候需要把Logger选项配置成我们自定义Logger

代码语言:javascript
复制
db, err := gorm.Open(
     mysql.Open(
       cfg.Dsn, &gorm.Config{
         Logger: MyGormLogger
       })
)

在使用GORM执行查询的地方,通过withContext 带上Context 信息即可

代码语言:javascript
复制
func (dao *userDao) AddUser(c context.Context, user *model.User) (userId int64, err error) { 
   err = db.GetConn().WithContext(c).Create(user).Error  
   ....
}

关于怎么自定义实现GORM Logger 把GORM 日志统一整合到项目应用日志,未来等我项目成型了再跟大家分享。接下来说下第二个让我决定使用GORM V2 的原因

CREATE方法支持批量创建模型

在GORM V1版本里,模型本身是不在带批量创建的功能的,想要批量创建一种选择是写个循环,在循环里调用模型的Create方法。

还有一种是使用db.Raw 或者 db.Exec 执行手写的SQL来进行批量创建,我以前每次需要批量创建模型是都会手动在模型里定义一个BulkCreate方法

代码语言:javascript
复制
func BulkInsertOrderGoods(unsavedRows []*table.OrderGoods) error {
 valueStrings := make([]string, 0, len(unsavedRows))
 valueArgs := make([]interface{}, 0, len(unsavedRows)*3)
 for _, row := range unsavedRows {
  valueStrings = append(valueStrings, "(?, ?, ?)")
  valueArgs = append(valueArgs, row.UserId)
  valueArgs = append(valueArgs, row.GoodsName)
  valueArgs = append(valueArgs, row.OrderId)
 }
 statement := fmt.Sprintf("INSERT INTO "+table.OrderGoods{}.TableName()+" (user_id, goods_name, order_id) VALUES %s",
  strings.Join(valueStrings, ","))

 err := DB().Exec(statement, valueArgs...).Error
 return err
}

还得时刻注意,尽量让程序拼接SQL时不出错。

那么在GORM V2 里,我们只需要把模型对象的把模型切片传给模型的Create方法

代码语言:javascript
复制
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  user.ID // 1,2,3
}

或者是使用gorm.DB对象上的方法 CreateInBatches 来指定批量插入的批次大小

代码语言:javascript
复制
var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}

// batch size 100
db.CreateInBatches(users, 100)

CreateInBatches方法需要在初始化GORM的时候指定对应的配置,推荐还是用第一种方法。另外更新或者插入方法Upsert 在V2也支持批量操作。

我觉得有了这两个特性,在新搭建项目的时候很难不选择使用V2版本,第一个特性让用日志排查问题变得更简单,第二个特性能让不用再去自己写代码实现批量操作。

接下来说几个破坏性更新,这个可能是从V1 升级到 V2的障碍

需要注意的几个破坏性更新

初始化方式变更

GORM V1 和 V2 用到的初始化Open方法发生了变更

代码语言:javascript
复制
/ jinzhu
func Open(dialect string, args ...interface{}) (db *DB, err error) {}

// grom.io
func Open(dialector Dialector, opts ...Option) (db *DB, err error) {}

此外还有一些设置连接的方式也有微调,我把V1和V2 初始化的Demo 放在这里大家可以比较一下,首先是V1版本的

代码语言:javascript
复制
 db, err := gorm.Open(config.Database.Type, config.Database.DSN)
 if err != nil {
  panic(err)
 }
 db.DB().SetMaxOpenConns(config.Database.MaxOpenConn)
 db.DB().SetMaxIdleConns(config.Database.MaxIdleConn)
 db.DB().SetConnMaxLifetime(config.Database.MaxLifeTime)

下面是V2版本的

代码语言:javascript
复制
 db, err := gorm.Open(
  mysql.Open(option.DSN),
  &gorm.Config{
   Logger: NewGormLogger(),
  },
 )
 if err != nil {
  panic(err)
 }
 sqlDb, _ := db.DB()
 sqlDb.SetMaxOpenConns(option.MaxOpenConn)
 sqlDb.SetMaxIdleConns(option.MaxIdleConn)
 sqlDb.SetConnMaxLifetime(option.MaxLifeTime)

Find 查不到数据时不再返回Error

使用Find查询数据的时候,在V1版本里如果查不到数据会返回错误,所以很多人在代码里的下面这行判断会失效

代码语言:javascript
复制
if err != gorm.ErrRecordNotFound

但是使用 FirstLastTake 这些预期会返回结果的方法查询记录时,还会返回 ErrRecordNotFound

软删除支持更多模式

说到这里,发现有个很好的特性在上面忘记说了,汗。。。 那就在这里在补充一下吧,GORM自带的软删除我之前是不会用的,因为它那个字段名还有字段的默认值都是限定不能改的,默认值NULL,这在很多公司里DBA设置的约束里是不允许的。

所以我之前没有使用过。但是现在GORM V2 支持Flag 模式了,就是咱们很多人用的0代表未删除 1代表删除

使用前需要先安装GORM的soft_delete这个包。

代码语言:javascript
复制
go get -u "gorm.io/plugin/soft_delete"

在定义模型时像下面这样给标记软删除的字段加上这个tag

代码语言:javascript
复制
type User struct {
  ID    uint
  Name  string
  IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

那么这样GORM在执行SQL语句时就会自动带上is_del这个字段进行查询啦

代码语言:javascript
复制
// Query
SELECT * FROM users WHERE is_del = 0;

// Delete
UPDATE users SET is_del = 1 WHERE ID = 1;

这个我觉得也很好用。

是否要升级V2

这里说的这些知识我目前体验下来的几点明显变化,像什么数据迁移之类的我就没看,因为真正做项目的时候没这个权限。

大家觉得有必要从V1升级到V2吗,反正我负责的这些祖传老项目我是不敢动的,新项目倒是可以无脑选择V2。

咱们有踩过V1升V2版本的坑么,可以在评论区里说说呀。


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 网管叨bi叨 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • V2 支持在日志中增加追踪信息
  • CREATE方法支持批量创建模型
  • 需要注意的几个破坏性更新
    • 初始化方式变更
      • Find 查不到数据时不再返回Error
        • 软删除支持更多模式
        • 是否要升级V2
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档