GORM 最佳实践(4) | 青训营笔记

303 阅读5分钟

GORM 最佳实践(4) | 青训营笔记

  • 数据序列化与 SQL 表达式 √
  • 批量数据操作 √
  • 代码复用、分库分表、Sharding √
  • 混沌工程/压测 √
  • Logger/Trace √
  • Migrator
  • Gen 代码生成/Raw SQL
  • 安全

Migrator - 数据库迁移管理

db.AutoMigrate(&User{})

import "github.com/go-gormigate/gormigate/v2"

m := gormigate.New(db, gormigate.DefaultOptions, []*gormigate.Migration{
    {
        ID: "201608301400",
        Migrate: func(tx *gorm.DB) error {
            type User struct {
                Name string
            }
            return tx.Model(&User{}).AddColumn(&User{})
        },
        Rollback: func(tx *gorm.DB) error {
            return tx.Model(&User{}).DropColumn(&User{})
        },
    },
})

m.Migrate()

Gen 代码生成/Raw SQL - Raw SQL

db.Raw("SELECT id, name, age FROM users WHERE name = ?", "jinzhu").Scan(&result)
db.Raw("SELECT id, name, age FROM users WHERE name = @name","jinzhu").Scan(&result)
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(),[]int64{1,2,3})

row := db.Table("users").Where("name = ?", "jinzhu").Select("name", "age").Row()
row.Scan(&name, &age)

DB.Where("name1 = @name OR name2 = @name2", sql.Named("name","jinzhu")).Find(&user)
DB.Where("name1 = @name OR name2 = @name2", map[string]interface{}{"name": "jinzhu"}).Find(&user)
DB.Where("name1 = @name OR name2 = @name2", User{Name: "jinzhu"}).Find(&user)

DB.Raw(
    "SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name",
    sql.Named("name", "jinzhu"),sql.Named("name2","jinzhu2")
).Find(&user)
  1. 第一行代码使用了db.Raw来构造原生的SQL语句,并通过Scan方法将查询结果映射到result变量中。
  2. 第二行代码与第一行代码类似,但是使用了@符号来标识参数,同时也通过Scan方法将查询结果映射到result变量中。
  3. 第三行代码使用了db.Exec来执行原生的SQL语句,其中第一个参数是要执行的SQL语句,第二个参数是要传递给SQL语句的参数,执行结果不返回值。
  4. 第四行代码用于查询某个表的行数据并映射到单个变量中,其中Table方法指定要查询的表,Where方法指定查询条件,Select方法指定返回的列,而RowScan方法用于将查询结果映射到变量中。
  5. 第五行代码使用了sql.Namedmap[string]interface{}两种方式来传递参数,其中Named方式是通过sql.Named类型来指定参数名和值,而map方式是通过键值对来指定参数名和值。与之类似的是第六行代码,但是使用了结构体的方式。
  6. 第七行代码与第一、二行代码类似,但是查询的SQL语句使用了多个参数。

Gen 代码生成/Raw SQL - Gen

type Query interface {
    FindUser(id int32, name string, age int)(gen.T, err)
}

g := gen.NewGenerator(gen.Config{
    OutPath: "../dal/query",
})
g.ApplyBasic(model.User{})
g.ApplyInterface(func(query method.Query) {}, model.User{})
g.Execute()

user, err := query.User.FindUser(10, "jinzhu", 16)

定义了 Query 接口,接口里只有一个方法 FindUser,该方法接受三个参数:idnameage,返回两个值:gen.T 类型和 err 类型。

新建一个 Generator 对象,指定其输出路径为 "../dal/query"

调用 Generator 对象的 ApplyBasic 方法,传入 model.User{},这意味着我们要使用该对象的基本方法。

调用 Generator 对象的 ApplyInterface 方法,传入一个匿名函数。该匿名函数接收一个 method.Query 类型的参数,该参数是一个结构体,表示一个基本的查询方法。这个函数在执行时会根据 model.User{} 的字段生成相应的查询方法。

调用 Generator 对象的 Execute 方法,根据前面的设置生成代码文件到指定的输出路径。

最后执行查询代码,调用上述生成的查询方法,查询 id10name"jinzhu"age16 的用户信息,并将查询结果赋值给 user 变量,如果有错误,则将 err 赋值给 err 变量。

安全问题

db.Cretae(User{Name: userInput})
db.Model(user).Update("name",userInput)
db.Where(User{Name: userInput}).First(&user)
db.Where("name = ?", userInput).First(&user)

sql := fmt.Sprintf("name = %v",userInput)
db.Where(sql).First(&user)
db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)
db.Model(&user).Pluck("name; drop table users;", &names)
db.Group("name; drop table users;").First(&user)
db.Group("name").Having("1 = 1; drop table users;").First(&user)
db.Raw("select name from users; drop table users;").First(&user)
db.Exec("select name from users; drop table users;")

db是一个数据库连接对象,User是一个结构体,包含了名为Name的属性。

  • db.Create:创建一个新的User对象,将其Name属性设置为输入的userInput值;
  • db.Model.Update:在db连接的User表中,找到Name属性为user.Name的记录,将其Name属性更新为输入的userInput值;
  • db.Where.First:在db连接的User表中,查找满足Name属性为输入的userInput值的记录,并将其中的第一条记录赋值给user对象;
  • db.Select.First:在db连接的User表中,查询所有记录的Name属性,并将其中的第一条记录赋值给user对象;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险;
  • db.Distinct.First:与上一个用法相同,只是使用了Distinct关键字去重;
  • db.Model.Pluck:在db连接的User表中,查询所有记录的Name属性,并将结果存储到names变量中;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险;
  • db.Group.First:在db连接的User表中,按照Name属性进行分组,再找到第一条记录,赋值给user对象;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险;
  • db.Group.Having.First:在db连接的User表中,按照Name属性进行分组,再查找满足1=1条件的记录,并将其中的第一条记录赋值给user对象;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险;
  • db.Raw.First:在db连接的User表中使用原生SQL语句进行查询,并将其中的第一条记录赋值给user对象;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险;
  • db.Exec:在db连接的User表中使用原生SQL语句进行查询,并将结果返回给调用者;其中,查询语句中包含了一个恶意的SQL注入代码,可能会造成数据库被破坏的风险。