惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 聂微东
IT之家
IT之家
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
博客园 - 【当耐特】
The Cloudflare Blog
宝玉的分享
宝玉的分享
罗磊的独立博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Visual Studio Blog
小众软件
小众软件
博客园_首页
Last Week in AI
Last Week in AI
J
Java Code Geeks
V
V2EX
雷峰网
雷峰网
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
腾讯CDC
博客园 - 司徒正美
Engineering at Meta
Engineering at Meta
The GitHub Blog
The GitHub Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
DataBreaches.Net
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
云风的 BLOG
云风的 BLOG
The Register - Security
The Register - Security
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
T
The Blog of Author Tim Ferriss
N
Netflix TechBlog - Medium
F
Full Disclosure
B
Blog
H
Help Net Security
C
Check Point Blog
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Jina AI
Jina AI
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
L
LangChain Blog
P
Proofpoint News Feed
D
Docker
Microsoft Security Blog
Microsoft Security Blog

Chenxu's Blog

RAG检索优化:从 68% 到 82.2% 学习深度学习 北邮计算机研究生选课指北 学习机器学习 上海篇 提出一个好问题 写代码的环境 选购一台合适的设备 计算机的第零课 序言 内蒙古篇 山东篇 一篇对transformers的疑惑 浅析GPUStack 浅谈后端项目分层 浙江篇 河南篇 一些三端协同的开发工具记录 浅谈编程语言中的 GC 在 Windows 上安装 Rust 美化博客 消息队列 MyBatis 现代交换原理2024年题目 回忆版 北邮计科本科课程指北 ClkLog埋点用户分析系统研究报告 大数据梧桐实验分享 大数据HBase实验分享 国产向量数据库研究实践 —— 基于 Milvus 的电影推荐系统 服务器环境配置 大数据HDFS实验分享 SSE多服务之间推送数据 数据库系统原理 操作系统 编译原理 手把手教你gozero从开发到部署 (番外篇): 介绍 gtodolist 前端项目 天津篇 北京篇 手把手教你gozero从开发到部署 (5): gtodolist 之任务的删除 前端相关问题 vue3 + element-plus学习 git相关命令 人工智能原理 使用 MongoTemplate 时开启事务 MySQL Redis 力扣刷题笔记之动态规划 记录一次 VSCode 出现的各种奇怪的问题 手把手教你gozero从开发到部署 (4): 在 model 中自己编写函数实现数据库分页查询 记录平时用到的 windows 右键管理 记录平时使用的 linux 的指令 安装 gstore 并使用 java api 手把手教你gozero从开发到部署 (3): gtodolist 之任务的创建和修改 手把手教你gozero从开发到部署 (番外篇): 记录第一次部署 CI/CD 的流程 记录完成 gtodolist 中遇到的 bug 手把手教你gozero从开发到部署 (2): gtodolist 之 user 模块开发 手把手教你gozero从开发到部署 (1): gtodolist 项目说明和环境准备 快速入门 gozero 框架 vue部署在nginx上的相关问题 记一次安装scrapy的报错 将博客部署至服务器 Java CS143: Compliers 《MySQL必知必会》读书笔记(2) 初识 gin 框架 Spring 《MySQL必知必会》读书笔记(1) 本地搭建博客 🤝友链 🙋🏻‍♂️关于 Browser and Device Check
初识 GORM
Chenxu · 2023-07-01 · via Chenxu's Blog

推荐阅读官方教程

安装

和 gin 一样, 使用前需要先使用以下命令安装:

本篇以 MySQL 举例, 所以还需要安装 MySQL 的驱动:

go get -u gorm.io/driver/mysql

连接

连接数据库只需要一句话即可:

dsn := "user:password@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
	panic(err)
}

建表

连接以后可以使用 AutoMigrate 创建数据表:

db.AutoMigrate(&User{}) // User是一个结构体

User 定义如下:

type User struct {
	gorm.Model
	Name     string
	Age      int `gorm:"default:12"` // 表示默认值为12
	Salary   float32
	Birthday time.Time
}

gorm.Model 是 gorm 官方模型, 其定义如下:

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

gorm.Model 中的值会在修改数据库时自动填充

gorm 默认表名为结构体的蛇形复数, 字段名为蛇形命名, id 为自增主键, 比如上述模型的数据表为:

你也可以自定义表名:

