代码结构
gin框架
gorm框架
GORM 指南gorm
是go语言中的一个orm
框架
连接
var DB *gorm.DB
func InitSqlConnection() {
dsn := "root:xxx@tcp(127.0.0.1:3306)/xxx?charset=utf8mb4&parseTime=True&loc=Local"
DB, _ = gorm.Open(mysql.New(mysql.Config{
DSN: dsn,
DefaultStringSize: 200,
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
}
- 创建数据库连接的时候,可以修改
string
类型映射到数据库中varchar
时,对应的长度,默认为:256,因此会被映射成Text
,一般可以修改为200左右- 还可以配置
sql
日志的级别,一般设置为Info
模型
定义如下的实体模型
type User struct {
ID int64
Name string
Age int64
}
创建
user := User{Name: "yxr", Age: 20}
res := db.Create(&user)
user.ID // 返回插入数据的主键
res.Error // 返回 error
res.RowsAffected // 返回插入记录的条数
批量删除
如果需要一次性插入多条数据,可以直接传入一个slice
给Create
方法
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
for _, user := range users {
user.ID // 1,2,3
}
默认值
可以通过给实体模型中的字段添加标签的方式来设置字段的默认值
默认值是数据库层面的默认值
type User struct {
ID int64
Name string `gorm:"default:'小王子'"`
Age int64
}
查询
一般查询
// 获取第一条记录(主键升序)
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.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error // returns error or nil
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
First
和Last
会根据主键进行排序,分别查询的是第一条数据和最后一条数据
主键检索
如果主键是数字类型,那么就可以使用内联条件
来检索对象
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
检索全部对象
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
条件查询
string条件
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';
// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
struct或map条件
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
使用
struct
或者map
作为条件的时候,会自动忽略0
,''
等其他零值字段
查询条件还可以被内联到First
、Find
方法中,起到与WHERE
方法一样的作用
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// Slice of primary keys
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
not查询
相当于在where
的语义上进行取反操作
db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
// Not In
db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
// Struct
db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
or
连接一个查询条件
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
// Struct
db.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);
// Map
db.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);
选择特定的字段
可以使用Select
方法来选择要从数据库中查询哪些字段,否则将会查询数据表中对应的所有字段
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
链式操作
在GORM中,可以使用链式操作,所以可以编写如下所示的代码:
db.Where("name = ?", "jinzhu").Where("age = ?", 18).First(&user)
在GORM
中一共有三种方法,分别是:
- 链式方法
Finisher
方法- 新建会话方法
但在进行链式调用或者调用Finisher
方法后,获得的*gorm.DB
实例并不是安全的,他可能会被后续的操作的所污染
After a Chain method, Finisher Method, GORM returns an initialized *gorm.DB instance, which is NOT safe to reuse anymore, or new generated SQL might be polluted by the previous conditions, for example:
queryDB := DB.Where("name = ?", "jinzhu")
queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10
queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20
如果要对*gorm.DB
实例进行复用,可以使用New Session Method
方法来创建一个可以共享的*gorm.DB
实例,如下:
queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})
queryDB.Where("age > ?", 10).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 10
queryDB.Where("age > ?", 20).First(&user2)
// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
链式方法
链式方法主要用来给当前的查询状态修改查询条件,比如以下几个方法:Where
, Select
, Omit
, Joins
, Scopes
, Preload
, Raw
(Raw
can’t be used with other chainable methods to build SQL)…
Finisher方法
Finisher
方法用来生成并立即执行SQL语句,就像下面的方法:Create
, First
, Find
, Take
, Save
, Update
, Delete
, Scan
, Row
, Rows
…
New Session Method
用来创建一个可共享的*gorm.DB
实例,从而解决链式调用造成的SQL可能被污染的问题
更新
保存所有字段
Save
会保存所有的字段,即使字段是零值
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
更新一或多列
当使用Update
更新的时候,需要指定条件,否则会返回ErrMissingWhereClause
错误。
当使用model
方法,并且指定的对象中主键是有值的情况下,该值会被用来构建查询条件,如下:
// 条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
Model
方法中传入那个参数,必须携带有对应的ID值 比如想要更新编号为5的用户的相关信息,那么就要传
db.Model(&User{ID: 5})
从而构造SQL的时候,能对我们想要进行更新的数据进行更新 如果没有通过
Model
来指定记录的主键,那么就会触发批量更新
Updates
方法允许使用Struct
或者map[string]interface{}
参数
使用上述两种类型的数据进行更新的时候,默认情况下,GORM只会更新非零值字段
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
更新指定字段
如果在更新的时候只想要更新部分字段(忽略部分字段),可以使用select
、omit
select
是选定某些字段omit
是排除某些字段
// 使用 Map 进行 Select
// User's ID is `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
// 使用 Struct 进行 Select(会 select 零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
// Select 所有字段(查询包括零值字段的所有字段)
db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
// Select 除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
获取更新的记录数
// 通过 `RowsAffected` 得到更新的记录数
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
result.RowsAffected // 更新的记录数
result.Error // 更新的错误
删除
在删除的时候,需要通过Model
来指定主键的值,否则会触发批量删除
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
主键删除
GORM
允许通过主键进行内联条件删除,可以使用数字,也可以使用字符串,如下所示:
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);
批量删除
如果不包括主属性,那么GORM
会触发批量删除,删除所有匹配的记录
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
软删除(逻辑删除)
// user 的 ID 是 `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
如果想使用软删除的特性,但是不想引入gorm.Model
,可以通过下面的方式来启用软删除的特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
永久删除
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
关联模式
约定
在关联模式中,我们一般有拥有者和被拥有者两种模型
对于外键,默认的外键名是拥有者的类型名加上其主键字段名
对于引用,默认的引用是外键的名称
一对一关联
一对一关联其实可以将某个实体拆分成单个的属性,从而存储到一张表上 当然这种一对一关联在现实生活中也是存在对应示例的,比如每一个孩子都只会对应一个爸爸或者妈妈
Belongs to
Belongs to
强调的是一种属于的关系
假设我们的应用中包含user
和company
,并且每个user都只能分配给一个company,下面的两个结构体就可以很好的描述这种关系
// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
gorm.Model
Name string
CompanyID int
Company Company
}
type Company struct {
ID int
Name string
}
在User对象中,有一个和Company
对应的CompanyID
。默认情况下,CompanyID
被隐含地用来在User
和Company
之间创建一个外键关系,因此必须包含在User
结构体中才能填充Company
内部结构体
约定:在默认情况下,外键的名字,使用的是,拥有者的类型名称加上表的主键的字段名字
比如,定义一个User实体属于Company实体,那么外键的名字一般使用
CompanyID
Has One
has one
同样用来创建一对一的关联,但是和一对一关系不太相同,这种关联表明一个模型的每一个实例都包含或拥有另一个模型的实例。
假设,我们的应用中包含User
和CreditCard
模型,并且每一个User
都只包含一张CreditCard
,对模型的声明如下:
// User 有一张 CreditCard,UserID 是外键
type User struct {
gorm.Model
CreditCard CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
查询用户列表的时候,需要预先加载
CreditCard
的相关信息,需要使用Preload
方法
func GetAll(db *gorm.DB) ([]User, error) {
var users []User
err := db.Model(&User{}).Preload("CreditCard").Find(&users).Error
return users, err
}
重写外键
对于外键字段,默认情况下同样是采用字段名+主键
来命名,对于上面的例子,也就是UserID
如果想要更改这种默认规则,那么可以使用foreignKey
标签来进行修改:
type User struct {
gorm.Model
CreditCard CreditCard `gorm:"foreignKey:UserName"`
// use UserName as foreign key
}
type CreditCard struct {
gorm.Model
Number string
UserName string
}
重写引用
默认情况下,拥有者实体会将 has one
对应模型的主键保存为外键,您也可以修改它,用另一个字段来保存,例如下个这个使用 Name
来保存的例子。
type User struct {
gorm.Model
Name string `gorm:"index"`
CreditCard CreditCard `gorm:"foreignkey:UserName;references:name"`
}
type CreditCard struct {
gorm.Model
Number string
UserName string
}
一对多关联
通过has many
与另外一个模型建立了一对多的连接
在这个关系中,拥有者可以有零个或多个关联模型,如下所示
// User 有多张 CreditCard,UserID 是外键
type User struct {
gorm.Model
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
多对多关联
在多对多关联中,会在两个关系中建立起一张连接表,下面以用户和课程之间的关系来做示例:
// User 拥有并属于多个Language, 'user_classes'是连接表
type User struct {
gorm.Model
UserClasses []Class `json:"userClasses" gorm:"many2many:user_classes;"`
}
type Class struct {
gorm.Model
ClassName string `gorm:"uniqueIndex" json:"className"`
Users []User `json:"users" gorm:"many2many:user_classes;"`
}
预加载
Preload
GORM允许在Preload
的其他SQL中直接加载依赖关系,并且支持链式调用,比如:
type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
// 查找 user 时预加载相关 Order
db.Preload("Orders").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);
db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
Join
Preload
在一个单独查询中加载关联数据。而 Join Preload
会使用 inner join 加载关联数据,例如:
db.Joins("Company").Joins("Manager").Joins("Account").First(&user, 1)
db.Joins("Company").Joins("Manager").Joins("Account").First(&user, "users.name = ?", "jinzhu")
db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})
但,Join Preload 适用于一对一的关系,比如:
has one
、belongs to
同时,预加载的时候还可以指定加载的条件,比如:
// 带条件的预加载 Order
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');