Bootstrap

gorm的学习笔记

数据库链接

文件引入

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

基础链接

func main(){
    dsn := "user:pwd@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db,err := gorm.Open(mysql.Open(dsn),&gorm.Config{})
}

自定义配置链接

func main(){
    db,err := gorm.Open(mysql.New(mysql.Config{
        DSN := "user:pwd@tcp(127.0.0.1:3306)/dbname?
        DefaultStringSize: 256, 
        // string 类型字段的默认长度
  		DisableDatetimePrecision: true, 
        // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
  		DontSupportRenameIndex: true, 
        // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  		DontSupportRenameColumn: true, 
        // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
  		SkipInitializeWithVersion: false, 
        // 根据当前 MySQL 版本自动配置
    }))
}

配置文件源码

type Config struct {
	DriverName                string
	ServerVersion             string
	DSN                       string
	Conn                      gorm.ConnPool
	SkipInitializeWithVersion bool
	DefaultStringSize         uint
	DefaultDatetimePrecision  *int
	DisableDatetimePrecision  bool
	DontSupportRenameIndex    bool
	DontSupportRenameColumn   bool
	DontSupportForShareClause bool
}

现有数据库链接

import (
  "database/sql"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
//获取已有dsn
sqlDB, err := sql.Open("mysql", "mydb_dsn")
gormDB, err := gorm.Open(mysql.New(mysql.Config{
  Conn: sqlDB,
}), &gorm.Config{})

重要配置

db, err := gorm.Open(mysql.Open(`dsn`), &gorm.Config{
  	SkipDefaultTransaction: false,	//跳过事件
    NamingStrategy:schema.NamingStrategy{
        TablePrefix:"t_",		//表明前缀 `User`表名为 `t_user`
        SingularTable: true,	//使用单数表名,否则加s
    },
    DisableForeignKeyConstraintWhenMigrating:true,//逻辑外键
})

设置完后,迁移会按照新的设置更新一个新的表。

模型和标签

在模型创建的时候,一般分配一个专门的文件存放,方便后期的修改与查询。

模型定义

模型是标准的 struct ,即结构体。由GO的基本数据类型、实现了 ScannerValuer 借口的自定义借口及指针或别名组成。

举个例子,用户模型中包含了 id、name、email、age 四个元素。

type User struct{
  ID           uint
  Name         string
  Email        *string
  Age          uint8
}

gorm.Model与嵌入结构体

不过我们创建数据记录的时候,需要知道 创建时间、更新时间、删除时间

gorm 中由一个事先定义好的结构体,包括键值 id 以及以上三项。

// gorm.Model
type Model struct{
    ID			unit			`gorm:"primarKey"`
    CreatedAt	time.Time	
    UpdatedAt	time.Time
    DeletedAt	gorm.DeletedAt 	`gorm:"index"`
}

所以我们重新定义一次 User ,让他包含了主键、创建时间、删除时间、更新时间的元素。

//User
type User struct{
    gorm.Model
    Name string
    Age	 uint
}
//相当于
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

可以看到我们直接写入了一个结构体gorm.Model, 这种写法属于匿名字段,GORM 会将其字段包含在父结构体之中。

那么我们也可以用正常的结构体字段去写入,不过我们需要通过标签 embedded 将其嵌入

type Author struct{
    Name string
    Email string
}
type Blog struct{
    ID	int
    Author	Author `grom:"embedded"`
    Upvotes int32
}

并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:

type Blog struct {
    ID      int  
    Author  Author `gorm:"embedded;embeddedPrefix:author_"`  
    Upvotes int32
}
// 等效于
type Blog struct {  
    ID          int64    
    AuthorName  string   
    AuthorEmail string  
    Upvotes     int32
}

对于嵌入结构体的两种方式,我个人认为匿名字段的方式更为简便,方便于书写编排。

不过标签嵌入也有增加前缀的功能,在一些地方也是不错选择。

标签

在上述 gorm.Model 的定义和标签嵌入结构体的方法中,我们都用到了标签。

标签是用来指定元素的一些属性,如 column 指定列名、 type 指定列数据类型、 primaryKey 指定主键等,具体写法如下:

type User struct{
    //指定 ID 为主键
    ID		`gorm:"primaryKey"`
    //指定默认值为 1
    Age		`grom:"default:1"`
    //嵌入结构体
    School	`gorm:"embedded"`
    //指定值不可为空
    Email	`gorm:"not null"`
    //指定db列名,不使用则为元素名称
    Time	`gorm:"column:my_time"`
    //指定迁移后注释
    Birthday`grom:"comment:年龄"`
}

CRUD

创建

User的定义

type User struct{
    gorm.Model
    Name string
    Age	 int
    birthday string
}

创建的基础指令

Create 创建会返回一个 *db 我们可以接收,我们可以通过 dbres.Error 去判断我们是否创建成功。

dbres := db.Create(&User)
单独创建
//直接创建
db.Create(&User{Name:"go",Age:"18"})
//用数据指针创建
user := User{Name:"go",Age:"18"}
result := db.Create(&user)

user.ID	//插入后user的ID会自动生成,可以返回
//Create会返回一个错误接口和一个成功插入条数
result.Error		
result.RowsAffected
指定字段

创建记录并更新给出的字段

//这里就会选择以下三点创建,不包括更新时间和生日
db.Select("Name","Age","CreatedAt").Create(&user)
// insert into `users` (`name`,`age`,created_at) values ("go","18","`创建日期`")`
跳过字段

创建并忽略传递过去的字段值

//忽略以下三点,传递更新时间和生日
db.Omit("Name","Age","CreatedAt").Create(&user)
// insert int `users`(`birthday`,`updated_at`) values ("`生日日期`","`更新日期`")
批量创建

将一个 slice 传递给 Create 方法,GORM 将生成单独一条SQL语句插入数据并返回主键值,钩子方法一样会调用。

var users = []User{{Name:"go1"},{Name:"go2"},{Name:"go3"}}
db.Create(&users)

for _, user := range users{
    user.ID	//1,2,3
}

查询

GORM 提供了 FirstTakeLast 方法,用来单个检索对象。并且如果没有查询到记录的时候,会返回 ErrRecordNotFound 的错误,我们可以使用 errors.Is(result.Error, gorm.ErrRecordNotFound ) 来检查该错误然后做后续处理。

接受返回值类型

Map

map 的方式是没有指定模型的,所以类似 FirstLast 需要指针和模型的时候,就必须指定对应模型才可以使用。

//两种实体化map写法
var result make(map[string]interface{})
result := map[string]interface{}
//
db.Model(&User{}).First(&result)

结构体

我们常用的接受数据形式,方便于查找创建

var user User
db.Model(&User).First(&user)
检索单个对象

查询主键排序的第一条

//完整语句
db.Model(&User).First(&user)
//结构体指针简写
db.First(&user)

语句 First 用来从主键排序第一位开始查找,我们可以添加特定条件

//查找主键为 12 的记录
db.First(&user, 12)
//条件查找第一个 code = "uid202" 的记录
db.First(&user, "code = ?", "uid202")

这里的条件查找方式属于 内联条件

查询第一条

result := map[string]interface{}{}
db.Table("users").Take(&result)

查询主键排序最后一条

//完整
db.Model(&User).Last(&user)
//结构体简写
db.Last(&user)
条件查询

条件查找的指令为 Where ,我们在之前的 First 中也实现过条件查找,不过 Where 运用更广泛,可以实现 First 的主键查找,也可以使用 LastFind 的查找方式

string类型的条件语句

var user User
var users []User
//string条件单记录查找
db.Where("name = ?", "jinzhu").First(&user)
//string条件多记录查找
db.Where("name <> ?", "jinzhu").Find(&users)

// IN 方式,即查找多种匹配记录
db.Where("name IN ?",[]string{"jinzhu","jinzhu 2"}).Find(&users)

// Like 方式, 查找有类似子串的数据
db.Where("name LIKE ?", "%jin%").Find(&users)

// AND 方式,即多条件
db.Where("name = ? AND age >= ?","jinzhu","22").Find(&users)

// Time 时间查找
db.Where("updated_at > ?", lastWeek).FInd(&users)

// BETWEEN 范围时间查找
db.Where("created_at BETWEEN ? AND ?",lastWeek, today).Find(&users)

Struct & Map 条件查找

利用 结构体查找的时候,GORM 只会查找非 0 字段,即字段值为 false 或其他 零值 , 就不会构成查询条件。

若要查找零值,可以使用 Map 条件查询,或者使用上述 String 方式

//结构体条件查找
db.Where(&User{Name:"jinzhu",Age: 20}).First(&user)

//Map 条件查找
db.Where(map[string]interface{}{"name":"jinzhu","age":20}).Find(&users))