type Tabler interface {
    TableName() string
}

// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
  return "profiles"
}

除了上面展示的 default 标签, 你也可以自定义以下标签:

增删改查

增加

gorm 可以单个增加:

user := User{Name: "张三", Age: 13, Salary: 12.0, Birthday: time.Now()}
db.Debug().Create(&user) // 打印sql语句

也可以增加多个:

users := make([]User, 0)
users = append(users, User{Name: "张三", Salary: 12.0, Birthday: time.Now()})
users = append(users, User{Name: "张三", Salary: 15.0, Birthday: time.Now()})
users = append(users, User{Name: "李四", Salary: 15.0, Birthday: time.Now()})
db.Debug().Create(&users)

但是注意: 当结构体中字段值为空或者零时, 此时插入数据库则会填入默认值

user := User{Name: "张三", Age: 0, Salary: 12.0, Birthday: time.Now()}
db.Debug().Create(&user)

比如上述代码打印的 sql 语句如下:

可以看到图中插入的 sql 语句在 Age 处的值为默认值 12, 并不是我们想要的 0, 针对此问题我们有两种方法解决:

  1. 使用指针类型变量

    将字段类型改为指针, 结构体定义如下:

    type User struct {
        gorm.Model
        Name     string
        Age      *int `gorm:"default:12"`
        Salary   float32
        Birthday time.Time
    }
    

    go 语句如下:

    // 指针类型的零值需要使用 new()
    user := User{Name: "张三", Age: new(int), Salary: 12.0, Birthday: time.Now()}
    db.Debug().Create(&user)
    

    sql 语句如下:

    可以看到 Age 字段的值变为了 0

  2. 使用 Scanner/Valuer 接口

    结构体定义如下:

    type User struct {
    	gorm.Model
    	Name     string
    	Age      sql.NullInt32 `gorm:"default:12"`
    	Salary   float32
    	Birthday time.Time
    }
    

    可以查看 sql.NullInt32 的定义如下, 可以知道这是一个结构体, 当 Validtrue 时, 使用 Int32 的值; 否则认为传入的值为空, 使用默认值

    // NullInt32 represents an int32 that may be null.
    // NullInt32 implements the Scanner interface so
    // it can be used as a scan destination, similar to NullString.
    type NullInt32 struct {
    	Int32 int32
    	Valid bool // Valid is true if Int32 is not NULL
    }
    
    // Scan implements the Scanner interface.
    func (n *NullInt32) Scan(value any) error {
    	if value == nil {
    		n.Int32, n.Valid = 0, false
    		return nil
    	}
    	n.Valid = true
    	return convertAssign(&n.Int32, value)
    }
    
    // Value implements the driver Valuer interface.
    func (n NullInt32) Value() (driver.Value, error) {
    	if !n.Valid {
    		return nil, nil
    	}
    	return int64(n.Int32), nil
    }
    

    go 语句如下:

    user := User{Name: "张三", Age: sql.NullInt32{0, true}, Salary: 12.0, Birthday: time.Now()}
    db.Debug().Create(&user)
    

    sql 语句如下:

删除

当你的结构体定义中有 go.Model 时, 此时你将获取软删除功能, 你的删除操作都是将 delete_at 设定为删除事件, 当你执行查询操作时会默认加上一个 delete_al is null 的条件

// 删除单个 user的id作为条件
// UPDATE users SET deleted_at="2023-07-01 17:03" WHERE id = 67;
db.Delete(&user)

// 批量删除
// UPDATE users SET deleted_at="2023-07-01 17:13" WHERE age = 15;
db.Where("age = ?", 15).Delete(&User{})

// 查询记录时会忽略被软删除的记录
// SELECT * FROM users WHERE age = 15 AND deleted_at IS NULL;
db.Where("age = 15").Find(&user)

// Unscoped 方法可以查询被软删除的记录
// SELECT * FROM users WHERE age = 15;
db.Unscoped().Where("age = 15").Find(&users)

// 永久删除 user的id作为条件
// DELETE FROM orders WHERE id=78;
db.Unscoped().Delete(&user)

修改

更新所有字段

db.First(&user)
user.Name = "李四"
*user.Age = 17
// UPDATE `users` SET `created_at`='2023-07-01 17:58:57.788',`updated_at`='2023-07-01 17:58:57.803',`deleted_at`=NULL,`name`='李四',`age`=17,`salary`=12.000000,`birthday`='2023-07-01 17:58:57.788' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Save(&user)
db.First(&user)

