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的基本数据类型、实现了 Scanner
和 Valuer
借口的自定义借口及指针或别名组成。
举个例子,用户模型中包含了 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 提供了 First
、Take
、Last
方法,用来单个检索对象。并且如果没有查询到记录的时候,会返回 ErrRecordNotFound
的错误,我们可以使用 errors.Is(result.Error, gorm.ErrRecordNotFound )
来检查该错误然后做后续处理。
接受返回值类型
Map
map 的方式是没有指定模型的,所以类似 First
、Last
需要指针和模型的时候,就必须指定对应模型才可以使用。
//两种实体化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
的主键查找,也可以使用 Last
、Find
的查找方式
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)
内联条件
查询条件可以内联到 First
和 Find
之类的语句之中
`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
的操作可以用 Select
和 Omit
先进行选择
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)