代码结构

image.png

gin框架

gorm框架

GORM 指南
gorm是go语言中的一个orm框架

连接

  1. var DB *gorm.DB
  2. func InitSqlConnection() {
  3. dsn := "root:xxx@tcp(127.0.0.1:3306)/xxx?charset=utf8mb4&parseTime=True&loc=Local"
  4. DB, _ = gorm.Open(mysql.New(mysql.Config{
  5. DSN: dsn,
  6. DefaultStringSize: 200,
  7. }), &gorm.Config{
  8. Logger: logger.Default.LogMode(logger.Info),
  9. })
  10. }
  1. 创建数据库连接的时候,可以修改string类型映射到数据库中varchar时,对应的长度,默认为:256,因此会被映射成Text,一般可以修改为200左右
  2. 还可以配置sql日志的级别,一般设置为Info

模型

定义如下的实体模型

  1. type User struct {
  2. ID int64
  3. Name string
  4. Age int64
  5. }

创建

  1. user := User{Name: "yxr", Age: 20}
  2. res := db.Create(&user)
  3. user.ID // 返回插入数据的主键
  4. res.Error // 返回 error
  5. res.RowsAffected // 返回插入记录的条数

批量删除
如果需要一次性插入多条数据,可以直接传入一个sliceCreate方法

  1. var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
  2. db.Create(&users)
  3. for _, user := range users {
  4. user.ID // 1,2,3
  5. }

默认值

可以通过给实体模型中的字段添加标签的方式来设置字段的默认值

默认值是数据库层面的默认值

  1. type User struct {
  2. ID int64
  3. Name string `gorm:"default:'小王子'"`
  4. Age int64
  5. }

查询

一般查询

  1. // 获取第一条记录(主键升序)
  2. db.First(&user)
  3. // SELECT * FROM users ORDER BY id LIMIT 1;
  4. // 获取一条记录,没有指定排序字段
  5. db.Take(&user)
  6. // SELECT * FROM users LIMIT 1;
  7. // 获取最后一条记录(主键降序)
  8. db.Last(&user)
  9. // SELECT * FROM users ORDER BY id DESC LIMIT 1;
  10. result := db.First(&user)
  11. result.RowsAffected // 返回找到的记录数
  12. result.Error // returns error or nil
  13. // 检查 ErrRecordNotFound 错误
  14. errors.Is(result.Error, gorm.ErrRecordNotFound)

FirstLast会根据主键进行排序,分别查询的是第一条数据和最后一条数据

主键检索

如果主键是数字类型,那么就可以使用内联条件来检索对象

  1. db.First(&user, 10)
  2. // SELECT * FROM users WHERE id = 10;
  3. db.First(&user, "10")
  4. // SELECT * FROM users WHERE id = 10;
  5. db.Find(&users, []int{1,2,3})
  6. // SELECT * FROM users WHERE id IN (1,2,3);

检索全部对象

  1. // Get all records
  2. result := db.Find(&users)
  3. // SELECT * FROM users;
  4. result.RowsAffected // returns found records count, equals `len(users)`
  5. result.Error // returns error

条件查询

string条件
  1. // Get first matched record
  2. db.Where("name = ?", "jinzhu").First(&user)
  3. // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
  4. // Get all matched records
  5. db.Where("name <> ?", "jinzhu").Find(&users)
  6. // SELECT * FROM users WHERE name <> 'jinzhu';
  7. // IN
  8. db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
  9. // SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
  10. // LIKE
  11. db.Where("name LIKE ?", "%jin%").Find(&users)
  12. // SELECT * FROM users WHERE name LIKE '%jin%';
  13. // AND
  14. db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
  15. // SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
  16. // Time
  17. db.Where("updated_at > ?", lastWeek).Find(&users)
  18. // SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
  19. // BETWEEN
  20. db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
  21. // SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

