Gorm学习笔记

本文详细介绍了GORM库在Go语言中的使用,涵盖了模型定义、数据插入(包括Create、Save、Omit、批量插入、钩子方法)、查询(First、Last、Find、Where、Join、Scan等)、更新(Save、Update、Updates、Hook)、删除(普通删除、钩子、批量删除)以及事务处理。同时,文章讨论了字段类型选择、SQL表达式、原生SQL语句和日志功能等关键知识点。
摘要由CSDN通过智能技术生成

文章目录

一. 模型定义

1. 常见的字段类型
type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

上图是不带tag标签的结构体, 这里我有几个疑问:

Q: string 和 *string 有什么区别?
A: 假设在插入Name时没有填写任何Name, 那么MySQL中会存入""空字符串; 如果Email没有填写任何值, 而MySQL中的值是(NULL), 这就是两者的差距; 因为Golang默认值的原因, string型、int型的默认值不是nil(也不能为nil), 所以需要借助指针来为nil, 与MySQL中的(NULL)映射, 另外 *time.Time 也是如此。

Q: sql.NullString和普通的string有什么区别?
A: MySQL中varchar的值为(NULL), 如果结构体中仅是string, 那么该如何映射是好呢? string又不能映射成nil, 就需要sql.NullString, 其结构体如下, 它对(NULL)做了支持。其他的sql.NullBool…都是相同道理。当然还有一种办法, 当不填写值时, 不以go中的类型默认值进行填充, 可以使用 default:(-) 这个gorm标签。

type NullString struct {
	String string
	Valid  bool // 如果为true, 则代表MySQL字段对应的结构体String字段非空
}

Q: 针对上一个提问, *string 也可以做同样的事情, sql.NullString有什么特殊之处吗?
A: https://stackoverflow.com/questions/40092155/difference-between-string-and-sql-nullstring 查询了资料, 两者确实都能达到相同的效果, 但是针对MySQL来说是没有’nil’这个概念的, 它只有(NULL), 所以标准姿势应该是使用sql.NullString, 因为这里是与MySQL打交道的地方。

2. gorm标签
标签名说明
column指定 db 列名
type列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
size指定列大小,例如:size:256
primaryKey指定列为主键
unique指定列为唯一
default指定列的默认值
precision指定列的精度
scale指定列大小
not null指定列为 NOT NULL
autoIncrement指定列为自动增长
autoIncrementIncrement自动步长,控制连续记录之间的间隔
embedded嵌套字段
embeddedPrefix嵌入字段的列名前缀
autoCreateTime创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndexindex 相同,但创建的是唯一索引
check创建检查约束,例如 check:age > 13,查看 约束 获取详情
<-设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 (没权限时写入nil值)
->设置字段读的权限,->:false 无读权限
-忽略该字段,- 无读写权限
comment迁移时为字段添加注释

tag的常用标签在MySQL建表时指定好就可以, 大部分都不会用到, 一般为了项目的可读性可以将tag补满。

3. 一些规范
ID int // 默认的主键
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt

上面四个字段是gorm规范的字段, 其会自动对它们进行处理。

二. insert类型语句

1. Create插入
user := model.User{Username: "姓名", Password: "666", Age: 18, Sex: 0}
result := db.Create(&user)
user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

朴素的插入, 如果结构体没有为一个字段填写值, 那么在插入时会已类型的默认值插入。

2. Save插入, 若主键存在则更新所有字段

具体查看Update类型的第一条 Save 语句。

3. Omit插入时忽略某些字段
db.Omit("Password").Create(&user) // 插入时忽略Password字段
4. 批量插入
db.Create(&userArr) // 插入对象数组即可
db.CreateInBatches(users, 每批数量) // 如果数量太多, 还可以分批插入
5. 对象操作钩子(拦截器)
BeforeSave, BeforeCreate, AfterSave, AfterCreate // 四个钩子函数, 都会开启事务
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
	//return errors.New("AfterCreate,可知是插入后执行,返回error会进行事务回滚")
	return nil
}
// 当然, 如果你想针对某次操作session忽略钩子函数, 可以做出一下操作
db.Session(&gorm.Session{SkipHooks: true}).Create(&user)
6. 根据Map创建
// 图个方便, 感觉不是很规范
db.Model(&User{}).Create(map[string]interface{}{
	{"Name": "jinzhu_1", "Age": 18},
  	{"Name": "jinzhu_2", "Age": 20},
})
7. Java中的TypeHandler