//主键切片查找
db.Where([]int64{20,21,22}).Find(&users)
Not 条件

Where 类似,不过 Where 代表指定 该条件, Not 指定 不是 该条件。也可以和 Where 构成 需要条件1 不需要条件2 的语句。

// String
db.Not("name = ?","jinzhu").First(&user)

// Map
db.NOt(map[string]interface{}{"name":[]string{"jinzhu","jinzhu 2"}}).Find(&users)

//Struct
db.Not(User{Name:"jinzhu",Age:18}).First(&user)

//主键切片记录
db.Not([]int{1,2,3}).First(&user)
Or 条件

可以和 Where 构成多种条件

//String
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)

// Struct
db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
内联条件

查询条件可以内联到 FirstFind 之类的语句之中

`db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';// Structdb.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);// Mapdb.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);`
选择字段查找

使用类似 mysql 语句 select 来执行

db.Select("name","age").Find(&users)

db.Select([]string{"name","age"}).Find(&uesrs)

db.Table("users").Select("COALESCE(age,?)",42).Rows()

高级查询

智能选择字段

GORM 允许通过 Select 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段,例如:

type User struct {
  ID     uint
  Name   string
  Age    int
  Gender string
  // 假设后面还有几百个字段...
}

type APIUser struct {
  ID   uint
  Name string
}

