
gorm初体验
特殊设置
显示sql
go
//第一种 db后面加Debug()
var user User
db.Debug().Take(&user, 1)
fmt.Printf("user: %+v\n", user)
//第二种 全局设置
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
指定表名
因为gorm默认是按照对象的驼峰转下划线后面加s当做表名的,比如 TestTime -> test_times , 如果我们想要显示的指定也是可以的。
go
db.Table("test_time").First(&result1)
数据库表
sql
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`age` tinyint(3) unsigned NOT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `articles` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL,
`content` text COLLATE utf8mb4_unicode_ci,
`user_id` bigint(20) unsigned NOT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_articles_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
mock数据
sql
INSERT INTO `test`.`users`(`id`, `name`, `email`, `age`, `created_at`, `updated_at`) VALUES (1, '张三', 'zhangsan@example.com', 25, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`users`(`id`, `name`, `email`, `age`, `created_at`, `updated_at`) VALUES (2, '李四', 'lisi@example.com', 30, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`users`(`id`, `name`, `email`, `age`, `created_at`, `updated_at`) VALUES (3, '赵六', 'zhaoliu@example.com', 35, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`articles`(`id`, `title`, `content`, `user_id`, `created_at`, `updated_at`) VALUES (1, 'GORM使用指南', '这是一篇关于GORM的详细教程...', 1, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`articles`(`id`, `title`, `content`, `user_id`, `created_at`, `updated_at`) VALUES (2, 'Go语言入门', '学习Go语言的基础知识...', 1, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`articles`(`id`, `title`, `content`, `user_id`, `created_at`, `updated_at`) VALUES (3, 'Web开发实战', '使用Go进行Web开发...', 2, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
INSERT INTO `test`.`articles`(`id`, `title`, `content`, `user_id`, `created_at`, `updated_at`) VALUES (4, '数据库设计原则', '良好的数据库设计实践...', 3, '2025-09-16 12:53:22', '2025-09-16 12:53:22');
插入数据
go
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 定义用户模型
type User struct {
ID uint `gorm:"primaryKey"` // 主键
Name string `gorm:"size:100;not null"` // 用户名,长度限制100,非空
Email string `gorm:"size:100;uniqueIndex"` // 邮箱,长度限制100,唯一索引
Age uint8 `gorm:"check:age > 0"` // 年龄,检查约束
Articles []Article `gorm:"foreignKey:UserID"` // 用户拥有的文章列表
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
// 定义文章模型(与用户是一对多关系)
type Article struct {
ID uint `gorm:"primaryKey"` // 主键
Title string `gorm:"size:200;not null"` // 文章标题
Content string `gorm:"type:text"` // 文章内容,使用文本类型
UserID uint `gorm:"not null"` // 外键,关联用户ID
User User `gorm:"foreignKey:UserID"` // 属于用户关系
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
func main() {
// MySQL 连接配置
// 格式: "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 获取底层的 sql.DB 对象并设置连接池
sqlDB, err := db.DB()
if err != nil {
log.Fatal("Failed to get database instance:", err)
}
// 设置连接池参数
sqlDB.SetMaxIdleConns(10) // 最大空闲连接数
sqlDB.SetMaxOpenConns(100) // 最大打开连接数
sqlDB.SetConnMaxLifetime(time.Hour) // 连接的最大可复用时间
// 插入Mock数据
insertMockData(db)
// 关闭数据库连接
if err := sqlDB.Close(); err != nil {
log.Fatal("Failed to close database:", err)
}
}
// 插入Mock数据
func insertMockData(db *gorm.DB) {
// 先清空表
db.Exec("DELETE FROM articles")
db.Exec("DELETE FROM users")
// 创建用户
users := []User{
{Name: "张三", Email: "zhangsan@example.com", Age: 25},
{Name: "李四", Email: "lisi@example.com", Age: 30},
{Name: "赵六", Email: "zhaoliu@example.com", Age: 35},
}
for i := range users {
result := db.Create(&users[i])
if result.Error != nil {
log.Fatal("Failed to create user:", result.Error)
}
}
// 创建文章
articles := []Article{
{Title: "GORM使用指南", Content: "这是一篇关于GORM的详细教程...", UserID: users[0].ID},
{Title: "Go语言入门", Content: "学习Go语言的基础知识...", UserID: users[0].ID},
{Title: "Web开发实战", Content: "使用Go进行Web开发...", UserID: users[1].ID},
{Title: "数据库设计原则", Content: "良好的数据库设计实践...", UserID: users[2].ID},
}
for i := range articles {
result := db.Create(&articles[i])
if result.Error != nil {
log.Fatal("Failed to create article:", result.Error)
}
}
fmt.Println("Mock数据插入完成")
}
查询数据
go
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 定义用户模型
type User struct {
ID uint `gorm:"primaryKey"` // 主键
Name string `gorm:"size:100;not null"` // 用户名,长度限制100,非空
Email string `gorm:"size:100;uniqueIndex"` // 邮箱,长度限制100,唯一索引
Age uint8 `gorm:"check:age > 0"` // 年龄,检查约束
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
func main() {
// MySQL 连接配置
// 格式: "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 示例:查询所有用户及其文章
fmt.Println("=== 所有用户及其文章 ===")
var users []User
db.Model(&User{}).Find(&users) // Preload预加载关联的文章
for _, user := range users {
fmt.Printf("用户: %s (%d岁), 邮箱: %s\n", user.Name, user.Age, user.Email)
}
}
根据id主键查询
take 只认主键
go
// 示例:用take查询,后面传id,生成的sql:SELECT * FROM `users` WHERE `users`.`id` = 1 LIMIT 1
// 如果不传id的话,sql则是 SELECT * FROM `users` LIMIT 1
// 如果传入的 &user 对象id有值的话,则会自动当做条件添加在where后面
var user User
//user.ID = 3 // SELECT * FROM `users` WHERE `users`.`id` = 1 AND `users`.`id` = 3 LIMIT 1
db.Take(&user, 1)
fmt.Printf("user: %+v\n", user)
// 第二种,效果一样
var user User
db.First(&user, 3)
fmt.Printf("user: %+v\n", user)
where 条件查询
go
// 示例:条件查询
fmt.Println("\n=== 年龄大于25岁的用户 ===")
var olderUsers []User
db.Where("age > ?", 25).Find(&olderUsers)
for _, user := range olderUsers {
fmt.Printf("用户: %s (%d岁)\n", user.Name, user.Age)
}
分页查询
go
package main
import (
"fmt"
"log"
"strings"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 定义用户模型
type User struct {
ID uint `gorm:"primaryKey"` // 主键
Name string `gorm:"size:100;not null"` // 用户名,长度限制100,非空
Email string `gorm:"size:100;uniqueIndex"` // 邮箱,长度限制100,唯一索引
Age uint8 `gorm:"check:age > 0"` // 年龄,检查约束
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
type UserQuery struct {
IDs []uint // id in ?
Name *string // name LIKE ?
Email *string // email = ?
MinAge *uint8 // age > ?
StartTime *time.Time // created_at >= ?
EndTime *time.Time // created_at <= ?
Page int `json:"page" form:"page"` // 页码
PageSize int `json:"page_size" form:"page_size"` // 每页数量
SortField *string `json:"sort_field" form:"sort_field"` // 排序字段,如 "created_at", "age"
SortOrder *string `json:"sort_order" form:"sort_order"` // 排序顺序:asc 或 desc
}
type PaginatedResult struct {
List interface{} `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
func PaginateUsers(db *gorm.DB, query *UserQuery) (*PaginatedResult, error) {
var users []User
var total int64
// 构建基础查询(带所有 WHERE 条件)
baseDB := query.BuildWhere(db.Model(&User{}))
// 第一步:获取总数(COUNT)
if err := baseDB.Count(&total).Error; err != nil {
return nil, err
}
// 第二步:获取分页数据(LIST)
page := query.Page
if page == 0 {
page = 1
}
pageSize := query.PageSize
if pageSize == 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
// 构建排序
order := query.BuildOrder()
if err := baseDB.Offset(offset).Limit(pageSize).Order(order).Find(&users).Error; err != nil {
return nil, err
}
// 计算总页数
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
return &PaginatedResult{
List: users,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
}, nil
}
func (u *UserQuery) BuildOrder() string {
// 默认排序
order := "id DESC"
// 如果传了排序字段,且在白名单中
if u.SortField != nil {
// 默认升序
direction := "ASC"
// 如果传了 desc,则降序
if u.SortOrder != nil && (strings.ToLower(*u.SortOrder) == "desc") {
direction = "DESC"
}
order = fmt.Sprintf("%s %s", *u.SortField, direction)
}
return order
}
func (u *UserQuery) BuildWhere(db *gorm.DB) *gorm.DB {
if len(u.IDs) > 0 {
db = db.Where("id IN ?", u.IDs)
}
if u.Name != nil {
db = db.Where("name LIKE ?", "%"+*u.Name+"%")
}
if u.Email != nil {
db = db.Where("email = ?", *u.Email)
}
if u.MinAge != nil {
db = db.Where("age > ?", *u.MinAge)
}
if u.StartTime != nil {
db = db.Where("created_at >= ?", *u.StartTime)
}
if u.EndTime != nil {
db = db.Where("created_at <= ?", *u.EndTime)
}
return db
}
func main() {
// MySQL 连接配置
// 格式: "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 示例查询条件
name := "john"
email := "john@example.com"
minAge := uint8(18)
startTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
endTime := time.Now()
sortField := "created_at"
sortOrder := "desc"
query := &UserQuery{
IDs: []uint{1, 2, 3},
Name: &name,
Email: &email,
MinAge: &minAge,
StartTime: &startTime,
EndTime: &endTime,
Page: 1,
PageSize: 10,
SortField: &sortField,
SortOrder: &sortOrder,
}
result, err := PaginateUsers(db, query)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total: %d\n", result.Total)
fmt.Printf("Total Pages: %d\n", result.TotalPages)
fmt.Printf("Current Page: %d\n", result.Page)
fmt.Printf("Data: %+v\n", result.List)
}
update 更新数据
有很多方法,Save、Update、UpdateColumn、Updates 不同的方法有不同的区别,如下:
- Save,有主键记录就是更新非零值,否则就是创建
- Update,可以更新零值,必须要有条件
- UpdateColumn,可以更新零值,不会走更新的Hook
- Updates,如果是结构体,则更新非零值,map可以更新零值
非空更新
go
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// User 模型对应users表
type User struct {
ID uint `gorm:"primaryKey;column:id"`
Name string `gorm:"column:name"`
Email string `gorm:"column:email"`
Age uint8 `gorm:"column:age"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName 指定表名
func (User) TableName() string {
return "users"
}
// UpdateUserByID 根据ID更新用户,只更新非零值字段
func UpdateUserByID(db *gorm.DB, user *User) error {
// 使用Updates方法,GORM会自动忽略零值字段:
result := db.Model(&User{}).Where("id = ?", user.ID).Updates(user)
return result.Error
}
// 使用示例
func main() {
dsn := "root:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("连接数据库失败: " + err.Error())
}
// 示例1:只更新Name字段(Age为0,不会被更新)
user1 := &User{
ID: 3, // 必须指定ID
Name: "张三",
Age: 0, // 零值,不会被更新
}
err = UpdateUserByID(db, user1)
if err != nil {
panic("更新失败: " + err.Error())
}
}
空值更新
第一种写法
注意:这种写法必须要有where条件!!!
go
db.Model(&User{}).Where("id = ?", user.ID).Update("age", nil)
如果真的想批量全表更新,那就用1=1这样的写法
db.Model(&User{}).Where("1=?", 1).Update("age", 12)
第二种写法
注意:这种写法必须要有where条件!!!
go
db.Model(&User{}).Where("id = ?", user.ID).Updates(map[string]interface{}{
"email": "xxx",
"status": nil,
})
事务操作
例子1
go
// 示例:事务操作 - 创建用户同时创建文章
fmt.Println("\n=== 事务操作 - 创建用户同时创建文章 ===")
err = db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些数据库操作
newUserWithArticle := User{
Name: "钱七",
Email: "qianqi@example.com",
Age: 32,
}
if err := tx.Create(&newUserWithArticle).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}
newArticle := Article{
Title: "事务操作示例",
Content: "这是一个演示事务操作的示例文章...",
UserID: newUserWithArticle.ID,
}
if err := tx.Create(&newArticle).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})
if err != nil {
log.Fatal("Transaction failed:", err)
}
fmt.Println("事务操作成功完成")
例子2
go
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 假设 db 已经通过 sql.Open 创建并配置好
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 开启事务
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil) // nil 表示使用数据库默认的事务选项
if err != nil {
log.Fatal(err)
}
// 使用事务执行操作
_, err = tx.Exec("INSERT INTO users(name) VALUES(?)", "Alice")
if err != nil {
tx.Rollback() // 出错时回滚
log.Fatal(err)
}
// 提交事务
if err := tx.Commit(); err != nil {
log.Fatal(err)
}
}
删除用户
第一种写法
go
var user User
user.ID = 3
db.Model(&User{}).Delete(&user)
第二种写法
go
db.Model(&User{}).Delete(&User{}, 4)
第三种写法
go
db.Where("age > ?", 18).Delete(&User{})
自定义映射对象
下面的写法就是通过传统的orm来映射自定义的对象。准确的来说还不属于原生SQL映射
go
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 定义用户模型
type User struct {
ID uint `gorm:"primaryKey"` // 主键
Name string `gorm:"size:100;not null"` // 用户名,长度限制100,非空
Email string `gorm:"size:100;uniqueIndex"` // 邮箱,长度限制100,唯一索引
Age uint8 `gorm:"check:age > 0"` // 年龄,检查约束
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
type UserDTO struct {
ID uint
Name string
}
func main() {
// MySQL 连接配置
// 格式: "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
var user []UserDTO
db.Model(&User{}).Where("id > ?", 4).Scan(&user)
fmt.Printf("user: %d\n", len(user))
fmt.Printf("user: %+v\n", user)
}
去重&指定查询字段
go
// 示例 1: 查询单个用户信息
var userName = &[]string{}
// 使用 Raw 执行原生 SQL 查询,去重查询
result := db.Model(&User{}).Where("id > ?", 1).Select("age").Distinct("age").Scan(userName)
// 检查 RowsAffected
if result.Error != nil {
log.Fatal("查询执行失败:", result.Error)
}
fmt.Println(*userName)
指定表名
go
原生查询
类似mybatis那种的结果映射
单对象映射
go
package main
import (
"fmt"
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// 定义用户模型
type User struct {
ID uint `gorm:"primaryKey"` // 主键
Name string `gorm:"size:100;not null"` // 用户名,长度限制100,非空
Email string `gorm:"size:100;uniqueIndex"` // 邮箱,长度限制100,唯一索引
Age uint8 `gorm:"check:age > 0"` // 年龄,检查约束
CreatedAt time.Time // 创建时间
UpdatedAt time.Time // 更新时间
}
// 定义一个结构体来映射查询结果
type UserResult struct {
Id uint `gorm:"column:id"` // 使用标签指定列名
Name string `gorm:"column:name"`
Email string `gorm:"column:email"`
Age int `gorm:"column:age"`
CreatedAt time.Time `gorm:"column:created_at"`
}
func main() {
// MySQL 连接配置
// 格式: "user:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
// 连接数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 示例 1: 查询单个用户信息
var user = &UserResult{}
userId := 5
// 使用 Raw 执行原生 SQL 查询,并通过 Scan 将结果映射到结构体
result := db.Raw("SELECT id, name, email, age, created_at FROM users WHERE id = ?", userId).Scan(user)
// 检查 RowsAffected
if result.Error != nil {
log.Fatal("查询执行失败:", result.Error)
}
if result.RowsAffected == 0 {
fmt.Printf("未找到ID为 %d 的用户\n", userId)
} else {
fmt.Printf("用户信息: %+v\n", user)
}
}
多对象映射
go
// 示例: 查询多个用户信息
var users []UserResult
userId := 2
// 使用 Raw 执行原生 SQL 查询,并通过 Scan 将结果映射到结构体
result := db.Raw("SELECT id, name, email, age, created_at FROM users WHERE id >= ?", userId).Scan(&users)
if result.Error != nil {
log.Fatal("查询执行失败:", result.Error)
} else {
for _, user := range users {
fmt.Printf("用户信息: %+v\n", user)
}
}
更新删除操作
go
// 示例 5: 执行更新操作并获取影响行数
newAge := 30
updateResult := db.Exec("UPDATE users SET age = ? WHERE age < ?", newAge, 25)
if updateResult.Error != nil {
log.Fatal("Failed to execute update:", updateResult.Error)
}
rowsAffected := updateResult.RowsAffected
fmt.Printf("更新了 %d 行数据\n", rowsAffected)