【问题标题】:Create a general func from particular function (refactoring)从特定函数创建通用函数(重构)
【发布时间】:2019-03-17 15:42:05
【问题描述】:

我正在使用createUsers func 填充我的假数据库,仅用于测试。

我正在使用pq (https://godoc.org/github.com/lib/pq#hdr-Bulk_imports) 的批量导入功能。

func createUsers() {

    users := []models.User{}

    for i := 0; i < 10; i++ {
        users = append(users, models.User{Username: "username"+i, Age: i})
    }

    connStr := "user=postgres password=postgres dbname=dbname sslmode=disable"
    DB, err = sql.Open("postgres", connStr)
    checkErr(err)

    txn, err := DB.Begin()
    checkErr(err)

    stmt, err := txn.Prepare(pq.CopyIn("users", "username", "age"))
    checkErr(err)

    for _, user := range users {
        _, err = stmt.Exec(user.Username, user.Age)
        checkErr(err)
    }

    _, err = stmt.Exec()
    checkErr(err)

    err = stmt.Close()
    checkErr(err)

    err = txn.Commit()
    checkErr(err)
}

此代码中的所有内容都运行良好。

需要:

我现在需要的是使其“通用”,而不仅仅是用户模型。

我想我需要类似的东西:

DBBulkInsert(users, "users", "username", "age")

与 func DBBulkInsert 类似:

func DBBulkInsert(rows []interface{}, tableName string, tableColumns ...string) {
    // DB var from connection func

    txn, err := DB.Begin()
    checkErr(err)

    stmt, err := txn.Prepare(pq.CopyIn(tableName, tableColumns...))
    checkErr(err)

    for _, row := range rows {
        _, err = stmt.Exec(row[0], row[1]) //THIS IS TOTALLY WRONG! WHAT TO DO HERE?
        checkErr(err)
    }

    _, err = stmt.Exec()
    checkErr(err)

    err = stmt.Close()
    checkErr(err)

    err = txn.Commit()
    checkErr(err)
}

问题:

显然_, err = stmt.Exec(row[0], row[1]) 是完全错误的。我不明白如何使用我的 users 数组调用 DBBulkInsert

仍然更好:

也许我也可以删除DBBulkInsert(users, "users", "username", "age") 中的参数"users", "username", "age",但是如何?反射?

【问题讨论】:

    标签: postgresql go refactoring bulkinsert pq


    【解决方案1】:

    您的rows 类型必须是[][]interface{},即行列表,其中每一行都是列值的列表。然后使用该类型,每个row 都可以使用...“解包”到Exec 调用中。

    即:

    for _, row := range rows {
        _, err = stmt.Exec(row...)
    }
    

    要从[]model.User[]model.Whatever[][]interface{},您需要使用反射。如果需要,还可以使用反射来获取列名和表名。

    假设您有一个模型类型,例如:

    type User struct {
        _        struct{} `rel:"users"`
        Username string   `col:"username"`
        Age      int      `col:"age"`
    }
    

    现在您可以使用反射从字段的结构标记中获取表名和列列表。 (请注意,使用_(空白)字段只是如何指定表名的一种选择,它有其缺点和优点,因此由您选择,这里我只是想演示如何反映包可以利用)。

    下面是一个更完整的例子,说明如何从标签中收集“元”数据以及如何从结构字段中聚合列值。

    func DBBulkInsert(source interface{}) {
        slice := reflect.ValueOf(source)
        if slice.Kind() != reflect.Slice {
            panic("not a slice")
        }
    
        elem := slice.Type().Elem()
        if elem.Kind() == reflect.Ptr {
            elem = elem.Elem()
        }
        if elem.Kind() != reflect.Struct {
            panic("slice elem not a struct, nor a pointer to a struct")
        }
    
        // get table and column names
        var tableName string
        var cols []string
        for i := 0; i < elem.NumField(); i++ {
            f := elem.Field(i)
            if rel := f.Tag.Get("rel"); len(rel) > 0 {
                tableName = rel
            }
            if col := f.Tag.Get("col"); len(col) > 0 {
                cols = append(cols, col)
            }
        }
    
        // aggregate rows
        rows := [][]interface{}{}
        for i := 0; i < slice.Len(); i++ {
            m := slice.Index(i)
            if m.Kind() == reflect.Ptr {
                m = m.Elem()
            }
    
            vals := []interface{}{}
            for j := 0; j < m.NumField(); j++ {
                ft := m.Type().Field(j)
                if col := ft.Tag.Get("col"); len(col) > 0 {
                    f := m.Field(j)
                    vals = append(vals, f.Interface())
                }
            }
    
            rows = append(rows, vals)
        }
    
        // ...
    }
    

    Run it on playground

    【讨论】:

    • 你的代码太棒了。如果我不能在模型的结构上使用标签怎么办?
    • 另一个问题是表名可能不同(例如:“TeamId”可以是 DB 上的“team_id”)并且没有标签我认为这是不可能的,我还需要将此信息传递给功能我错了吗?
    • @FredHors 在这种情况下,另一种选择是创建reflect.Types 到表名和列的映射,但您需要确保列与字段的顺序相同.因此,只要字段的顺序发生更改或添加/删除字段,您还需要调整列的切片。 play.golang.com/p/cohQ3rg3BvV
    • @FredHors In Go 执行 User{Username:"Joe"}User{Username:"Joe", Age: 0} 相同,User{Age: 21}User{Username:"", Age: 21} 相同。 复合文字中未列出的字段将自动初始化为它们的零值
    • @FredHors 跳过字段当然是一种选择,对于gorm.Model 来说可能是正确的选择。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-10
    • 1970-01-01
    • 2020-02-20
    • 2016-06-23
    • 1970-01-01
    • 2018-02-07
    相关资源
    最近更新 更多