GO编程规范&性能分析调优笔记 | 青训营笔记

162 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

GORM框架的学习笔记

一、理解database/sql

如果用go原生的drive去访问sql,将十分繁琐,因此这里引出使用GORM来访问sql

二、GORM使用简介

首先官方文档:点击此处

它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了 。

2.1 安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

2.2 快速入门

package main
​
import (
  "gorm.io/gorm"
  "gorm.io/driver/sqlite"
)
​
type Product struct {
  gorm.Model//建结构体就建议嵌入这个,这个会记录创造更新时间等,并自动生成主键
  Code  string
  Price uint
}
​
func main() {
    // 用户名:密码@tcp(ip:port)/数据库名?charset=utf8mb4&parseTime=True&loc=Local(后面两个参数是时间设置为本地时间)
    dsn := "root:root123@tcp(127.0.0.1:3306)/test_gorm?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
  if err != nil {
    panic("failed to connect database")
  }
​
  // 创建一个表表名叫Product,并且dp指向该数据库
  db.AutoMigrate(&Product{})
​
  // Create---插入
  db.Create(&Product{Code: "D42", Price: 100})
​
  // 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)
}

建表

db.AutoMigrate(&Product{})这个就是建表

//常见办法
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) //记得是指针
​
/*指定字段值插入记录*/
//创建新记录并更新相应字段的值
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
//创建新记录且一同忽略传递给略去的字段值。
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
​
/*批量插入记录*/
//要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 GORM 将生成单独一条SQL语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

/*删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete,*/
// 假设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";
​
/*根据主键删除记录(数字和字符串均可)*/
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);
​

/*更新单个列*/
// 条件更新
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;
​
/*更新多列*/
// 根据 `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` 更新属性,就会把111的所有列都更新了
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;
​
//如果您想要在更新时选定、忽略某些字段,您可以使用 SelectOmit,具体见官方文档

/* 检索单个对象 */
// 获取第一条记录(主键升序)
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)
​
/* 根据主键检索单个对象 */
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
//这个具体看官方文档里面的string条件
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
​
/* 检索多个对象 */
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
result := db.Find(&users)// 检索全部对象
// SELECT * FROM users;

对于根据键来查询,可以点击此处查看具体格式

2.3关联

预加载

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

belongs to

belongs to 会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例。

例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这种关系。 注意,在 User 对象中,有一个和 Company 一样的 CompanyID

// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   Company
}
​
type Company struct {
  ID   int
  Name string
}

Has One

has one 与另一个模型建立一对一的关联,但它和一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。

例如,您的应用包含 user 和 credit card 模型,且每个 user 只能有一张 credit card。

// User 有一张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCard CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
/*检索*/
// 检索用户列表并预加载信用卡
func GetAll(db *gorm.DB) ([]User, error) {
    var users []User
    err := db.Model(&User{}).Preload("CreditCard").Find(&users).Error
    return users, err
}

Has Many

has many 与另一个模型建立了一对多的连接。 不同于 has one,拥有者可以有零或多个关联模型。

例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。

// User 有多张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCards []CreditCard
}

type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}
/*检索*/
// 检索用户列表并预加载信用卡
func GetAll(db *gorm.DB) ([]User, error) {
    var users []User
    err := db.Model(&User{}).Preload("CreditCards").Find(&users).Error
    return users, err
}

Many To Many

Many to Many 会在两个 model 中添加一张连接表。

例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。

// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}

type Language struct {
  gorm.Model
  Name string
}
//当使用 GORM 的 AutoMigrate 为 User 创建表时,GORM 会自动创建连接表

关联下的特性

【自动创建、更新】

在创建、更新记录时,GORM 会通过 Upsert( update 和 insert 的组合) 自动保存关联及其引用记录。

/*自动创建*/
user := User{
  Name:            "jinzhu",
  BillingAddress:  Address{Address1: "Billing Address - Address 1"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
  Emails:          []Email{
    {Email: "jinzhu@example.com"},
    {Email: "jinzhu-2@example.com"},
  },
  Languages:       []Language{
    {Name: "ZH"},
    {Name: "EN"},
  },
}

db.Create(&user)
// BEGIN TRANSACTION;
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "languages" ("name") VALUES ('ZH'), ('EN') ON DUPLICATE KEY DO NOTHING;
// INSERT INTO "user_languages" ("user_id","language_id") VALUES (111, 1), (111, 2) ON DUPLICATE KEY DO NOTHING;
// COMMIT;
db.Save(&user)

/*自动更新--使用 FullSaveAssociations 模式来更新关联的数据*/
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&user)
// ...
// INSERT INTO "addresses" (address1) VALUES ("Billing Address - Address 1"), ("Shipping Address - Address 1") ON DUPLICATE KEY SET address1=VALUES(address1);
// INSERT INTO "users" (name,billing_address_id,shipping_address_id) VALUES ("jinzhu", 1, 2);
// INSERT INTO "emails" (user_id,email) VALUES (111, "jinzhu@example.com"), (111, "jinzhu-2@example.com") ON DUPLICATE KEY SET email=VALUES(email);
// ...

这些关联也可以关闭,具体查看官方文档。

关联下的增删改查

先看

//查找所有匹配的关联记录
db.Model(&user).Association("Languages").Find(&languages)
//查找带条件的关联
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages)

db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages)
//查询关联的数量(条件计数为例)
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()

随后

//为 many to many、has many 添加新的关联(当然这个也可以用下面的进行改)
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})

db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})

//为 has one, belongs to 替换当前的关联
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})

db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)

最后是,但是注意,如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。:

db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)