前言
这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记,做笔记记录一下自己的学习过程。
此笔记主要内容如下:
- 理解database/sql
- GORM使用简介
1 理解database/sql
- 基本用法
- 设计原理
- 基础概念
1.1 基础用法 - Quick Start
- import driver实现
import "github.com/go-sql-driver/mysql" - 使用driver+DSN初始化DB连接
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello") - 执行一条SQL,通过rows取回返回的数据
- 处理完毕,需要释放链接
rows,err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
// xxx
}
defer rows.Close()
- 数据、错误处理
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil {
// ...
}
users = append(users, user)
}
if rows.Err() != nil {
// ...
}
1.2 设计原理
- database/sql采用极简接口设计原则,对上面应用程序提供标准API操作接口,对下层驱动暴露简单驱动接口,在database/sql包内部实现连接池的管理。
- 这意味着支持不一样的数据库,只需要对数据库实现相同的连接接口,操作接口,然后把同样的一套操作接口暴露给应用程序就可以了。
1.3 基础概念
- 什么是数据库、什么是 SQL
- DSN 是什么
2 GORM的基础使用
- 基础用法
- Model定义
- 惯例约定
- 关联操作
2.1 基础用法
2.1.1 数据库连接
db, err := gorm.Open(
mysql.Open("user:password@tcp(127.0.0.1:3306)/dbname"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
panic("failed to connect database")
}
- GORM 允许用户通过覆盖默认的
NamingStrategy来更改命名约定,默认NamingStrategy也提供了SingularTable的选项可以使用单数表名。 - 更多GORM命名策略配置可查看GORM配置文档
2.1.2 CRUD
- Create
// 创建
user := User{Id: "nh100116", Name: "zzb"}
result := db.Create(&user)
fmt.Println(user.Id) //返回主键 last insert id
fmt.Println(result.Error) //返回 error
fmt.Println(result.RowsAffected) //返回影响的行数,这里是插入多少行
// 批量创建
var users = []User{{Name: "zzb1"}, {Name: "zzb2"}, {Name: "zzb3"}}
result := db.Create(&users)
fmt.Println(result.RowsAffected) //插入3行,这里输出是3
- Query
// 读取
var users []User
result := db.Select("id", "name").Find(&users)
//带条件的查询
result := db.Select("id", "name").Where("id = ?", "nh100111").Find(&users)
fmt.Println(result.RowsAffected) //查询到几行就输出几
- Update
var user User
// 更新某个字段
result := db.Model(&user).Where("name = ?", "zzb1").Update("id", "nh100123")
result := db.Model(&user).Where("name = ?", "zzb2").UpdateColumn("id", "nh100133")
// 更新多个字段
result := db.Model(&user).Where("name = ?", "zzb1").Updates(User{Id: "nh100144", Name: "zzb11"})
result := db.Model(&user).Where("name = ?", "zzb2").Updates(map[string]interface{}{"Id": "nh100155", "Name": "zzb22"})
fmt.Println(result.RowsAffected) //更新了几行就输出几
- Delete
// 删除
result := db.Delete(&User{}, "name = ?", "zzb3") //批量删除所有符合条件的记录
fmt.Println(result.RowsAffected) //删除几条记录就输出几
- 小结
- 可以用result.RowAffected来获取影响的数据库记录数
- 可以用Select、Where等指定字段与条件
- 具体更多GORM操作可查看GORM文档
2.2 模型定义
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成。
- 嵌入结构体
type User struct {
Id string
Name string
gorm.Model
}
//等价于
type User struct {
Id string
Name string
}
type User struct {
Id string
Name string
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
2.3 惯例约定
- 约定优于配置
- 表名为struct name的snake_cases复数格式(结构体小写的复数格式)
- 字段名为filed name的snake_case单数格式(结构体字段小写的单数格式)
- ID/Id字段为主键,如果为数字,则为自增主键
- CreatedAt字段,创建时,保存当前时间
- UpdatedAt字段,创建、更新时,保存当前时间
- gorm.DeletedAt字段,默认开启soft delete软删除模式
- 如果您的模型包含了一个
gorm.deletedat字段(gorm.Model已经包含了该字段),它将自动获得软删除的能力!拥有软删除能力的模型调用Delete时,记录不会从数据库中被真正删除。但 GORM 会将DeletedAt置为当前时间, 并且你不能再通过普通的查询方法找到该记录。
- 如果您的模型包含了一个
2.4 关联介绍
- User 拥有一个 Account (has one) ,拥有多个 Pets (has many) ,多个 Toys (多态 has many) 。
- 属于某 Company (belongs to) 属于某 Manager (单表 belongs to) 管理 Team (单表 has many)
- 会多种 Languages (many to many) 拥有很多 Friends (单表 many to many)
- 并且他的 Pet 也有一个玩具 Toy (多态 has one)
type User struct {
gorm.Model
Name string
Account Account
Pets []*Pet
Toys []Toy `gorm:"polymorphic:Owner"`
CompanyID *int
Company Company
ManagerID *uint
Manager *User
Team []User `gorm:"foreignkey:ManagerID"`
Languages []Language `gorm:"many2many:UserSpeak;"`
Friends []*User `gorm:"many2many:user_friends;"`
}
2.4.1 关联操作 - CRUD
结构体
type User struct {
gorm.Model
Name string
Languages []Language `gorm:"many2many:UserSpeak;"`
}
type Language struct {
gorm.Model
Name string
}
user := User{
Name: "lyy",
Languages: []Language{
{Name: "ZH"},
{Name: "EN"},
},
}
//自动创建、更新记录
db.Create(&user)
var user User
db.First(&user)
//关联模式
langAssociation := db.Model(&user).Association("Languages")
//查找关联
var languages []Language
langAssociation.Find(&languages)
fmt.Println(languages)
//添加关联
//把德语添加到用户掌握的语言中
langAssociation.Append(&Language{Name: "DE"})
//替换关联
//把用户掌握的语言替换为汉语、德语
var languageZH Language
db.Where("id = ?", 1).First(&languageZH)
var languageDE Language
db.Where("id = ?", 5).First(&languageDE)
langAssociation.Replace([]Language{languageZH, languageDE})
//删除关联
//删除用户掌握的两种语言
langAssociation.Delete(languageEN, languageDE)
//清空关联
//删除用户所有掌握的语言,仅在关联表中删除,不删除语言、用户实体
langAssociation.Clear()
//关联计数
//返回用户所掌握的语言的数量
langAssociation.Count()
// 批量模式Append、Replace,参数的长度必须与数据的长度相同,否则会返回 error
// 例如:现在有三个 user,将 userA 到 user1 的 team,
// 将 userB 到 user2 的 team,将 userA、userB 和 userC 到 user3 的 team
var users = []User{user1, user2, user3}
db.Model(&users).Association("Team").Append(&userA, &userB, &[]User{userA, userB, userC})
2.4.2 关联操作 - 级联删除
//方法1:使用Select实现级联删除,不依赖数据库约束及软删除
//删除user时,也删除user掌握的language
//在关系表中删除,也就是不删除language实体
var user User
db.First(&user)
db.Select("Language").Delete(&user)
//删除user时,也删除用户及其依赖的所有has one/many, many2many记录
db.Select(clause.Associations).Delete(&user)
//方法2:使用数据库约束自动删除
type User struct {
ID uint
Name string
Account Account `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
Orders []Order `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
}
//需要使用GORM Migrate 数据库迁移数据库外键才行
db.AutoMigrate(&User{})
//如果未启用软删除,在删除User时会自动删除其依赖
db.Delete(&User{})
2.4.3 关联操作 - Preload / Joins 预加载
type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
// 查询用户的时候并找出其订单,个人信息 (1+1 条SQL)
db.Preload("Orders").Preload("Profile").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // 一对多
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // 一对一
// 使用 Join SQL 加载 (单条 JOIN SQL)
// 注意 Join Preload 适用于一对一的关系
// Preload 在一个单独查询中加载关联数据。而 Join Preload 会使用 inner join 加载关联数据
db.Joins("Company").Joins("Manager").Joins("Account").First(&user, 1)
// 带条件的Join
db.Joins("Company", DB.Where(&Company{Alive: true})).Find(&users)
// 预加载全部关联 (只加载一级关联)
db.Preload(clause.Associations).Find(&users)
// 多级/嵌套 预加载
db.Preload("Orders.OrderItems.Product").Find(&users)
// 多级预加载 + 预加载全部一级关联
db.Preload("Orders.OrderItems.Product").Preload(clause.Associations).Find(&users)
// 带条件的预加载
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');