青训营X豆包MarsCode 技术训练营第六课 | GROM增删改查

102 阅读8分钟

GORM中文文档

基本的GORM示例

安装

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

也可以是mysqlsqlseverpostgres

demo

package main
​
import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)
​
type User struct {
    gorm.Model
    Name string
    Age  int
}
​
func main() {
    
    //连接数据库
    dsn := "root:yuchao123@tcp(127.0.0.1:3306)/work?charset=utf8mb4"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
​
    //自动创建数据库表来匹配User结构体
    db.AutoMigrate(&User{})
​
    //create
    db.Create(&User{Name: "yc", Age: 15})
​
}
  • &gorm.Config{}:是GORM中用于配置数据库连接的结构体,允许你在打开数据库时自定义一些选项

  • db.AutoMigrate:自动创建于该结构体匹配的数据表,如果表已存在且与结构体字段不匹配,则会更新字段。但是不会删除。

  • gorm.Model:是GORM自带的结构体,里面包含了IDCreateAtUpdateAtDeleteAt 四个参数,用来记录数据的主键,创建时间,修改时间,删除时间(GORM删除数据时不会直接删除,而是将删除时间设置)

    如果创建时间与实际时间不符,有可能是数据库时区不对,只需在dsn中加上 parseTime=true&loc=Local

CRUD

创建

创建记录

使用 Create() 创建记录

func (db *DB) Create(value interface{}) (tx *DB)
db.Create(&User{Name: "yc", Age: 15})
​
//也可以创建多条记录
db.Create([]*User{
    {Name:"yc", Age:10},
    {Name:"ycc", Age:11}
})

使用指定字段创建

为指定字段赋值

db.Select("Name", "Age").Create(&User)

忽略指定字段的值

db.Omit("Name", "Age").Create(&User) //会忽略User中name和age的值来创建记录

使用map创建

GORM支持使用 map[string]interface{}[]map[string]interface{} 来创建记录

db.Model(&User{}).Create(map[string]interface{}{
  "Name": "jinzhu", "Age": 18,
})
​
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
  {"Name": "jinzhu_1", "Age": 18},
  {"Name": "jinzhu_2", "Age": 20},
})

使用map创建不会执行钩子方法,关联不会被保存且不会写回主键

创建记录时使用sql表达式

需要使用map创建记录才能使用sql语句

db.Model(&User{}).Create(map[string]interface{
    "Name" : "sql",
    "Location": clause.Expr{SQL: "ST_PointFromText(?)", Vars: []interface{}{"POINT(100     100)"}},
})
  • clause.Expr :是一个结构体,用于表示sql表达式及其参数这一特性使得在构建复杂 SQL 查询时,可以直接使用一些特定的 SQL 函数、操作符或其他表达式。

    type Expr struct {
        SQL                string
        Vars               []interface{}
        WithoutParentheses bool
    }
    
  • db.Model :用于指定模型对像即数据库表,在这里表示对User表进行操作

也可以使用自定义数据类型

// Create from customized data type
type Location struct {
    X, Y int
}
​
// Scan implements the sql.Scanner interface
func (loc *Location) Scan(v interface{}) error {
  // Scan a value into struct from database driver
}
​
func (loc Location) GormDataType() string {
  return "geometry"
}
​
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  return clause.Expr{
    SQL:  "ST_PointFromText(?)",
    Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  }
}
​
type User struct {
  Name     string
  Location Location
}
​
db.Create(&User{
  Name:     "jinzhu",
  Location: Location{X: 100, Y: 100},
})
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))

此方法自定义了结构体Location,实现了Scan,GormDataType,GormValue三个方法,GORM会在创建时自动调用这三个方法(自动实现函数名),其中

  • Scan 方法允许 GORM 将从数据库中读取到的地理数据转换为 Location 类型。具体实现未提供,可以根据需要解析数据库返回的地理数据。
  • GormDataType 方法指定了 Location 在数据库中的数据类型为 geometry
  • GormValue 方法构建 SQL 表达式,生成用于插入的 WKT 表达式。

关联创建

关联创建时,如果关联值非零,这些关联会被upsert,并且他们的Hooks 方法也会被调用,如果不需要更新关联制,可以用 Omit() 方法忽略掉不需要的字段