struct或map条件
  1. // Struct
  2. db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
  4. // Map
  5. db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
  6. // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
  7. // Slice of primary keys
  8. db.Where([]int64{20, 21, 22}).Find(&users)
  9. // SELECT * FROM users WHERE id IN (20, 21, 22);

使用struct或者map作为条件的时候,会自动忽略0''等其他零值字段

查询条件还可以被内联到FirstFind方法中,起到与WHERE方法一样的作用

  1. // Struct
  2. db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
  4. // Map
  5. db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
  6. // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
  7. // Slice of primary keys
  8. db.Where([]int64{20, 21, 22}).Find(&users)
  9. // SELECT * FROM users WHERE id IN (20, 21, 22);

not查询

相当于在where的语义上进行取反操作

  1. db.Not("name = ?", "jinzhu").First(&user)
  2. // SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;
  3. // Not In
  4. db.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)
  5. // SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
  6. // Struct
  7. db.Not(User{Name: "jinzhu", Age: 18}).First(&user)
  8. // SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;
  9. // Not In slice of primary keys
  10. db.Not([]int64{1,2,3}).First(&user)
  11. // SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

or

连接一个查询条件

  1. db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
  2. // SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
  3. // Struct
  4. db.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)
  5. // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
  6. // Map
  7. db.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)
  8. // SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

选择特定的字段

可以使用Select方法来选择要从数据库中查询哪些字段,否则将会查询数据表中对应的所有字段

  1. db.Select("name", "age").Find(&users)
  2. // SELECT name, age FROM users;
  3. db.Select([]string{"name", "age"}).Find(&users)
  4. // SELECT name, age FROM users;
  5. db.Table("users").Select("COALESCE(age,?)", 42).Rows()
  6. // SELECT COALESCE(age,'42') FROM users;

链式操作

在GORM中,可以使用链式操作,所以可以编写如下所示的代码:

  1. 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:

  1. queryDB := DB.Where("name = ?", "jinzhu")
  2. queryDB.Where("age > ?", 10).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10
  4. queryDB.Where("age > ?", 20).First(&user2)
  5. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10 AND age > 20

如果要对*gorm.DB实例进行复用,可以使用New Session Method方法来创建一个可以共享的*gorm.DB实例,如下:

  1. queryDB := DB.Where("name = ?", "jinzhu").Session(&gorm.Session{})
  2. queryDB.Where("age > ?", 10).First(&user)
  3. // SELECT * FROM users WHERE name = "jinzhu" AND age > 10
  4. queryDB.Where("age > ?", 20).First(&user2)
  5. // 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会保存所有的字段,即使字段是零值

  1. db.First(&user)
  2. user.Name = "jinzhu 2"
  3. user.Age = 100
  4. db.Save(&user)
  5. // 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方法,并且指定的对象中主键是有值的情况下,该值会被用来构建查询条件,如下:

  1. // 条件更新
  2. db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
  3. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
  4. // User 的 ID 是 `111`
  5. db.Model(&user).Update("name", "hello")
  6. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
  7. // 根据条件和 model 的值进行更新
  8. db.Model(&user).Where("active = ?", true).Update("name", "hello")
  9. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

Model方法中传入那个参数,必须携带有对应的ID值 比如想要更新编号为5的用户的相关信息,那么就要传

  1. db.Model(&User{ID: 5})

从而构造SQL的时候,能对我们想要进行更新的数据进行更新 如果没有通过Model来指定记录的主键,那么就会触发批量更新

Updates方法允许使用Struct或者map[string]interface{}参数
使用上述两种类型的数据进行更新的时候,默认情况下,GORM只会更新非零值字段

  1. // 根据 `struct` 更新属性,只会更新非零值的字段
  2. db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
  3. // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
  4. // 根据 `map` 更新属性
  5. db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  6. // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

更新指定字段

如果在更新的时候只想要更新部分字段(忽略部分字段),可以使用selectomit

