代码结构
gin框架
gorm框架
GORM 指南gorm是go语言中的一个orm框架
连接
var DB *gorm.DBfunc 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 int64Name stringAge int64}
创建
user := User{Name: "yxr", Age: 20}res := db.Create(&user)user.ID // 返回插入数据的主键res.Error // 返回 errorres.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 int64Name 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 recordsresult := db.Find(&users)// SELECT * FROM users;result.RowsAffected // returns found records count, equals `len(users)`result.Error // returns error
条件查询
string条件
// Get first matched recorddb.Where("name = ?", "jinzhu").First(&user)// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;// Get all matched recordsdb.Where("name <> ?", "jinzhu").Find(&users)// SELECT * FROM users WHERE name <> 'jinzhu';// INdb.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');// LIKEdb.Where("name LIKE ?", "%jin%").Find(&users)// SELECT * FROM users WHERE name LIKE '%jin%';// ANDdb.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;// Timedb.Where("updated_at > ?", lastWeek).Find(&users)// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';// BETWEENdb.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条件
// Structdb.Where(&User{Name: "jinzhu", Age: 20}).First(&user)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;// Mapdb.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;// Slice of primary keysdb.Where([]int64{20, 21, 22}).Find(&users)// SELECT * FROM users WHERE id IN (20, 21, 22);
使用
struct或者map作为条件的时候,会自动忽略0,''等其他零值字段
查询条件还可以被内联到First、Find方法中,起到与WHERE方法一样的作用
// Structdb.Where(&User{Name: "jinzhu", Age: 20}).First(&user)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;// Mapdb.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;// Slice of primary keysdb.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 Indb.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");// Structdb.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 keysdb.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';// 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);
选择特定的字段
可以使用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 > 10queryDB.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 > 10queryDB.Where("age > ?", 20).First(&user2)// SELECT * FROM users WHERE name = "jinzhu" AND age > 20
链式方法
链式方法主要用来给当前的查询状态修改查询条件,比如以下几个方法:Where, Select, Omit, Joins, Scopes, Preload, Raw (Rawcan’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 = 100db.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 intDeleted gorm.DeletedAtName 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.ModelName stringCompanyID intCompany Company}type Company struct {ID intName 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.ModelCreditCard CreditCard}type CreditCard struct {gorm.ModelNumber stringUserID uint}
查询用户列表的时候,需要预先加载
CreditCard的相关信息,需要使用Preload方法
func GetAll(db *gorm.DB) ([]User, error) {var users []Usererr := db.Model(&User{}).Preload("CreditCard").Find(&users).Errorreturn users, err}
重写外键
对于外键字段,默认情况下同样是采用字段名+主键来命名,对于上面的例子,也就是UserID
如果想要更改这种默认规则,那么可以使用foreignKey标签来进行修改:
type User struct {gorm.ModelCreditCard CreditCard `gorm:"foreignKey:UserName"`// use UserName as foreign key}type CreditCard struct {gorm.ModelNumber stringUserName string}
重写引用
默认情况下,拥有者实体会将 has one 对应模型的主键保存为外键,您也可以修改它,用另一个字段来保存,例如下个这个使用 Name来保存的例子。
type User struct {gorm.ModelName string `gorm:"index"`CreditCard CreditCard `gorm:"foreignkey:UserName;references:name"`}type CreditCard struct {gorm.ModelNumber stringUserName string}
一对多关联
通过has many与另外一个模型建立了一对多的连接
在这个关系中,拥有者可以有零个或多个关联模型,如下所示
// User 有多张 CreditCard,UserID 是外键type User struct {gorm.ModelCreditCards []CreditCard}type CreditCard struct {gorm.ModelNumber stringUserID uint}
多对多关联
在多对多关联中,会在两个关系中建立起一张连接表,下面以用户和课程之间的关系来做示例:
// User 拥有并属于多个Language, 'user_classes'是连接表type User struct {gorm.ModelUserClasses []Class `json:"userClasses" gorm:"many2many:user_classes;"`}type Class struct {gorm.ModelClassName string `gorm:"uniqueIndex" json:"className"`Users []User `json:"users" gorm:"many2many:user_classes;"`}
预加载
Preload
GORM允许在Preload的其他SQL中直接加载依赖关系,并且支持链式调用,比如:
type User struct {gorm.ModelUsername stringOrders []Order}type Order struct {gorm.ModelUserID uintPrice float64}// 查找 user 时预加载相关 Orderdb.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
同时,预加载的时候还可以指定加载的条件,比如:
// 带条件的预加载 Orderdb.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');
