【问题标题】:How to insert new record to db using gorm plugins/hooks如何使用 gorm 插件/钩子将新记录插入数据库
【发布时间】:2021-02-19 00:05:34
【问题描述】:

在尝试使用 golang 中的 gorm 插入日志以检测模型的值更改时,我正在使用插件:

type MyModel struct {
 Id
 Name
}

type Log struct {
 Id
 NewValue
 OldValue
 CreatedAt
}

我的插件定义是这样的:

func ChangelogCreatePlugin(db *gorm.DB) {
    log := &Log{NewValue: "the new value", OldValue: "the old value", CreatedAt: "current time"}
    // Here is the problem
    db.Save(log) <- this is not acceptable
}

在插件中使用 db *gorm.DB 参数插入不同的数据模型是不可接受的,因为该参数已初始化为接受来自触发插件的同一模型的数据。

我的要求是将我的日志存储在同一个数据库事务中,因此如果其中一个失败,它们都应该失败。如何在gorm中做这样的事情?

我知道钩子。钩子在我的情况下没有用,因为我希望我的日志跟踪不同的模型,所以我正在寻找更多“可重用”的解决方案,而不是在我的模型中复制/粘贴钩子实现。

【问题讨论】:

    标签: go transactions go-gorm


    【解决方案1】:

    经过大量的挖掘和调试,我想出了这个解决方案:

    您首先需要以正确的顺序注册您的插件执行,在我的情况下应该是这样的:

    gormDb.Callback().Create().Before("gorm:commit_or_rollback_transaction").Register("changelog_create", ChangelogCreatePlugin)
    

    此命令将保证您的插件将在模型的任何插入子句的事务提交之前被激活或触发。

    要了解有关在哪里注册插件的更多信息,请查看the default callbacks registered in gorm

    这样做后,我需要调整插件代码:

    func ChangelogCreatePlugin(db *gorm.DB) {
        // first make a check that the model insert transaction doesn't have any error
        if db.Error != nil {
            return
        }
    
        
        log := &Log{NewValue: "the new value", OldValue: "the old value", CreatedAt: "current time"}
        
        // get a new db session for the new model to work
        logDb := db.Session(&gorm.Session{})
    
    
        // if an error ocurred while saving the log
        // push it into original model db instance errors, so it will be rolledback eventually
        logErr := logDb.Save(log)
        if logErr != nil {
            db.AddError(logErr)
        }
    }
    

    希望有一天这对某人有所帮助!

    【讨论】:

      【解决方案2】:

      尝试创建事务性decorator 女巫使用func(d *gorm.DB)f ...func(d *gorm.DB) 作为参数。 它应该看起来像

      type TransactionalDecorator struct {
          DB *gorm.DB
      }
      
      func (t *TransactionalDecorator) Transaction(f ...func(d *gorm.DB) error) error  {
          return t.DB.Transaction(func(tx *gorm.DB) error {
              for _, fn := range f {
                  if err := fn(tx); err != nil {
                      return err
                  }
              }
              return nil
          })
      }
      

      所有数据库交互函数将在同一个事务中执行

      repo , _ := gorm.Open(...)
      
      tdb := &TransactionalDecorator{DB: repo}
      
      saveModel := func(d *gorm.DB) error {
          // save model
      }
      saveLog := func(d *gorm.DB) error {
          // save log
      }
      
      err := tdb.Transaction(saveModel, saveLog)
      

      【讨论】:

      • 您好,谢谢您的回答。但是,我不希望我的解决方案强迫我对现有代码库进行任何更改,因为该库应该通过仅在其中实现微小更改来与不同的存储库一起使用。这就是我选择插件的原因,或者从一开始就想到了钩子。
      • @MAZux - 添加一些使用插件的例子
      【解决方案3】:

      怎么样:

      func ChangelogCreatePlugin(db *gorm.DB) {
          log := &Log{NewValue: "the new value", OldValue: "the old value", CreatedAt: "current time"}
          // Here is the problem
          db.Session(&gorm.Session{}).Save(log)
      }
      

      这应该会给你一个“新鲜”的 db/tx 来使用

      【讨论】:

      • 谢谢,但它不会是事务性的,如果日志保存失败,模型的保存不会回滚
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-23
      • 1970-01-01
      • 2013-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多