这是我参与[第五届青训营]伴学笔记创作活动的第3天
Gorm背景介绍
Gorm简单理解和数据库进行交互的框架 类似于java中的mybatis
阅读官方文档GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
官方文档是最好的学习资料,看不懂的时候可以再看视频
什么叫做csn?
参考 github.com/go-sql-driv… 获取详情
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func (p Product) TableName()string{
return "product" //定义表名
}
func main() {
db, err := gorm.Open("user:pass@tcp(ip:port)数据库名"?charset=utf8mb4&parseTime=True&loc=Local, &gorm.Config{})//连接数据库
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100}) //如果是单个字段,可以使用map,多个字段使用左边这种类型
// Read
var product Product
db.First(&product, 1) // 根据整形主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})//如果想更新零值字段可以使用这个
// Delete - 删除 product
db.Delete(&product, 1)
}
【注意】Gorm约定定义的实体类和数据库字段的对应关系
使用名为ID的字段作为主键
如果未定义表明,则使用结构体的蛇形负数作为表名
字段名的蛇形作为列名
插入数据
gorm是链式调用:返回的数据获取error,获取插入数据的主键
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct{
gorm.Model
ID uint `gorm:primarykey` //ID默认为主键
Name string `gorm:"column:name"` //默认实体类Name与数据库name对应
Age uint `gorm"column:age"` //默认实体类Name与数据库name对应
}
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
user := User{ID:1,Name: "limingjie", Age: 23}
result := db.Create(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回插入数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
}
打印结果
1
<nil>
1
数据库中结果
【注意】
(1)要带指针,否则会报错result := db.Create(&user)
(2)两者的区别
- user.ID 和操作数据相关
- result.Error result.RowsAffected 操作结果相关
(3)插入数据默认会带有created_at、updated_at以及deleted_at ,因此设计表时应添加这些字段,否则会报错
(4)如何设置默认值?
这个在插入数据的时候生效而在插入数据的时候不会生效
type User struct{
gorm.Model
ID uint `gorm:primarykey`
Name string `gorm:"default:galeone"`
Age uint `gorm:"default:18"`
}
当插入数据的时候如果没有定义值,就会自动插入默认数据
(5)type User struct对应的数据库为users,这是因为没有指定表名,那么如何修改为user呢?
func ( u User) TableName()string{
return "user" //定义表名
}
查询数据
Gorm提供了First、Take、Last方法来查询数据库,并且这三个都能会自动添加Limit 1的条件,如果没有找到数据,它会返回 ErrRecordNotFound 错误
检索单个对象
问题1:limit 1这个条件意味着什么?
只查询一条数据,以此为基础,不难理解如下代码
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
user := User{}
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
//SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.Last(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回查询数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
}
【注意】
(1)同样需要连接数据库
(2)默认是只查询一条数据,First会主键升序,Last会主键降序 ,Take未指定排序字段,但都只会有一条记录(和主键为1要区别开来)
(3)我按照以上代码进行查询,发现db.First和db.Last查询结果一致,这是为什么呢?
1
<nil>
1
当一个函数中利用结构体指针为目标同时调用Last与First时,查询结果相同,为先执行的方法结果。
如果要使用Last就把First注释掉
(4)如果未指定主键,则按照第一个字段进行升序、降序或者不排序
// 根据第一个字段排序
type Language struct {
Code string
Name string
}
DB.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
根据主键进行检索
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
【注意】find和first的区别?
查询多个对象
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//查询多条数据
users :=make([]*User,0)
// 1.获取全部记录 SELECT * FROM users;
result := db.Find(&users)
//2.条件查询
//SELECT * FROM users WHERE age > 10;
// result := db.Where("id > 1").Find(&users)
//SELECT * FROM users WHERE name Like '%ming%';
// result := db.Where("name Like ?","%ming%").Find(&users)
//SELECT * FROM users WHERE age In (18.23);
// result := db.Where("age In ?",[]int{18,23}).Find(&users)
//SELECT * FROM users WHERE name ='limingjie' AND age >= 22;
// result := db.Where("age >= ? AND name = ?",18,"limingjie").Find(&users)
//SELECT * FROM users WHERE name ='limingjie'
// result := db.Where(User{Name:"limingjie",Age:0}).Find(&users)
//SELECT * FROM users WHERE name ='limingjie' AND age = 0
result := db.Where(map[string]interface{}{"Name":"limingjie","Age":0}).Find(&users)
fmt.Println(users) // 返回操作数据的主键
fmt.Println(result.Error) // 返回error
fmt.Println(result.RowsAffected) // 返回操作记录的条数
【注意】使用结构体作为查询条件,查询非零值字段,这意味着当gorm中设置查询条件为0,false或其他零值将不会生效
如果想要生效:可以使用map来构建查询条件
更新数据
更新一列
// UPDATE users SET name='hello' WHERE id = 1;
db.Model(User{ID:1}).Update("name","lizhian")
// UPDATE users SET name='hello' WHERE age < 18;
db.Model(User{}).Where("age < ?",18).Update("name","lizhian")
【注意】
第一行代码中的User ID为查询条件 Model(User)表示指定更新表为user
第二行代码中使用where作为条件,那么User只充当指定表的作用,放一个空的User
更新多列
//UPDATE users SET name='limingjie', age=20, updated_at = '2013-11-17 21:34:10' WHERE id = 2;
db.Model(User{ID: 2}).Updates(User{Name: "limingjie", Age: 20})
///根据map
db.Model(User{ID: 2}).Updates(map[string]interface{}{"name": "hello", "age": 18})
//选定字段 UPDATE users SET name='limingjie', updated_at = '2013-11-17 21:34:10' WHERE id = 2;
db.Model(User{ID: 2}).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18})
【注意】课程中说得只能非零值字段有歧义:意思是在.go文件中的结构体中设为0的话是不会生效的,而不是数据库中数据为0不能进行操作
如果要生效使用map来进行更新
删除数据
删除主要分为两类,一类是硬删除,另一类是软删除
-
硬删除:直接删除(从数据库中删除)
-
软删除:放入回收站,还未删除(记录不会从数据库中真正删除。但是会将
DeletedAt置为当前时间并且无法通过正常的方法找到该记录)如何实现软删除?
在定义模型的时候添加一个
gorm.deletedat字段type User struct { Deleted gorm.Deltedat //或者使用gorm.Model,因为这个模型已经包含了这个字段 ID uint `gorm:primarykey` Name string `gorm:"default:galeone"` Age uint `gorm:"default:18"` }// DELETE FROM users WHERE id = 10; db.Delete(&User{},10) // DELETE FROM users WHERE id IN (1,2,3); db.Delete(&User{},[]int{1,2,3}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; db.Delete(&User{ID :1}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; db.Where("age = 20").Delete(&User{})
事务
为什么要使用事务?
事务是为了保证数据一致性,多个操作要具有原子性,要不都成功,要不都不成功,事务中的操作不能割裂开来
为什么要使用transaction?
在设置事务时,如果操作出错需要事务回滚,如果操作都没问题,进行事务提交,但是在开发过程中,不可避免会忘记写回滚和提交的代码
因此使用transaction能够自动提交事务,避免用户漏泄,很好地解决这一问题
//函数返回的错误给到err,然后再对err进行if判断
if err = tx.Create(&User{Name:"name"}).Error; err != nil{
return err return nil
}
//利用db.Transaction给err赋值,如果里面的事务操作报错,则给error返回的不是nil,那么自动进行回滚,外层的err也不是nil;如果事务操作正常,则给error返回nil,事务自动提交,外层err为nil
if err = db.Transaction(func(tx *gorm.DB) error{
});err != nil
性能优化
使用PrepareStmt来进行性能优化
其他的一些性能优化方案可以阅读官方文档性能优化方案性能 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Gorm生态
Gorm拥有非常丰富的生态 阅读官方文档GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
官方文档是最好的学习资料,看不懂的时候可以再看视频
什么叫做csn? // 参考 github.com/go-sql-driv… 获取详情
安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func (p Product) TableName()string{
return "product" //定义表名
}
func main() {
db, err := gorm.Open("user:pass@tcp(ip:port)数据库名"?charset=utf8mb4&parseTime=True&loc=Local, &gorm.Config{})//连接数据库
if err != nil {
panic("failed to connect database")
}
// 迁移 schema
db.AutoMigrate(&Product{})
// Create
db.Create(&Product{Code: "D42", Price: 100}) //如果是单个字段,可以使用map,多个字段使用左边这种类型
// Read
var product Product
db.First(&product, 1) // 根据整形主键查找
db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
// Update - 将 product 的 price 更新为 200
db.Model(&product).Update("Price", 200)
// Update - 更新多个字段
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})//如果想更新零值字段可以使用这个
// Delete - 删除 product
db.Delete(&product, 1)
}
【注意】Gorm约定定义的实体类和数据库字段的对应关系
使用名为ID的字段作为主键
如果未定义表明,则使用结构体的蛇形负数作为表名
字段名的蛇形作为列名
插入数据
gorm是链式调用:返回的数据获取error,获取插入数据的主键
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct{
gorm.Model
ID uint `gorm:primarykey` //ID默认为主键
Name string `gorm:"column:name"` //默认实体类Name与数据库name对应
Age uint `gorm"column:age"` //默认实体类Name与数据库name对应
}
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
user := User{ID:1,Name: "limingjie", Age: 23}
result := db.Create(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回插入数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
}
打印结果
1
<nil>
1
数据库中结果
【注意】
(1)要带指针,否则会报错result := db.Create(&user)
(2)两者的区别
- user.ID 和操作数据相关
- result.Error result.RowsAffected 操作结果相关
(3)插入数据默认会带有created_at、updated_at以及deleted_at ,因此设计表时应添加这些字段,否则会报错
(4)如何设置默认值?
这个在插入数据的时候生效而在插入数据的时候不会生效
type User struct{
gorm.Model
ID uint `gorm:primarykey`
Name string `gorm:"default:galeone"`
Age uint `gorm:"default:18"`
}
当插入数据的时候如果没有定义值,就会自动插入默认数据
(5)type User struct对应的数据库为users,这是因为没有指定表名,那么如何修改为user呢?
func ( u User) TableName()string{
return "user" //定义表名
}
查询数据
Gorm提供了First、Take、Last方法来查询数据库,并且这三个都能会自动添加Limit 1的条件,如果没有找到数据,它会返回 ErrRecordNotFound 错误
检索单个对象
问题1:limit 1这个条件意味着什么?
只查询一条数据,以此为基础,不难理解如下代码
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
user := User{}
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 获取最后一条记录(主键降序)
db.Last(&user)
//SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.Last(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回查询数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
}
【注意】
(1)同样需要连接数据库
(2)默认是只查询一条数据,First会主键升序,Last会主键降序 ,Take未指定排序字段,但都只会有一条记录(和主键为1要区别开来)
(3)我按照以上代码进行查询,发现db.First和db.Last查询结果一致,这是为什么呢?
1
<nil>
1
当一个函数中利用结构体指针为目标同时调用Last与First时,查询结果相同,为先执行的方法结果。
如果要使用Last就把First注释掉
(4)如果未指定主键,则按照第一个字段进行升序、降序或者不排序
// 根据第一个字段排序
type Language struct {
Code string
Name string
}
DB.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
根据主键进行检索
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
【注意】find和first的区别?
查询多个对象
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/zjqx?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
//查询多条数据
users :=make([]*User,0)
// 1.获取全部记录 SELECT * FROM users;
result := db.Find(&users)
//2.条件查询
//SELECT * FROM users WHERE age > 10;
// result := db.Where("id > 1").Find(&users)
//SELECT * FROM users WHERE name Like '%ming%';
// result := db.Where("name Like ?","%ming%").Find(&users)
//SELECT * FROM users WHERE age In (18.23);
// result := db.Where("age In ?",[]int{18,23}).Find(&users)
//SELECT * FROM users WHERE name ='limingjie' AND age >= 22;
// result := db.Where("age >= ? AND name = ?",18,"limingjie").Find(&users)
//SELECT * FROM users WHERE name ='limingjie'
// result := db.Where(User{Name:"limingjie",Age:0}).Find(&users)
//SELECT * FROM users WHERE name ='limingjie' AND age = 0
result := db.Where(map[string]interface{}{"Name":"limingjie","Age":0}).Find(&users)
fmt.Println(users) // 返回操作数据的主键
fmt.Println(result.Error) // 返回error
fmt.Println(result.RowsAffected) // 返回操作记录的条数
【注意】使用结构体作为查询条件,查询非零值字段,这意味着当gorm中设置查询条件为0,false或其他零值将不会生效
如果想要生效:可以使用map来构建查询条件
更新数据
更新一列
// UPDATE users SET name='hello' WHERE id = 1;
db.Model(User{ID:1}).Update("name","lizhian")
// UPDATE users SET name='hello' WHERE age < 18;
db.Model(User{}).Where("age < ?",18).Update("name","lizhian")
【注意】
第一行代码中的User ID为查询条件 Model(User)表示指定更新表为user
第二行代码中使用where作为条件,那么User只充当指定表的作用,放一个空的User
更新多列
//UPDATE users SET name='limingjie', age=20, updated_at = '2013-11-17 21:34:10' WHERE id = 2;
db.Model(User{ID: 2}).Updates(User{Name: "limingjie", Age: 20})
///根据map
db.Model(User{ID: 2}).Updates(map[string]interface{}{"name": "hello", "age": 18})
//选定字段 UPDATE users SET name='limingjie', updated_at = '2013-11-17 21:34:10' WHERE id = 2;
db.Model(User{ID: 2}).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18})
【注意】课程中说得只能非零值字段有歧义:意思是在.go文件中的结构体中设为0的话是不会生效的,而不是数据库中数据为0不能进行操作
如果要生效使用map来进行更新
删除数据
删除主要分为两类,一类是硬删除,另一类是软删除
-
硬删除:直接删除(从数据库中删除)
-
软删除:放入回收站,还未删除(记录不会从数据库中真正删除。但是会将
DeletedAt置为当前时间并且无法通过正常的方法找到该记录)如何实现软删除?
在定义模型的时候添加一个
gorm.deletedat字段type User struct { Deleted gorm.Deltedat //或者使用gorm.Model,因为这个模型已经包含了这个字段 ID uint `gorm:primarykey` Name string `gorm:"default:galeone"` Age uint `gorm:"default:18"` }// DELETE FROM users WHERE id = 10; db.Delete(&User{},10) // DELETE FROM users WHERE id IN (1,2,3); db.Delete(&User{},[]int{1,2,3}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111; db.Delete(&User{ID :1}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20; db.Where("age = 20").Delete(&User{})
事务
为什么要使用事务?
事务是为了保证数据一致性,多个操作要具有原子性,要不都成功,要不都不成功,事务中的操作不能割裂开来
为什么要使用transaction?
在设置事务时,如果操作出错需要事务回滚,如果操作都没问题,进行事务提交,但是在开发过程中,不可避免会忘记写回滚和提交的代码
因此使用transaction能够自动提交事务,避免用户漏泄,很好地解决这一问题
//函数返回的错误给到err,然后再对err进行if判断
if err = tx.Create(&User{Name:"name"}).Error; err != nil{
return err return nil
}
//利用db.Transaction给err赋值,如果里面的事务操作报错,则给error返回的不是nil,那么自动进行回滚,外层的err也不是nil;如果事务操作正常,则给error返回nil,事务自动提交,外层err为nil
if err = db.Transaction(func(tx *gorm.DB) error{
});err != nil
性能优化
使用PrepareStmt来进行性能优化
其他的一些性能优化方案可以阅读官方文档性能优化方案性能 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Gorm生态
Gorm拥有非常丰富的生态,比如代码生成工具、分片库方案、手动索引、乐观锁、读写分离等等