Gorm框架入门 | 青训营笔记

623 阅读7分钟

这是我参与「第五届青训营 」笔记创作活动的第 3 天 。

一、主要内容:

这篇笔记记录了Gorm框架的基本用法

go get gorm.io/driver/mysql

go get gorm.io/gorm

二、详细知识点:

1.模型的概念

1)模型映射

数据库里面的表映射到Go语言中的结构,这个结构就叫做模型。 例如mysql里面的表:

CREATE TABLE goods(
    id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id,商品id',
    name varchar(30) NOT NULL COMMENT '商品名',
    price decimal(10,2) unsigned NOT NULL COMMENT '商品价格',
    type_id int(10) unsigned NOT NULL COMMENT '商品类型id',
    createtime int(10) NOT NULL DEFAULT 0 COMMENT '创建时间',
    PRIMARY KEY(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

翻译为模型后如下:

type Good struct{
    Id int            `gorm:"AUTO_INCREMENT"`
    Name string       `gorm:"type:varchar(25)"`//可以不用
    Price float64     
    TypeId int        `gorm:"size:10"`
    CreateTime int64  `gorm:"size:10; column:createtime"`
}
  • 默认gorm对struct字段名使用SnakeCase命名风格,需要全部转化成小写字母,并且单词之间使用下划线分割;
  • 默认情况下使用ID作为主键;
  • 使用结构体SnakeCase风格的附属形式作为表名;
  • 使用CreateAt、UpdateAt字段追踪创建、更新时间

2)标签定义

标签定义可以对模型中的字段进行翻译时的解释。 常用标签: column 指定列名 primaryKey 指定主键

3)表名映射

如果不想用默认的表名格式,可以自行使用下列代码定义

  • 静态表名
func (结构名) TableName() string {
    return "你想定义的表名"
}
  • 动态表名(临时表名) db.Table("临时表名")

4)结构体嵌套

需要在标签处加上"embedded"表示是嵌套结构体(Q:那么继承需要吗?)

good Good `gorm:"embedded"`

2.数据库连接

1)dsn参数

gorm库使用dsn作为连接数据库的参数,格式为:

//username 数据库账号
//password 数据库密码
//host     数据库连接地址,IP或者域名
//port     数据库端口
//Dbname   数据库名
username:password@tcp(host:port)/Dbname?charset=utf8mb4&parseTime=True&loc=Local&timeout=10

//填上参数后的例子
//username = root
//password = 123456
//host     = localhost
//port     = 3306
//Dbname   = gorm

//后面K/V值对参数含义为:
//    charset=utf8mb4
//    parseTime=True 支持把数据库datatime和date类型转换为golang的time.Time类型
//    loc=local 使用系统本地时区
//    timeout=10 设置10秒后连接超时
//    readTimeout - 读超时时间,0代表不限制
//    writeTimeout - 写超时时间,0代表不限制

//username 数据库账号
//password 数据库密码
//host     数据库连接地址,IP或者域名
//port     数据库端口
//Dbname   数据库名
username:password@tcp(host:port)/Dbname?charset=utf8mb4&parseTime=True&loc=Local&timeout=10

//填上参数后的例子
//username = root
//password = 123456
//host     = localhost
//port     = 3306
//Dbname   = gorm
//后面K/V值对参数含义为:
//    charset=utf8mb4
//    parseTime=True 支持把数据库datatime和date类型转换为golang的time.Time类型
//    loc=local 使用系统本地时区
//    timeout=10 设置10秒后连接超时
//    readTimeout - 读超时时间,0代表不限制
//    writeTimeout - 写超时时间,0代表不限制

2)连接数据库

dsn := root:123456@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local&timeout=10
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
  • 调试模式: db.Debug()
  • 连接池配置
db.DB().SetMaxOpenConns(100) //设置数据库连接池最大连接数
db.DB().SetMaxIdleConns(20)  //设置数据库连接池最大空闲连接数连接数,
                             //sql任务需要执行的连接数大于20的连接会被连接池关闭

3.数据的增删改查

1)插入数据

①普通插入

初始化模型类型的一个变量后调用

db.Create(&user)
//调用函数后
user.ID             //自动加上的主键
result.Error        //error
result.RowsAffected //插入记录的条数

②指定字段创建

db.Select("username","password").Create(&user)

③忽略字段

db.Omit("username").Create(&user)

④批量插入

var users = []User{{Username: "zhangsan"},{Username: "lisi"},{Username: "zhaoliu"}}
db.Create(&users)

或者 db.CreateInBatches(&users,2) //一批次2条

⑤使用map创建

db.Model(&User{}).Create([]map[string]interface{}{
    {"Name": "lisi", "Age": 18},
    {"Name": "zhangsan", "Age": 20},
})

需要注意的是map插入并不会自动填充主键(Q:那么自己也不填充主键会报错吗?)

⑥sql表达式

一般用于需要使用sql中的加密方式

db.Model(&User{}).Create(map[string]interface{}){
    "username": "liso",
    "password": clause.Expr{SQL: "md5(?)", Vars: []interface{}{"123456"}},
}

⑦使用原生sql语句

db.Exec("inser into users (username,password,createtime) values (?,?,?)", user.Username, user.Password, user.CreateTime)