type CreditCard struct {
    gorm.Model
    Number string
    UserID int
}
​
type User struct {
    gorm.Model
    UserID     int
    CreditCard CreditCard
}
​
func main() {
​
    dsn := "root:yuchao123@tcp(127.0.0.1:3306)/work?charset=utf8mb4&parseTime=true&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
​
    db.AutoMigrate(&User{})
​
    db.Omit("CreditCard").Create(&User{UserID: 1012, CreditCard: CreditCard{Number: "1016", UserID: 1012}})
​
}

默认值

tag定义字段的默认值

type User struct {
    gorm.Model
    Name string `gorm:"default:'KQLXK'"`
    Age  int `gorm:"default:10"`
}

查询

一般来说没有字段名的都是对主键进行的操作

一般查询

// 根据主键查询第一条记录
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.Find(&users)
//// SELECT * FROM users;// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

where条件

普通sql查询

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;
​
// Get all matched records
db.Where("name = ?", "jinzhu").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu';
​
// <>
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 LIMIT 1;
​
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
​
// 主键的切片
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);

not条件

db.Not("name", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;
​
// Not In
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
​
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
//// SELECT * FROM users WHERE id NOT IN (1,2,3);
​
db.Not([]int64{}).First(&user)
//// SELECT * FROM users;
​
// Plain SQL
db.Not("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE NOT(name = "jinzhu");
​
// Struct
db.Not(User{Name: "jinzhu"}).First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu";

or条件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(*User{})
// SELECT * from user where role = 'admin' or role = 'super_admin'
​
db.Where("name" = "KQLXK").Or(User{Name: "KQLXK2"}).Find(&User{})
//select * from user where name ='KQLXK' or name = 'KQLXK2'
​
db.Where("name" = "KQLXK").Or(map[string]interface{}{"Name":"KQLXK2"}).Find(&User{})
//select * from user where name ='KQLXK' or name = 'KQLXK2'

内联条件

// 根据主键获取记录 (只适用于整形主键)
db.First(&user, 23)
//// SELECT * FROM users WHERE id = 23 LIMIT 1;
// 根据主键获取记录, 如果它是一个非整形主键
db.First(&user, "id = ?", "string_primary_key")
//// SELECT * FROM users WHERE id = 'string_primary_key' LIMIT 1;
​
// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
//// SELECT * FROM users WHERE name = "jinzhu";
​
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
​
// Struct
db.Find(&users, User{Age: 20})
//// SELECT * FROM users WHERE age = 20;
​
// Map
db.Find(&users, map[string]interface{}{"age": 20})
//// SELECT * FROM users WHERE age = 20;

子查询

db.Where("amount > ?", db.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").SubQuery()).Find(&orders)
// SELECT * FROM "orders"  WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FRO" where(state = 'paid')

更新

更新所有字段

Save()默认会更新该对象的所有字段,即使你没有赋值。

解释db.First(&user)
​
user.Name = "七米"
user.Age = 99
db.Save(&user)
​
////  UPDATE `users` SET `created_at` = '2020-02-16 12:52:20', `updated_at` = '2020-02-16 12:54:55', `deleted_at` = NULL, `name` = '七米', `age` = 99, `active` = true  WHERE `users`.`deleted_at` IS NULL AND `users`.`id` = 1

更新修改字段

如果你只希望更新指定字段,可以使用Update或者Updates

解释// 更新单个属性,如果它有变化
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
​
// 根据给定的条件更新单个属性
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;
​
// 使用 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;
​
// 使用 struct 更新多个属性,只会更新其中有变化且为非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
​
// 警告:当使用 struct 更新时,GORM只会更新那些非零值的字段
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
db.Model(&user).Updates(User{Name: "", Age: 0, Active: false})

更新选定字段

如果你想更新或忽略某些字段,你可以使用 SelectOmit

解释db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' 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;

删除

删除记录

警告 删除记录时,请确保主键字段有值,GORM 会通过主键去删除记录,如果主键为空,GORM 会删除该 model 的所有记录。

解释// 删除现有记录
db.Delete(&email)
//// DELETE from emails where id=10;
​
// 为删除 SQL 添加额外的 SQL 操作
db.Set("gorm:delete_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Delete(&email)
//// DELETE from emails where id=10 OPTION (OPTIMIZE FOR UNKNOWN);

批量删除

删除全部匹配的记录

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%";

软删除

如果一个 model 有 DeletedAt 字段,他将自动获得软删除的功能! 当调用 Delete 方法时, 记录不会真正的从数据库中被删除, 只会将DeletedAt 字段的值会被设置为当前时间

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;
​
// 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;