需求: MySQL中sex是int类型仅有0/1,代表男/女, 而Golang中是string类型, 如何将它们做个转换?

// 方法一: 对结构体的字段类型进行处理

// 替换成sex的string类型
type SexString string

// GormDataType MySQL中的数据类型
func (s *SexString) GormDataType() string  {
   return "int"
}

// Scan 读出时转换
func (s *SexString) Scan(v interface{}) error {
   return nil
}

// GormValue 写入时转换
func (s SexString) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
   var sexNum int
   if s == "男" {
      sexNum = 1
   } else{
      sexNum = 0
   }
   return clause.Expr{
      SQL:  "?", // 当然, 这里可以替换复杂的sql语句, 比如一些聚合函数 sum(?), POINT类型转换GeomFromText('POINT(?)') 等等
      // Vars的值将替换上方的?
      Vars: []interface{}{sexNum},
   }
}

// 方法二: 执行时直接处理

db.Model(&user).Create(map[string]interface{}{
    "username": "你好",
    "sex": clause.Expr{SQL: "?", Vars: []interface{}{
        // 性别字符串转int
        func(string2 model.SexString) int{
            if string2 == "男" {
                return 1
            }
            return 0
        }("女"),
    }},
})
8. clause.Expr{}表达式

上方的方法二中, 有用到此表达式。 它可以编译SQL语句, 'SQL’字段 为原SQL语句, ‘Vars’ 字段可以对SQL进行填充。

9.唯一键或主键冲突的更新
// 在冲突时,什么都不做
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

// 在username字段冲突时,将role字段更新为'手动定义的role'
db.Clauses(clause.OnConflict{
   Columns:   []clause.Column{{Name: "username"}},
   // 这里是'Assignments', 译为'任务', 表明需自己写表达式
   DoUpdates: clause.Assignments(map[string]interface{}{"role": "手动定义的role"}),
}).Create(&user)

// 在username字段冲突时,更新'password'和'age'字段
db.Clauses(clause.OnConflict{
   Columns:   []clause.Column{{Name: "username"}},
    // 这里是'Assignments', 译为'赋值列', 表明指定更新列(大小写尽量与MySQL对齐, 实际不对齐也没影响)
   DoUpdates: clause.AssignmentColumns([]string{"password", "age"}),
}).Create(&user)

三. select类型语句

1. First & Last & Take
// &user的主键如果存在, 那会作为查询条件
result := db.First(&user) // 按照主键排序且查一条 order by id limit 1
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil
// 注意: 这一条语句还会再执行一次, 返回所有行数据
rows,_ := result.Rows()
for rows.Next(){
    user := &model.User{}
    err = db.ScanRows(rows, user)
}

db.Last(&user) // 主键排序, 最后一条

db.Take(&user) // 查一条, 但不排序

对于没有主键的表, First 和 Last 会排序表的第一个字段; 查询到的对象会放入&user中。

2. Find (无limit查询)

同上, 会返回

3. Where
db.Where("username = ?", "你好").Find(&user)
// Struct
db.Where(&User{Username: "jinzhu", Age: 20}).First(&user)
// Struct 结构体后面又跟了 'age' 参数, 表明只需 age 作为查询条件, username忽略。
db.Where(&User{Username: "jinzhu", Age: 20}, 'age').First(&user)
// Map
db.Where(map[string]interface{}{"id": []int{1,2,3,4,5}}).Find(&userArr) // in (1,2,3,4,5)

注意: Stuct 查询时会忽略类型默认值作为查询条件, 例如: age=0 的查询条件; 你可以使用 map 来查询类型默认值。

4. First Last Find Take 查询条件
db.First(&user, "id = ? and username = ?", 6, "你好")
db.Find(&users, User{Age: 20})
db.Find(&users, map[string]interface{}{"age": 20}) // 还是很多样化的
5. Not
db.Not("username = ?", "jinzhu").First(&user) // struct 和 map 的形式同上
6. Or

跟在where后面

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users) // struct 和 map 的形式同上
7. 查询指定字段
// 可以使用函数, 例如 sum(age)
db.Select("username", "age").Find(&users)
8. Order
// 默认是asc, desc需要写进参数中
db.Order("age desc").Order("name").Find(&users)
9. Limit & Offset

