岚天逸见

gorm的upsert操作不同字段

场景:

“INSERT INTO ... ON DUPLICATE KEY UPDATE”的应用,在 UPDATE 时不能更新字段 f_create_uid 和 f_create_time 的值,而必须更新 f_update_uid 和 f_update_time 的值。关键点在于指定 UPDATE 不更新的字段列表,实现依赖 gorm 的 tag,但如果 struct 的 field 名同表的 field 名,这没有此依赖。

示例表:

DROP TABLE IF EXISTS TableA;
CREATE TABLE TableA (
    f_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    f_name VARCHAR(25) NOT NULL,
    f_address VARCHAR(100) NOT NULL,
    f_create_uid VARCHAR(20) NOT NULL,
    f_update_uid VARCHAR(20) NOT NULL,
    f_create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    f_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (f_id),
    UNIQUE KEY (f_name)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

使用 sql2struct 工具生成的 struct:

type TableA struct {
    Id uint32 `gorm:"column:f_id;primaryKey;autoIncrement" json:"id" db:"f_id" form:"id"`
    Name string `gorm:"column:f_name" json:"name" db:"f_name" form:"name"`
    Address string `gorm:"column:f_address" json:"address" db:"f_address" form:"address"`
    CreateUid string `gorm:"column:f_create_uid" json:"create_uid" db:"f_create_uid" form:"create_uid"`
    UpdateUid string `gorm:"column:f_update_uid" json:"update_uid" db:"f_update_uid" form:"update_uid"`
    CreateTime time.Time `gorm:"column:f_create_time" json:"create_time" db:"f_create_time" form:"create_time"`
    UpdateTime time.Time `gorm:"column:f_update_time" json:"update_time" db:"f_update_time" form:"update_time"`
}

表的“INSERT INTO ... ON DUPLICATE KEY UPDATE”操作:

package main

import (
    "flag"
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/clause"
    "os"
    "reflect"
    "strings"
    "time"
)

/*
DROP TABLE IF EXISTS TableA;
CREATE TABLE TableA (
    f_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
    f_name VARCHAR(25) NOT NULL,
    f_address VARCHAR(100) NOT NULL,
    f_create_uid VARCHAR(20) NOT NULL,
    f_update_uid VARCHAR(20) NOT NULL,
    f_create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    f_update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (f_id),
    UNIQUE KEY (f_name)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
*/

// TableA 本例依赖 gorm 的 tag
type TableA struct {
    Id         uint32    `gorm:"column:f_id;primaryKey;autoIncrement" json:"id" db:"f_id" form:"id"`
    Name       string    `gorm:"column:f_name" json:"name" db:"f_name" form:"name"`
    Address    string    `gorm:"column:f_address" json:"address" db:"f_address" form:"address"`
    CreateUid  string    `gorm:"column:f_create_uid" json:"create_uid" db:"f_create_uid" form:"create_uid"`
    UpdateUid  string    `gorm:"column:f_update_uid" json:"update_uid" db:"f_update_uid" form:"update_uid"`
    CreateTime time.Time `gorm:"column:f_create_time" json:"create_time" db:"f_create_time" form:"create_time"`
    UpdateTime time.Time `gorm:"column:f_update_time" json:"update_time" db:"f_update_time" form:"update_time"`
}

func (t *TableA) TableName() string {
    return "TableA"
}

var (
    dsn = flag.String("dsn", "", "dbuser:dbpassword@tcp(dbhost:dbport)/dbname?charset=utf8mb4&parseTime=True&loc=Local")
)

func main() {
    // 命令行参数解析
    flag.Parse()

    // 命令行参数检查
    if *dsn == "" {
        fmt.Printf("parameter[-dsn] is not set\n")
        flag.Usage()
        os.Exit(1)
    }

    // 连接数据库
    tx, err := gorm.Open(mysql.Open(*dsn), &gorm.Config{})
    if err != nil {
        fmt.Printf("open mysql error: %s\n", err.Error())
        os.Exit(1)
    }
    tx = tx.Debug() //.Table("TableA") // 这里不能有 Table 调用,否则会导致 Omit 影响后面的 Find,导致 Find 不是全部的字段,即不是”SELECT * FROM“

    // 获取 DsFundBatchStudent 结构体的所有字段
    typeOfModel := reflect.TypeOf((*TableA)(nil)).Elem()
    numFields := typeOfModel.NumField()           // 取得总的字段数
    updateColumns := make([]string, 0, numFields) // 存放所有需更新的字段

    // 遍历所有字段,排除不需要更新的字段
    for i := 0; i < numFields; i++ {
        field := typeOfModel.Field(i)
        fieldName := field.Name
        if fieldName != "Id" && fieldName != "CreateUid" && fieldName != "CreateTime" && fieldName != "UpdateTime" {
            // 使用结构体标签(tag)获取字段的列名
            columnName := field.Tag.Get("gorm")
            if strings.HasPrefix(columnName, "column:") {
                columnName = strings.TrimPrefix(columnName, "column:")
            }
            updateColumns = append(updateColumns, columnName)
        }
    }
    fmt.Printf("updateColumns=>%+v\n", updateColumns)

    aa10 := []*TableA{
        &TableA{
            Name:      "zhangsan",
            Address:   "shenzhen",
            CreateUid: "lisi",
            UpdateUid: "lisi",
        },
        &TableA{
            Name:      "wangwu",
            Address:   "nanjing",
            CreateUid: "lisi",
            UpdateUid: "lisi",
        },
    }
    // 插入时忽略的字段
    db := tx
    db = db.Omit("f_id", "f_update_time", "f_create_time")
    db = db.Clauses(clause.OnConflict{
        //Columns: []clause.Column{{Name: "f_id"}}, // 指定冲突检查的列
        DoUpdates: clause.AssignmentColumns(updateColumns), // 指定更新的列
    })
    err = db.Create(aa10).Error
    if err != nil {
        fmt.Printf("create aa1 error: %s\n", err.Error())
        os.Exit(1)
    }

    var a11 []*TableA
    db = tx // .Table("TableA") // 这里的 Table 调用可有可无
    err = db.Order("f_name").Find(&a11).Error
    if err != nil {
        fmt.Printf("find aa11 error: %s\n", err.Error())
        os.Exit(1)
    }
    for _, a110 := range a11 {
        fmt.Printf("%+v\n", *a110)
    }

    aa20 := []*TableA{
        &TableA{
            Name:      "zhangsan",
            Address:   "hangzhou",
            CreateUid: "xiaoming",
            UpdateUid: "xiaoming",
        },
        &TableA{
            Name:      "zhouba",
            Address:   "guangzhou",
            CreateUid: "xiaoming",
            UpdateUid: "xiaoming",
        },
    }
    // 插入时忽略的字段
    db = tx
    db = db.Omit("f_id", "f_update_time", "f_create_time")
    db = db.Clauses(clause.OnConflict{
        //Columns: []clause.Column{{Name: "f_id"}}, // 指定冲突检查的列
        DoUpdates: clause.AssignmentColumns(updateColumns), // 指定更新的列
    })
    err = db.Create(aa20).Error
    if err != nil {
        fmt.Printf("create aa2 error: %s\n", err.Error())
        os.Exit(1)
    }

    var a21 []*TableA
    db = tx //.Table("TableA") // 这里的 Table 调用可有可无
    err = db.Order("f_name").Find(&a21).Error
    if err != nil {
        fmt.Printf("find a21 error: %s\n", err.Error())
        os.Exit(1)
    }
    for _, a210 := range a11 {
        fmt.Printf("%+v\n", *a210)
    }

    // 当本程序反复执行时,由于只有 zhangsan 的 UpdateUid 的交替变化,
    // 故只会观察到 zhangsan 一行的 f_update_time 在一直变化,
    // wangwu 和 zhouba 执行一次后保持不再变化,因为没有任何字段值发生变化。
}

posted on 2024-05-23 18:03  岚天逸见  阅读(93)  评论(0编辑  收藏  举报

导航