SQL与GORM实践 | 青训营笔记

641 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记

GORM基础使用

功能:

  • 关联:一对一,一对多,单表自关联、多态;Preload、Joins预加载、级联删除;关联模式;自定义关联表
  • 事务:事务代码块、嵌套事务、Save Point
  • 多数据库、读写分离、命名参数、Map、子查询、分组条件、代码共享、SQL表达式(查询、创建、更新)、自动选字段、查询优化器
  • 字段权限、软删除、批量数据处理、Prepared Stmt、自定义类型、命名策略、虚拟字段、自动track时间、SQL Builder、Logger
  • 代码生成、复合主键、Constraint、Prometheus、Auto Migration
  • 多模式灵活自由扩展
  • 开发友好

基本用法

初始化DB连接

import _"github.com/go-sql-driver/mysql"
func main(){
    //通过driver+DSN初始化连接
    db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")
}

CRUD

//操作数据库,依据go的结构体自动迁移在数据库中创建表
//表中的字段就是结构体内的元素
db.AutoMigrate(&Product{})
db.Migrator().CreateTable(&Product{})

//创建,注意传递的是地址,也就是执行结构体实例的指针
user := User{Name: "Tom", Age: 18, Birthday: time.Now()}
result := db.Create(&user)

//批量创建,可以通过CreateInBatches指定批次的数量
var users = []User{{Name: "Amy"}, {Name: "Bob"}}
db.Create(&users)
db.CreateInBatches(users, 100)

//读取 获取第一条记录(主键升序)
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;

//更新
// 条件更新 
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;  

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

//删除
db.Delete(&user)

注:传入的都是地址或指针

模型定义

gorm自带了Model模型的结构体定义,可以嵌入到任何结构体中,Model模型:

type Model struct{
    ID        uint            `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt  `gorm:"index"`
}

其中ID就是默认主键,全局唯一,会自增,DeletedAt是为了方便软删除,拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。

可以使用 Unscoped 找到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users) 
// SELECT * FROM users WHERE age = 20;

也可以使用 Unscoped 永久删除匹配的记录

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

模型定义的管理约定(约定优于配置)

  • 表名为struct namesnake_cases复数格式
  • 字段名为field namesnake_case单数格式
  • ID/Id字段为主键,如果为数字,则为自增主键
  • CreatedAt字段,创建时,保存当前时间
  • UpdateAt字段,创建、更新时保存当前时间
  • gorm.DeletedAt字段,默认开启soft delete模式

关联

包含一对一,一对多(一般通过gorm设置外键tag实现),多对多(通过gorm设置一个tag来生成关联中间表)

说明:User拥有一个Account(has one,一对一),拥有多个Pets(has many,一对多),多个Toys(多态has many,User可以拥有Toy,Pet也可以拥有Toy),属于某Company(belongs to),属于某Manager(单表,belongs to),管理Team(单表,has many),会多种Language(manytomany,多对多),拥有很多Friend(单表,many to many)

type User struct{
    gorm.Model
    Name       string
    Account    Account
    Pets       []*Pet
    Toys       []Toy      `gorm:"polymorphic:Owner"` //polymorphic,多态的tag
    CompanyID  *int
    Company    Company
    ManagerID  *uint
    Manager    *User
    //外键关联实现一对多,ManagerID作为外键
    Team       []User     `gorm:"foreignkey:ManagerID"`
    //many2many实现多对多,会生成一张userspeak中间表
    Languages  []Language `gorm:"many2many:UserSpeak;"`
    Friends    []*User    `gorm:"many2many:user_friends;`
}

type Pet struct{
    gorm.Model
    UserID *uint
    //多态
    Toy    Toy    `gorm:"polymorphic:Owner;"`
}

type Toy struct{
    ID          uint
    Name        string
    OwnerID     string
    OwnerType   string
    CreatedAt   time.Time
}

关联操作--CRUD

保存用户及其关联

db.Save(&User{
    Name:      "Tom",
    Languages: []Language{{Name: "zh-CN"}, {Name: "en-US"}},
})

关联模式

langAssociation := db.Model(&user).Association("Languages")

查询关联

langAssociation.Find(&languages)

将汉语,英语添加到用户掌握的语言中

langAssociation.Append([]Language{languageZH, languageEN})

把用户掌握的语言换为汉语,德语

langAssociation.Replace([]Language{languageZH, languageDE})

删除用户掌握的汉语和英语

langAssociation.Delete(languageZH, languageEN)

删除用户掌握的所有语言

langAssociation.Clear()

返回用户掌握的语言的数量

langAssociation.Count()

关联操作--Preload/Joins预加载

结构体

type User struct{
    Orders  []Order
    Profile Profile
}

查询用户的时候并找出其订单和个人信息

db.Preload("Orders").Preload("Profile").Find(&user)
//SELECT * FROM users;
//SELECT * FROM orders WHERE user_id IN (1,2,3,4);//一对多
//SELECT * FROM profiles WHRER user_id IN (1,2,3,4);//一对一

使用join SQL加载

db.Joins("Company").Joins("Manager").First(&user, 1)
db.Joins("Company", db.Where(&Company{Alive: True})).Find(&user)

预加载全部关联(只加载一级关联)

db.Preload(clause.Associations).Find(&user)

多级预加载

db.Preload("Orders.OrderItems.Product").Find(&users)

多级预加载+预加载全部一级关联

db.Preload("Order.OrderItems.Product").Preload(clause.Associations).Find(&user)

关联操作--级联删除

结构体:

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;"`
}

方法一:使用数据库约束自动删除

//如果未启动软删除,在删除User时会自动删除其依赖。
db.Delete(&User{})

方法二:使用Select实现级联删除,不依赖数据库约束及软删除

//删除user时也删除其相应的account
db.Select("Account").Delete(&user)

//删除user时,也删除user的Orders, CreditCards记录
db.Select("Orders", "CreditCards").Delete(&user)