一般用作分页

// SELECT * FROM users OFFSET 5 LIMIT 10;
db.Limit(10).Offset(5).Find(&users)
10. Group By & Having & Distinct

加上对应的函数即可(这三个函数顺序不用关心, 转成sql语句时gorm会帮忙调整, 尽量按顺序)。

11. Joins连接
// 连表查询
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

// joins函数内可以预编译SQL, 下面就
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "我是预编译的邮箱参数").Where("credit_cards.number = ?", "110").Find(&user)
12. Scan将行数据转为结构体
type Result struct {
  Name string
  Age  int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)
14. Raw 编写原生语句
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
15. 非 * 字段查询
// select `id`,`username`,`age`.... 将字段拼接, 不会仅仅有一个*号
db.Session(&gorm.Session{QueryFields: true}).First(&user)
// 注: 也可以进行全局设置

在MySQL层面会加快一些速度。

16. 锁
// 排他锁, 'for update'
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users)

// 针对 `users` 表的共享锁
db.Clauses(clause.Locking{
  Strength: "SHARE",
  Table: clause.Table{Name: clause.CurrentTable},
}).Find(&users)

// 排他锁, 有锁则不等待 SELECT * FROM `users` FOR UPDATE NOWAIT
db.Clauses(clause.Locking{
  Strength: "UPDATE",
  Options: "NOWAIT",
}).Find(&users)
17. in 多个字段
// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
18. 查询结果放入 Map
// select * from users
var results []map[string]interface{}
db.Table("users").Find(&results)
19. FirstOrInit 查询不到则返回默认值
// user 如果找不到-> User{Username: "non_existing"}
// 注意: FirstOrInit 的 第二个参数会被放入查询条件
// Where `user`本身的条件 and username = '我会被放入查询条件'
db.FirstOrInit(&user, User{Username: "我会被放入查询条件"})

// 不会作为查询条件 可以使用 Attrs() 函数, 查询不到时作为默认字段
db.Attrs(model.User{Age: 20}).FirstOrInit(&user)

// 不会作为查询条件 但不管有没有查到都会替换默认字段 Assign()函数
db.Assign(model.User{Age: 20}).FirstOrInit(&user)
20. FirstOrCreate 查不到就创建
// 默认的第二个参数, 会加入查询条件, 会作为插入值
大致同上,// Attrs()函数 不会作为查询条件, 只会作为插入值
大致同上,// Assign()函数 不会作为查询条件, 没查询到->进行插入操作 查询道理->进行更新操作(必定进行,即使库中数据相同)
大致同上,
21. 强制索引
// 三个函数
IgnoreIndex()// 忽略某某索引
UseIndex() // 建议使用某某索引, MySQL考虑采纳
ForceIndex() // 必须使用某某索引, MySQL必须采纳

// SELECT * FROM `users` USE INDEX (`idx_user_name`)
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
22. 迭代式获取数据
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
// 记得关闭rows()
defer rows.Close()

for rows.Next() {
    var user User
    // ScanRows 方法用于将一行记录扫描至结构体
    db.ScanRows(rows, &user)
}
23. FindInBatches 批量查询
// 每次查询100条, 并且结果会传入匿名函数
result := db.FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
  for _, result := range results {
    // 批量处理找到的记录
  }
  // 如果返回错误会终止后续批量操作
  return nil
})
24. 查询钩子(拦截器)
// 仅有一个 AfterFind
func (u *User) AfterFind(tx *gorm.DB) (err error) {
	if u.Age > 100 {
		u.Age = 18
	}
	return nil
}
25. Scope 封装常用查询(含分页内容)
// 成年人封装
func GrownUpAge(db *gorm.DB) *gorm.DB {
	return db.Where("age >= ?", 18)
}
// 放入封装函数
db.Scopes(GrownUpAge).Find(&user)

// 分页
func PageHelper(pageNum int, pageSize int) func(db *gorm.DB)  *gorm.DB{
	return func(db *gorm.DB) *gorm.DB {
		offset := (pageNum - 1) * pageSize
		return 	db.Offset(offset).Limit(pageSize)
	}
}
db.Scopes(PageHelper(1, 10)).Find(&user)
26. Count
var count int64
// SELECT count(1)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT COUNT(DISTINCT(`name`)) FROM `users`
db.Model(&User{}).Distinct("name").Count(&count)