select是选定某些字段 omit是排除某些字段

  1. // 使用 Map 进行 Select
  2. // User's ID is `111`:
  3. db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  4. // UPDATE users SET name='hello' WHERE id=111;
  5. db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  6. // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
  7. // 使用 Struct 进行 Select(会 select 零值的字段)
  8. db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
  9. // UPDATE users SET name='new_name', age=0 WHERE id=111;
  10. // Select 所有字段(查询包括零值字段的所有字段)
  11. db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})
  12. // Select 除 Role 外的所有字段(包括零值字段的所有字段)
  13. db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})

获取更新的记录数

  1. // 通过 `RowsAffected` 得到更新的记录数
  2. result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
  3. // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
  4. result.RowsAffected // 更新的记录数
  5. result.Error // 更新的错误

删除

在删除的时候,需要通过Model来指定主键的值,否则会触发批量删除

  1. // Email 的 ID 是 `10`
  2. db.Delete(&email)
  3. // DELETE from emails where id = 10;
  4. // 带额外条件的删除
  5. db.Where("name = ?", "jinzhu").Delete(&email)
  6. // DELETE from emails where id = 10 AND name = "jinzhu";

主键删除

GORM允许通过主键进行内联条件删除,可以使用数字,也可以使用字符串,如下所示:

  1. db.Delete(&User{}, 10)
  2. // DELETE FROM users WHERE id = 10;
  3. db.Delete(&User{}, "10")
  4. // DELETE FROM users WHERE id = 10;
  5. db.Delete(&users, []int{1,2,3})
  6. // DELETE FROM users WHERE id IN (1,2,3);

批量删除

如果不包括主属性,那么GORM会触发批量删除,删除所有匹配的记录

  1. db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
  2. // DELETE from emails where email LIKE "%jinzhu%";
  3. db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
  4. // DELETE from emails where email LIKE "%jinzhu%";

软删除(逻辑删除)

image.png

  1. // user 的 ID 是 `111`
  2. db.Delete(&user)
  3. // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
  4. // 批量删除
  5. db.Where("age = ?", 20).Delete(&User{})
  6. // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
  7. // 在查询时会忽略被软删除的记录
  8. db.Where("age = 20").Find(&user)
  9. // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

如果想使用软删除的特性,但是不想引入gorm.Model,可以通过下面的方式来启用软删除的特性:

  1. type User struct {
  2. ID int
  3. Deleted gorm.DeletedAt
  4. Name string
  5. }

永久删除

  1. db.Unscoped().Delete(&order)
  2. // DELETE FROM orders WHERE id=10;

关联模式

约定

在关联模式中,我们一般有拥有者被拥有者两种模型
对于外键,默认的外键名是拥有者的类型名加上其主键字段名
对于引用,默认的引用是外键的名称

一对一关联

一对一关联其实可以将某个实体拆分成单个的属性,从而存储到一张表上 当然这种一对一关联在现实生活中也是存在对应示例的,比如每一个孩子都只会对应一个爸爸或者妈妈

Belongs to

Belongs to强调的是一种属于的关系

假设我们的应用中包含usercompany,并且每个user都只能分配给一个company,下面的两个结构体就可以很好的描述这种关系

  1. // `User` 属于 `Company`,`CompanyID` 是外键
  2. type User struct {
  3. gorm.Model
  4. Name string
  5. CompanyID int
  6. Company Company
  7. }
  8. type Company struct {
  9. ID int
  10. Name string
  11. }

在User对象中,有一个和Company对应的CompanyID。默认情况下,CompanyID被隐含地用来在UserCompany之间创建一个外键关系,因此必须包含在User结构体中才能填充Company内部结构体
约定:在默认情况下,外键的名字,使用的是,拥有者的类型名称加上表的主键的字段名字

比如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID

Has One