// 查询时会自动选择 `id`, `name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
//这样的输出会更加的简洁
//相对于 Select 用 原模型接收,会有少零值数据输出
fmt.Println(APIUser)

更新

所有的更新和查询的写法如出一辙,例如

db.Model(&User).update("name","update this")

并且也可以使用 where 等条件语句。

db.Model(&User).Where("name = ?","oldinf").updates(User{Name:"new",Age: 18})

上述字段更新都是更新列,多个或者单个。那么我们有时候需要对一个记录更新,我们就需要先找到该记录。

那么如何找到,就是利用查询指令先找后更。例如

db.Model(&User).First(&user).update("name","new")

下述分为三个更新语句细说

Update

只更新所选择字段

我们可以指定一个列去更新

//更新 name 字段全部为hello
db.Model(&User).update("name","hello")

当然这个也可以被 Select 语句所代替

// 选择 name age 字段更新
db.Model(&User).Select("name","age").updates(User{Name:"hello",Age:0,gender:"male"})
updates

更新所有字段,不过结构体的零值不参与更新。

并且 updates 的操作可以用 SelectOmit 先进行选择

updates更新方式有 结构体、Map 两种方式。

//结构体
db.Model(&User).updates(User{Name:"hello", Age: 18})
//Map
db.Model(&User).updates(map[string]interface{}{"name":"hello","age":18})

上述更新值没有零值如果有零值,那么该值不参与更新

// 原数据  name : "old"  	age: 18
db.Model(&User).updates(User{Name:"hello",Age:0})
// 更新后	name : "hello"	age: 18

可以看出更新后 age 的值没有改变,担如果使用 Select 的时候,会选择 0 值

Save

更新所有字段,包括零值。(不常用)

save的更新有一点不一样,前两种方式可以直接更改,但是 save 需要主键才可以实现更新,否则会创建一条新的记录。

dbres := db.Where("name = ?","newthing").Find(&users)
for k := range users{
    users[k].Age = 18
}
dbres.Save(&users)
阻止全局更新

如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误

对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:

db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause

db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu" WHERE 1=1

db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"

删除

软删除

软删除即删除之后,会留下 deleted_at 删除时间,但是数据任然会留在数据库中。

//删除 name 为空的条目
db.Where("name = ?","").Delete(&users)
Unscoped()

强制删除

Delete 一同使用,例如

//永久删除指定条目
db.Unscoped().Where("name = ?","").Delete(&user)

查找软删除记录

//
db.Unscoped().Where("age = 20").Find(&users)
;