四. Update类型语句

1. Save 根据主键更新, 没有则插入
// 会更新所有字段, 即使字段是零值, 比如createdAt也会进行更新, 可配合Omit()去忽略
db.Save(&user)
// 1. 进行更新, 如果返回的更新行数, 如果行数为0, 则进入第二条。
// 2. 行数为0, 执行Take仅根据id查询, 判断现在是否有数据了, 有 -> 进入第三条; 没有 -> 进入第四条
// 3. 有数据, 那么会重新进入1去进行修改。
// 4. 没有数据, 那么会执行Create创建。
2. Update 更新单列
// 切记, 如果使用了Model,且&user{}指定了主键, 那么主键也会被加入Where条件
// update users set name = 'hello' where active = true and id = 5;
db.Model(&User{id:6,age:18}).Where("active = ?", true).Update("name", "hello")
3. Updates 更新多列
// 根据结构体更新, 只会更新结构体的非零字段, 借助Model()参数的主键
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// Map更新
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
4. Updates + (Select & Omit) 限制更新字段
// Omit 忽略更新某些字段
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

// 结构体配select会更新 select('xx') 里面的参数
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})

// Select * 会更新所有字段(包括零值)
db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
5. 钩子方法(拦截器)
BeforeSave、BeforeUpdate、AfterSave、AfterUpdate
同insert语句, 四种拦截器
6. 特性: 自动阻止无Where更新
// 执行下面语句, 会抛出gorm.ErrMissingWhereClause错误, 因为是全局更新, 无where条件
db.Model(&User{}).Update("name", "jinzhu")
// 可以使用原生sql 或者 开启全局更新
db.Session(&gorm.Session{AllowGlobalUpdate: true})
7. 使用SQL表达式 gorm.Expr
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
8. 跳过钩子方法和时间追踪的Update
// 拦截器和时间统计 不生效
db.Model(&user).UpdateColumn("name", "hello")
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
9. 修改前检查字段是否有改变

Changed方法只能与 Update、Updates 方法一起使用,并且它只是检查 Model 对象字段的值与 Update、Updates 的值是否相等(不是查MySQL中的数据比较)。

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
   // 如果 Role 字段有变更, 返回错误,停止修改
   if tx.Statement.Changed("Role") {
      return errors.New("role not allowed to change")
   }
   // // 如果 Name 或 Role 字段有变更, 就修改 age 为 18
   if tx.Statement.Changed("Name", "Admin") {
      tx.Statement.SetColumn("Age", 18)
   }
   // 任意字段是否有变更
   if tx.Statement.Changed() {
   }
   return nil
}

五. Delete类型语句

1. Delete 普通删除
// 根据 userId 删除
db.Delete(&user)
// DELETE from emails where id = 10 AND name = "jinzhu";
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE FROM users WHERE id IN (1,2,3);
db.Delete(&users, []int{1,2,3})
2. 钩子方法(拦截器)
`BeforeDelete``AfterDelete`
// BeforeDelete(tx *gorm.DB) (err error)
// AfterDelete(tx *gorm.DB) (err error)
同前系列的钩子方法; 删除前, 删除后;
3. 批量删除 + 特性: 自动阻止无Where批量删除
// 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// 自动阻止无where条件的删除语句
db.Delete(&User{})
// 允许无Where删除
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
4. 返回被删除的数据
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
// 还可以设置返回结构体中指定字段
clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "age"}}}
5. 特性: 自动逻辑删除

模型包含了一个 gorm.deletedat 字段即可, 推荐字段名称 DeletedAt。

6. 查找被逻辑删除的数据
// 使用 Unscoped() 函数
db.Unscoped().Where("age = 20").Find(&users)
7. 永久删除(非逻辑删除)
// 使用 Unscoped() 函数
db.Unscoped().Delete(&order)

可知, 逻辑删除是靠 Scoped 封装函数实现的。

8. 时间戳形式软删除
// 结构体字段 `soft_delete.DeletedAt`类型即可 [gorm.io/plugin/soft_delete]
DeletedAt soft_delete.DeletedAt

Q: 软删除字段要加唯一索引怎么办?
A: 和另一个字段生成复合索引。

如果不想用时间记录逻辑删除, 只想要个 bool 记录, 那么可以如下:

