如何使用 gorm plugins/hooks 将新记录插入数据库

How to insert new record to db using gorm plugins/hooks

在尝试插入日志以使用 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 中做这样的事情?

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

尝试创建交易 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)

怎么样:

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来使用

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

您首先需要以正确的顺序注册您的插件执行,在我的例子中它应该是这样的:

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)
    }
}

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