可以在值对应的地方使用sql加密函数,如在user.Password处写 "md5('123456')"

2)更新数据

①Save

Save函数会将所有字段都更新一次,效率较低

var good Good
db.Where("id=?",3).Take(&good)
good.Price = 5.2
db.Save(&good) //此时的Save是更新
//或者
good := {
    //...
    //主键id没有出现过
}
db.Save(&good) //此时的Save是插入

②Update

var good Good
db.Where("id=?",3).Take(&good)
db.Model(&good).Update("price",5.2) //Update(列名,值)
或者
db.Model(&Good{}).Where("id=?",3).Update("price",5.2) //Update(列名,值)
//更新多列为
Updates(Good{
    price: 5.2,
    name: "栗子",
})

③指定字段更新

依然是Select()和Omit()配合使用即可

④表达式更新

用Update函数,第二个参数为gorm.Expr("表达式") 其中表达式可以出现列名 db.Model(&good).Update("price",gorm.Expr("price+1")) //Update(列名,值)

3)删除数据

①主键删除

db.Delete(&Good{}, 1)

②查询删除

db.Where(...).Delete(&Good{})

4)查询数据

①查询行

  • Find() 查找多个,一般使用切片接受,没有拿出东西不会报错!
  • Limit() 限制拿出多少条数据
  • Take() 等效于 First() ,都是拿出第一个; 如果没有拿出东西会报错Not Found
  • Last() 拿出最后一个

②查询列

var names []string
db.Model(&Good{}).Pluck("要查询的列名",&names)

③条件查询

  • Where(query,args) query中可以为in,not in等各种sql语句中使用的查询条件,遇到参数用 ? 占位,在第二个参数处填充。
  • Select() 设置select子句,返回指定的字段;Select()中可以使用聚合函数,如:
var total int 
db.Model(&Good{}).Select("count(*) as total").Pluck("total",&total)

④Order排序和分页分组

分页
var goods []Good
db.Order("id desc").Limit(10).Offset(10).Find(&goods)

上述示例为:按照id降序,每页10条数据,取第十页的数据

分组
  • Group("分组的字段名")
  • Having("条件")
  • 返回结果可以用Find()函数,这就要求传入Find的参数结构必须与表模型一致;
  • 可以使用Scan(),只要参数结构的字段与前面Select()拿出来的字段完全一致即可
  • Group()函数必须搭配Select函数一起使用
复杂查询

使用sql语句

sql := "sql语句"
db.Raw(sql,语句中占位的参数).Scan(&result)

⑤Count()

Count()返回匹配查询的行数 db.Model(Good{}).Count(&total)

5)Session会话

本内容老师在讲课时没有重点提及,所以这里就不研究了,放一个网站以后需要时再参考。 Gorm Session——迹忆客 (jiyik.com)

6)事务

①自动事务

err := db.Transaction(func(tx \*gorm.DB) error{
    //进行db操作,但是要用tx
    //如果出现错误,就return err,会自动回滚
    //return nil就会提交
})
  • 自动事务的嵌套事务就是在func里面再次调用一次Transaction()

②手动事务

tx := db.Begin()
//执行操作...
if err != nil {
    tx.Rollback()
} else {
    tx.Commit()
}

③保存点

tx.SavePoint("保存点名称")
//...执行各种操作
tx.Rollbackto("保存点")
tx.Commit()

7)Hook

  • Hook是在创建、查询、更新、删除等操作之前、之后调用的函数;
  • 如果任何钩子返回错误,gorm将停止后续操作并回滚事务 函数签名为 func(调用的表模型)(tx *gorm.DB) error 执行的顺序为
// 开始事务
BeforeSave
BeforeCreate/BeforeUpdate
// 操作,保存
AfterCreate/AfterUpdate/AfterFind(查询到多少条就出发多少次)
AfterSave
// 提交或回滚事务

8)gorm性能提高

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    SkipfaultTransaction: true,
    PrepareStmt: true
})
  • 关闭默认事务
  • 缓存预编译

9)gorm扩展

其他

快速开始

在main文件里面添加如下代码再go mod tidy就可以一键导入gorm框架

import (
   "fmt"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
)

func main(){
   dsn := "root:123456@tcp(localhost:3306)/mybase?charset=utf8mb4&parseTime=True&loc=Local"
   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
   if err != nil {
      fmt.Println("连接失败", err)
      return
   } else {
      fmt.Println("连接成功")
   }
}

先初始化数据库

在这里补充一个在代码中新建表的方法,之前都是在数据库里面已有的表操作的,比较麻烦。

先贴上官方文档:迁移 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

常用的Migrator方法

  • 创建表(User是在go语言中定义的结构体)
m:=db.Migrator()
m.CreateTable(&User{})
  • 是否存在表
//以结构体的方式查
m.HasTable(&User{})
//以表名的方式查
fmt.Println(m.HasTable("gva_users"))
  • 删除表
err = m.DropTable(&User{})
if err != nil {
	fmt.Println(err)
}
err = m.DropTable("gva_users")
if err != nil {
	fmt.Println(err)
}
if m.HasTable(&User{}) {
	m.DropTable(&User{})
} else {
	m.CreateTable(&User{})
}