// softDelete 标签加上即可, 0是未删除 1是删除
IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
// 查询未删除的所有记录
SELECT * FROM users WHERE is_del = 0;

六. 原生SQL语句

1. Raw 查询类
// 单单 Raw 不能单单进行执行, 需要 Scan 进行执行
db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)
2. Exec 执行类(删除, 修改…)
// Exec 执行
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
3. 查看函数生成的语句
// 并非原 sql, 参数会被 '?' 占领 
stmt := db.Session(&gorm.Session{DryRun: true}).First(&user, 1).Statement
stmt.SQL.String() //=> SELECT * FROM `users` WHERE `id` = ? ORDER BY `id`
stmt.Vars         //=> []interface{}{1}
// 原 SQL, 占位符已经被填入了
sqlStr := DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
  return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})

七. 事务

1. 特性: 默认开启事务(查询语句外)
// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{SkipDefaultTransaction: true,})
// 单次禁用
tx := db.Session(&gorm.Session{SkipDefaultTransaction: true})
2. 事务函数
db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }
  // 返回 nil 提交事务
  return nil
})
3. 嵌套事务
外层事务小事务1号小事务2号结果
成功失败失败两个小事务回滚, 外层事务提交
失败成功成功都回滚
成功失败成功仅小事务1号回滚

事务会进行传播, 相同级别不影响, 外层事务失败了会影响内层事务。

// 外层事务
db.Transaction(func(tx *gorm.DB) error {
   tx.Create(&user1)
   // 小事务一号
   tx.Transaction(func(tx2 *gorm.DB) error {
      tx2.Create(&user2)
      return errors.New("rollback user2") // Rollback user2
   })
   // 小事务二号
   tx.Transaction(func(tx2 *gorm.DB) error {
      tx2.Create(&user3)
      return nil
   })

   return nil
})
4. 手动开启提交
// 开始事务
tx := db.Begin()
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)
// 遇到错误时回滚事务
tx.Rollback()
// 否则,提交事务 tx.Commit().Error 可以查看错误
tx.Commit()
5. 中途存档和归档功能

MySQL自带的中途存档和归档功能

1、使用 SAVEPOINT identifier 来创建一个名为identifier的回滚点

2、ROLLBACK TO identifier,回滚到指定名称的SAVEPOINT,这里是identifier

3、 使用 RELEASE SAVEPOINT identifier 来释放删除保存点identifier

// 运用MySQL功能, 貌似没有Release释放存档的API
tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1

八. 操纵MySQL DDL语句

gorm提供了DDL层面的语句, 如 删除或创建表、修改字段、创建修改索引这些API。

具体略。

九. 一些小事项

1. 日志功能
newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
    logger.Config{
        SlowThreshold: time.Second,   // 慢 SQL 阈值
        LogLevel:      logger.Silent, // 日志级别
        // 忽略ErrRecordNotFound(记录未找到)错误, 一些API例如 First()
        IgnoreRecordNotFoundError: true,
        Colorful:      false,         // 禁用彩色打印
    },
)
2. 使用预编译SQL

对于一些频繁SQL可以使用预编译提高速率

// 全局
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  PrepareStmt: true,
})

// 单词
tx := db.Session(&gorm.Session{PrepareStmt: true})
3. 结构体的对应表名和字段
  1. 表名默认是 结构体的复数形式, 即 + ‘s’, 可以对结构体创建 TableName()方法, 返回表名。
  2. 默认主键是 ID, 可以使用 gorm:"primaryKey" 标签进行重新指认主键。
  3. 结构体字段映射到MySQL中的蛇形形式(create_at这种), 可以使用 gorm:"column:hello" 标签自定义MySQL中对应的字段名称。
4. *gorm.DB 方法间传递 K-V 键值

​ 1. 全局式 Set(k, v) & Get(k)

// 使用Set()函数
db.Set("my_value", myValue).Create(&User{})
// 在钩子方法或在其他地方可以使用Get()函数获取
myValue, ok := tx.Get("my_value")

​ 2. 一次Session式 InstanceSet(k, v) & InstanceGet(k)

同上, 仅函数作了改变;
5. SQL注入

一般不要用户输入的数据直接拼接到 SQL 中, 需要通过 ? 字符, 进行预编译生成。

// 不推荐
db.Where("name = " + name).First(&user)
// 推荐
db.Where("name =  ?", name).First(&user)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值