更新部分字段

// 更新单个属性,如果它有变化
// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.805' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Update("name", "赵六")
// 根据给定的条件更新单个属性
// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.807' WHERE age = 12 AND `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Where("age = ?", 12).Update("name", "赵六")
// 使用 map 更新多个属性,只会更新其中有变化的属性
// UPDATE `users` SET `age`=18,`name`='张三',`updated_at`='2023-07-01 17:58:57.808' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Updates(map[string]interface{}{"name": "张三", "age": 18})

更新选定字段

// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.809' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Select("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})
//  UPDATE `users` SET `age`=17,`updated_at`='2023-07-01 17:58:57.811' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
db.Debug().Model(&user).Omit("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})

批量更新

// UPDATE `users` SET `age`=18,`name`='李' WHERE id IN (151,155)
db.Debug().Table("users").Where("id IN (?)", []int{151, 155}).Updates(map[string]interface{}{"name": "李", "age": 18})
// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.816',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
db.Debug().Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"})
// 使用 `RowsAffected` 获取更新记录总数
// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.818',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
rows := db.Debug().Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"}).RowsAffected
fmt.Println(rows)

查询

查询条件

String 条件
// SELECT * FROM users WHERE name = '张三' ORDER BY id LIMIT 1;
db.Where("name = ?", "张三").First(&user)
// SELECT * FROM users WHERE name = '张三' ORDER BY id;
db.Where("name = ?", "张三").Find(&users)
Struct 和 Map 条件
// Struct
// SELECT * FROM users WHERE name = "张三" AND age = 15 ORDER BY id LIMIT 1;
age := 15
db.Debug().Where(&User{Name: "张三", Age: &age}).First(&user)
// Map
// SELECT * FROM users WHERE name = "张三" AND age = 15;
db.Where(map[string]interface{}{"name": "张三", "age": 15}).Find(&users)
// 主键切片条件
// SELECT * FROM users WHERE id IN (20, 21, 22);
db.Where([]int64{20, 21, 22}).Find(&users)
内联条件
// SELECT * FROM users WHERE name = "李四";
db.Find(&user, "name = ?", "李四")
NOT 条件
// SELECT * FROM users WHERE NOT name = "张三" ORDER BY id LIMIT 1;
db.Not("name = ?", "张三").First(&user)

// Not In
// SELECT * FROM users WHERE name NOT IN ("张三", "李四");
db.Not(map[string]interface{}{"name": []string{"张三", "李四"}}).Find(&users)
OR 条件
// SELECT * FROM users WHERE name = '李四' OR name = '王五';
db.Where("name = ?", "李四").Or("name = ?", "王五").Find(&users)

选取特定字段

// SELECT name, age FROM users;
db.Select("name", "age").Find(&users)

其他还有诸如 limit , offset, group 等查询就不一一列举, 详情可以查看官网的 查询高级查询

总结

增加用 Create() 就行, 删除修改和查询实际上都是使用 Where() 加附加条件, 最后再分别调用 Delete(), Update(), Updates(), First(), Find() 执行最终操作即可

程序清单

package main

import (
	"fmt"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model
	Name     string
	Age      *int `gorm:"default:12"`
	Salary   float32
	Birthday time.Time
}

func main() {
    // 修改你的 用户名 密码 连接地址 数据库名
	dsn := "user:password@tcp(127.0.0.1:3306)/db?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

	db.AutoMigrate(&User{})

	// 增加

	// 单个增加
	user := User{Name: "张三", Age: new(int), Salary: 12.0, Birthday: time.Now()}
	db.Create(&user)

	// 增加多个
	users := make([]User, 0)
	users = append(users, User{Name: "张三", Salary: 12.0, Birthday: time.Now()})
	users = append(users, User{Name: "李四", Salary: 15.0, Birthday: time.Now()})
	users = append(users, User{Name: "王五", Salary: 15.0, Birthday: time.Now()})
	db.Create(&users)

	// 修改

	// 更新所有字段
	db.First(&user)
	user.Name = "李四"
	*user.Age = 17
	// UPDATE `users` SET `created_at`='2023-07-01 17:58:57.788',`updated_at`='2023-07-01 17:58:57.803',`deleted_at`=NULL,`name`='李四',`age`=17,`salary`=12.000000,`birthday`='2023-07-01 17:58:57.788' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Save(&user)
	db.First(&user)

	// 更新部分字段
	// 更新单个属性,如果它有变化
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.805' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Update("name", "赵六")
	// 根据给定的条件更新单个属性
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.807' WHERE age = 12 AND `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Where("age = ?", 12).Update("name", "赵六")
	// 使用 map 更新多个属性,只会更新其中有变化的属性
	// UPDATE `users` SET `age`=18,`name`='张三',`updated_at`='2023-07-01 17:58:57.808' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Updates(map[string]interface{}{"name": "张三", "age": 18})

	// 更新选定字段
	// UPDATE `users` SET `name`='赵六',`updated_at`='2023-07-01 17:58:57.809' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})
	//  UPDATE `users` SET `age`=17,`updated_at`='2023-07-01 17:58:57.811' WHERE `users`.`deleted_at` IS NULL AND `id` = 199
	db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "赵六", "age": 17})

	// 批量更新
	// UPDATE `users` SET `age`=18,`name`='李' WHERE id IN (151,155)
	db.Table("users").Where("id IN (?)", []int{151, 155}).Updates(map[string]interface{}{"name": "李", "age": 18})
	// 使用 struct 更新时,只会更新非零值字段,若想更新所有字段,请使用map[string]interface{}
	// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.816',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
	db.Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"})
	// 使用 `RowsAffected` 获取更新记录总数
	// UPDATE `users` SET `updated_at`='2023-07-01 17:58:57.818',`name`='赵六' WHERE 1 = 1 AND `users`.`deleted_at` IS NULL
	rows := db.Model(User{}).Where("1 = 1").Updates(User{Name: "赵六"}).RowsAffected
	fmt.Println(rows)

	// 查询

	// SELECT * FROM users WHERE name = '张三' ORDER BY id LIMIT 1;
	user = User{}
	db.Where("name = ?", "张三").First(&user)
	// SELECT * FROM users WHERE name = '张三' ORDER BY id;
	user = User{}
	db.Where("name = ?", "张三").Find(&users)

	// Struct
	// SELECT * FROM users WHERE name = "张三" AND age = 15 ORDER BY id LIMIT 1;
	age := 12
	user = User{}
	db.Where(&User{Name: "张三", Age: &age}).First(&user)
	// Map
	// SELECT * FROM users WHERE name = "张三" AND age = 15;
	db.Where(map[string]interface{}{"name": "张三", "age": 15}).Find(&users)
	// 主键切片条件
	// SELECT * FROM users WHERE id IN (20, 21, 22);
	db.Where([]int64{20, 21, 22}).Find(&users)

	// SELECT * FROM users WHERE name = "李四";
	user = User{}
	db.Find(&user, "name = ?", "李四")

	// SELECT * FROM users WHERE NOT name = "张三" ORDER BY id LIMIT 1;
	user = User{}
	db.Not("name = ?", "张三").First(&user)
	// Not In
	// SELECT * FROM users WHERE name NOT IN ("张三", "李四");
	db.Not(map[string]interface{}{"name": []string{"张三", "李四"}}).Find(&users)

	// SELECT * FROM users WHERE name = '张三' OR name = '李四';
	db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users)

	// SELECT name, age FROM users;
	db.Select("name", "age").Find(&users)

	// 删除

	// 删除单个 user里的值当作条件
	// UPDATE users SET deleted_at="2023-07-01 17:03" WHERE id = 67;
	db.Delete(&user)
	// 批量删除
	// UPDATE users SET deleted_at="2023-07-01 17:13" WHERE age = 15;
	db.Where("age = ?", 15).Delete(&User{})

	// 查询记录时会忽略被软删除的记录
	// SELECT * FROM users WHERE age = 15 AND deleted_at IS NULL;
	db.Where("age = 15").Find(&user)

	// Unscoped 方法可以查询被软删除的记录
	// SELECT * FROM users WHERE age = 15;
	db.Unscoped().Where("age = 15").Find(&users)

	// 永久删除
	// DELETE FROM orders WHERE id=78;
	db.Unscoped().Delete(&user)

	// 删库跑路
	db.Unscoped().Where("1 = 1").Delete(&User{})
}

参考文章

gorm 官方教程

GORM CRUD 指南