has one同样用来创建一对一的关联,但是和一对一关系不太相同,这种关联表明一个模型的每一个实例都包含或拥有另一个模型的实例。
假设,我们的应用中包含UserCreditCard模型,并且每一个User都只包含一张CreditCard,对模型的声明如下:

  1. // User 有一张 CreditCard,UserID 是外键
  2. type User struct {
  3. gorm.Model
  4. CreditCard CreditCard
  5. }
  6. type CreditCard struct {
  7. gorm.Model
  8. Number string
  9. UserID uint
  10. }

查询用户列表的时候,需要预先加载CreditCard的相关信息,需要使用Preload方法

  1. func GetAll(db *gorm.DB) ([]User, error) {
  2. var users []User
  3. err := db.Model(&User{}).Preload("CreditCard").Find(&users).Error
  4. return users, err
  5. }

重写外键

对于外键字段,默认情况下同样是采用字段名+主键来命名,对于上面的例子,也就是UserID
如果想要更改这种默认规则,那么可以使用foreignKey标签来进行修改:

  1. type User struct {
  2. gorm.Model
  3. CreditCard CreditCard `gorm:"foreignKey:UserName"`
  4. // use UserName as foreign key
  5. }
  6. type CreditCard struct {
  7. gorm.Model
  8. Number string
  9. UserName string
  10. }

重写引用

默认情况下,拥有者实体会将 has one 对应模型的主键保存为外键,您也可以修改它,用另一个字段来保存,例如下个这个使用 Name来保存的例子。

  1. type User struct {
  2. gorm.Model
  3. Name string `gorm:"index"`
  4. CreditCard CreditCard `gorm:"foreignkey:UserName;references:name"`
  5. }
  6. type CreditCard struct {
  7. gorm.Model
  8. Number string
  9. UserName string
  10. }

image.png

一对多关联

通过has many与另外一个模型建立了一对多的连接
在这个关系中,拥有者可以有零个或多个关联模型,如下所示

  1. // User 有多张 CreditCard,UserID 是外键
  2. type User struct {
  3. gorm.Model
  4. CreditCards []CreditCard
  5. }
  6. type CreditCard struct {
  7. gorm.Model
  8. Number string
  9. UserID uint
  10. }

多对多关联

在多对多关联中,会在两个关系中建立起一张连接表,下面以用户和课程之间的关系来做示例:

  1. // User 拥有并属于多个Language, 'user_classes'是连接表
  2. type User struct {
  3. gorm.Model
  4. UserClasses []Class `json:"userClasses" gorm:"many2many:user_classes;"`
  5. }
  6. type Class struct {
  7. gorm.Model
  8. ClassName string `gorm:"uniqueIndex" json:"className"`
  9. Users []User `json:"users" gorm:"many2many:user_classes;"`
  10. }

预加载

Preload

GORM允许在Preload的其他SQL中直接加载依赖关系,并且支持链式调用,比如:

  1. type User struct {
  2. gorm.Model
  3. Username string
  4. Orders []Order
  5. }
  6. type Order struct {
  7. gorm.Model
  8. UserID uint
  9. Price float64
  10. }
  11. // 查找 user 时预加载相关 Order
  12. db.Preload("Orders").Find(&users)
  13. // SELECT * FROM users;
  14. // SELECT * FROM orders WHERE user_id IN (1,2,3,4);
  15. db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
  16. // SELECT * FROM users;
  17. // SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
  18. // SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
  19. // SELECT * FROM roles WHERE id IN (4,5,6); // belongs to

Join

Preload在一个单独查询中加载关联数据。而 Join Preload 会使用 inner join 加载关联数据,例如:

  1. db.Joins("Company").Joins("Manager").Joins("Account").First(&user, 1)
  2. db.Joins("Company").Joins("Manager").Joins("Account").First(&user, "users.name = ?", "jinzhu")
  3. db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})

但,Join Preload 适用于一对一的关系,比如:has onebelongs to

同时,预加载的时候还可以指定加载的条件,比如:

  1. // 带条件的预加载 Order
  2. db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
  3. // SELECT * FROM users;
  4. // SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
  5. db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
  6. // SELECT * FROM users WHERE state = 'active';
  7. // SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');