golang学习笔记——gorm

news/2025/2/22 14:56:39/文章来源:https://www.cnblogs.com/tonglin0325/p/18730884

gen是gorm官方推出的一个GORM代码生成工具

官方文档:https://gorm.io/zh_CN/gen/

1.使用gen框架生成model和dao

安装gorm gen

go get -u gorm.io/gen

假设有如下用户表

CREATE TABLE user
(`id`     bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`username`  varchar(128) NOT NULL COMMENT '用户名',`email` varchar(128) NOT NULL COMMENT '邮箱',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

在cmd目录下创建gen/generate.go文件

package mainimport ("fmt""gorm.io/driver/mysql""gorm.io/gen""gorm.io/gorm"
)const MySQLDSN = "root:123456@tcp(127.0.0.1:55000)/default?charset=utf8mb4&parseTime=True"func connectDB(dsn string) *gorm.DB {db, err := gorm.Open(mysql.Open(dsn))if err != nil {panic(fmt.Errorf("connect mysql fail: %w", err))}return db
}func main() {// 指定生成代码的具体相对目录(相对当前文件),默认为:./query// 默认生成需要使用WithContext之后才可以查询的代码,但可以通过设置gen.WithoutContext禁用该模式g := gen.NewGenerator(gen.Config{// 默认会在 OutPath 目录生成CRUD代码,并且同目录下生成 model 包// 所以OutPath最终package不能设置为model,在有数据库表同步的情况下会产生冲突// 若一定要使用可以通过ModelPkgPath单独指定model package的名称OutPath:      "./internal/dao",ModelPkgPath: "./internal/model",// gen.WithoutContext:禁用WithContext模式// gen.WithDefaultQuery:生成一个全局Query对象Q// gen.WithQueryInterface:生成Query接口Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,})// 通常复用项目中已有的SQL连接配置db(*gorm.DB)// 非必需,但如果需要复用连接时的gorm.Config或需要连接数据库同步表信息则必须设置g.UseDB(connectDB(MySQLDSN))// 从连接的数据库为所有表生成Model结构体和CRUD代码//g.ApplyBasic(g.GenerateAllTable()...)// 也可以手动指定需要生成代码的数据表//g.ApplyBasic(g.GenerateModel("user"), g.GenerateModel("role"))// 还可以指定只生成modelvar models = [...]string{"user"}for _, tableName := range models {// 只生成modelg.GenerateModel(tableName)// 生成model和query//tableModel := g.GenerateModel(tableName)//g.ApplyBasic(tableModel)}//g.ApplyInterface(func(model.Filter) {}, g.GenerateModel("user"))// 执行并生成代码g.Execute()
}

运行generate.go,将会生成model和dao文件

需要注意mysql的tinyint(1)生成的时候会映射成bool,但是tinyint(1)的实际范围为-128~127,这可能会有些问题;而tinyint(4)生成的时候会映射成int32

2.gorm框架CRUD

1.insert

func init() {SetDefault(database.DB)
}func Test_userDo_Create(t *testing.T) {user := model.User{Username: "test",Email:    "test@test",}err := User.Create(&user)if err != nil {fmt.Println("create user fail")}
}

2.delete

func Test_userDo_Delete(t *testing.T) {/*user := model.User{ID: 1,}result, err := User.Delete(&user)*/result, err := User.Where(User.ID.Eq(2)).Delete()if err != nil {fmt.Println("delete user fail")}fmt.Println(result)
}

3.update

func Test_userDo_Update(t *testing.T) {/*user := model.User{ID:       2,Username: "test2",Email:    "test2@test",}result, err := User.Updates(&user)*/result, err := User.Where(User.ID.Eq(2)).Update(User.Username, "test22")if err != nil {fmt.Println("update user fail")}fmt.Println(result.RowsAffected)
}

4.select

func Test_userDo_Scan(t *testing.T) {user := model.User{}err := User.Where(User.ID.Eq(2)).Scan(&user)if err != nil {fmt.Println("scan user fail")}fmt.Println(user)
}

参考:GORM Gen使用指南

5.Gorm处理可变结果集

在上面例子中使用scan获得查询结果的时候,字段的个数是固定的,如果当字段的个数是不定长度的时候,可以使用gorm来处理可变结果集

可以将[]interface{}或者[]orm.Params来保存查询结果,参考:Golang开发实践:把数据库数据保存到map[string]interface{}中 或者【巧妙】GO + MySQL的通用查询方法

func (repo *userRepo) ListUsers(ctx context.Context) (*model.User, error) {connect, err := repo.data.db.DB()if err != nil {repo.log.Error(fmt.Sprintf("connect fail, error: %v", err))return nil, err}rows, err := connect.Query("select * from `user`")if err != nil {repo.log.Error(fmt.Sprintf("query fail, error: %v", err))return nil, err}defer rows.Close()cols, err := rows.Columns()if err != nil {return nil, err}values := make([]interface{}, 0)for i := 0; i < len(cols); i++ {var value interface{}values = append(values, &value)}for rows.Next() {err = rows.Scan(values...)if err != nil {repo.log.Error(fmt.Sprintf("scan fail, error: %v", err))return nil, err}for k, v := range values {key := cols[k]var rawValue = *(v.(*interface{}))switch v := rawValue.(type) {case string:fmt.Print("key=>" + key + ":string ")fmt.Print(v)case int32:fmt.Print("key=>" + key + ":int32 ")fmt.Println(v)case []uint8:fmt.Print("key=>" + key + ",type=>uint8[],value=>")fmt.Print(string(v))fmt.Print(" ")default:fmt.Print(v)}}fmt.Println()}return nil, nil
}

输出结果

key=>id,type=>uint8[],value=>5 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 
key=>id,type=>uint8[],value=>6 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 
key=>id,type=>uint8[],value=>7 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 
key=>id,type=>uint8[],value=>8 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 
key=>id,type=>uint8[],value=>9 key=>username,type=>uint8[],value=>test key=>email,type=>uint8[],value=>test@test 

6.分页

可以使用gorm的scopes来实现分页(先count再分页),参考:Gorm Scopes复用你的逻辑 和 学习gorm系列十之:使用gorm.Scopes函数复用你的查询逻辑

func Paginate(pageNum int, pageSize int) func(db *gorm.DB) *gorm.DB {return func(db *gorm.DB) *gorm.DB {if pageNum <= 0 {pageNum = 1}if pageSize > 100 {pageSize = 100} else if pageSize <= 0 {pageSize = 10}offset := (pageNum - 1) * pageSizereturn db.Offset(offset).Limit(pageSize)}
}func (repo *userRepo) ListUser(ctx context.Context, pageNum int, pageSize int) ([]*model.User, error) {var result []*model.Uservar total int64err := repo.data.db.Model(&model.User{}).Count(&total).Errorif err != nil {repo.log.Error(fmt.Sprintf("list user fail, error: %v", err))}err = repo.data.db.Scopes(Paginate(pageNum, pageSize)).Find(&result).Errorif err != nil {repo.log.Error(fmt.Sprintf("update user fail, error: %v", err))return nil, err}return result, nil
}

测试分页函数

func Test_userRepo_ListUsers(t *testing.T) {data, _, err := NewData(zap_logger, db, rdb)if err != nil {panic(err)}userRepo := userRepo{data: data, log: zap_logger}result, err := userRepo.ListUser(context.Background(), 1, 5)if err != nil {fmt.Println(err)}for _, user := range result {fmt.Println(user)}
}

输出

&{5 test test@test}
&{6 test test@test}
&{7 test test@test}
&{8 test test@test}
&{9 test test@test}

Mybatis Page Helper分页插件原理:Mybatis分页插件PageHelper的配置和使用方法

3.gorm框架自定义SQL

gorm还支持编写sql模板,来添加自定义sql的函数逻辑,其中使用的语法是text template,可以参考: go学习笔记——text template

package modelimport "gorm.io/gen"type Filter interface {// FilterWithColumn SELECT * FROM @@table WHERE @@column=@valueFilterWithColumn(column string, value string) ([]*gen.T, error)// FilterWithObject//// SELECT * FROM @@table where id > 0// {{if user != nil}}//   {{if user.ID > 0}}//     AND id = @user.ID//   {{else if user.Username != ""}}//     AND username=@user.Username//   {{else if user.Email != ""}}//     AND email=@user.Email//   {{end}}// {{end}}FilterWithObject(user *gen.T) ([]*gen.T, error)
}

然后在generate.go文件中添加

g.ApplyInterface(func(model.Filter) {}, g.GenerateModel("user"))

使用自定义SQL生成的函数

func Test_userDo_FilterWithColumn(t *testing.T) {result, err := User.FilterWithColumn("username", "test")if err != nil {fmt.Println("filter user fail")}for _, each := range result {fmt.Println(each)}
}func Test_userDo_FilterWithObject(t *testing.T) {user := model.User{ID:       3,Username: "test2",Email:    "test2@test",}result, err := User.FilterWithObject(&user)if err != nil {fmt.Println("filter user fail")}for _, each := range result {fmt.Println(each)}
}

其中FilterWithObject函数的User对象的ID不为空,且大于的时候,执行的SQL如下

SELECT * FROM user where id > 0 AND id = 3 

ID为空的时候,执行的SQL如下

SELECT * FROM user where id > 0 AND username='test2' 

ID和Username都为空的时候,执行的SQL如下

SELECT * FROM user where id > 0 AND email='test2@test' 

参考:GORM Gen使用指南

4.gorm框架关联查询

1.has one

使用外键关联查询,得到一个struct结果,比如查询一个用户,一个用户只有一张信用卡,同时查询用户信息和用户的这张信用卡struct

参考:https://gorm.io/zh_CN/docs/has_one.html

2.has many

使用外键关联查询,得到一个list of struct结果,比如查询一个用户,一个用户拥有多张信用卡,同时查询用户信息和用户所有的信用卡list

参考:https://gorm.io/zh_CN/docs/has_many.html

3.Many to many

Many to Many 会在两个 model 中添加一张连接表。例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。

参考:https://gorm.io/zh_CN/docs/many_to_many.html

下面的例子以user和role为例子,user和role之间通过一张r_user_role关联表进行关联

定义UserDetail,其中的Roles为关联属性,如果在表上没有指定外键的话,需要通过 ForeignKey 和 References 标签指定使用哪些字段作为外键关联

package modeltype UserDetail struct {ID       int64  `gorm:"column:id;primaryKey;autoIncrement:true;comment:主键" json:"id"` // 主键Username string `gorm:"column:username;comment:用户名" json:"username"`                  // 用户名Email    string `gorm:"column:email;comment:邮箱" json:"email"`                         // 邮箱Roles    []Role `gorm:"many2many:r_user_role;foreignKey:ID;joinForeignKey:UserID;References:ID;JoinReferences:RoleID"` // 没有外键,需要通过 ForeignKey 和 References 标签指定使用哪些字段作为外键关联
}func (*UserDetail) TableName() string {return "user"
}

data查询层

func (repo *userRepo) FindUserDetailByID(ctx context.Context, id int64) (*model.UserDetail, error) {user := model.UserDetail{}err := repo.data.db.WithContext(ctx).Model(&model.UserDetail{}).Preload("Roles").Where("id = ?", id).Find(&user).Errorif err != nil {repo.log.Error(fmt.Sprintf("find user detail by id fail, error: %v", err))return nil, err}return &user, nil
}

测试

func Test_userRepo_FindUserDetailByID(t *testing.T) {data, _, err := NewData(zap_logger, db, rdb)if err != nil {panic(err)}userRepo := userRepo{data: data, log: zap_logger}result, err := userRepo.FindUserDetailByID(context.Background(), 1)if err != nil {fmt.Println(err)}fmt.Print(result)
}

结果

2024/11/10 21:38:11 /Users/lintong/coding/go/gin-template/internal/data/user.go:40
[1.824ms] [rows:2] SELECT * FROM `r_user_role` WHERE `r_user_role`.`user_id` = 12024/11/10 21:38:11 /Users/lintong/coding/go/gin-template/internal/data/user.go:40
[1.753ms] [rows:2] SELECT * FROM `role` WHERE `role`.`id` IN (1,2)2024/11/10 21:38:11 /Users/lintong/coding/go/gin-template/internal/data/user.go:40
[5.847ms] [rows:1] SELECT * FROM `user` WHERE id = 1
&{1 jshellshear0 jshellshear0@taobao.com [{1 test_role1} {2 test_role2}]}--- PASS: Test_userRepo_FindUserDetailByID (0.01s)

5.gorm事务

1.data层承载事务

参考:https://gorm.io/zh_CN/docs/transactions.html

2.biz层承载事务

参考:在 Go-Kratos 框架中优雅的使用 GORM 完成事务

以及 https://github.com/go-kratos/examples/blob/main/transaction/gorm/internal/biz/biz.go

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/888021.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

原神

oj.hailiangedu.com/file/22/dragon.gif

平衡树从启蒙到入土

首先得承认伊德利拉美貌盖世无双将数列改成数后处理起来更舒服 什么是平衡树 更广泛的定义:左右子树高度不超过 1 的 如果将这东西和二叉搜索树结合,便是平衡树搜索树 平衡树分类:treap 随机 splay 贪心 fhq 合并 分裂fhq 实现 合并 给出两个树,根分别为 a、b,如果我们将 …

Entity Framework Core简单使用

它是微软官方发布的基于ADO.NET的ORM框架。通过EF可以很方便地将表映射到实体对象或将实体对象转换为数据库表。 ORM:将数据存储从域对象自动映射到关系型数据库的工具。ORM主要包括3个部分:域对象、关系数据库对象、映射关系。ORM使类提供自动化CRUD,使开发人员从数据库API…

易语言 -- 开山篇章

易语言简介 易语言(EPL)是一门以中文作为程序代码的编程语言,其以“易”著称,创始人为吴涛。易语言早期版本的名字为 E 语言,也通常代指与之对应的集成开发环境。其最早的版本发布可追溯至 2000 年 9 月 11 日。创造易语言的初衷是进行用中文来编写程序的实践,方便中国人…

《ESP32-S3使用指南—IDF版 V1.6》第八章 MENUCONFIG菜单配置

第八章 MENUCONFIG菜单配置 1)实验平台:正点原子DNESP32S3开发板 2)章节摘自【正点原子】ESP32-S3使用指南—IDF版 V1.6 3)购买链接:https://detail.tmall.com/item.htm?&id=768499342659 4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/es…

已安装TortoiseGit,但是文件夹不显示相关图标

问题原因:注册表图标显示太靠后了,只有前15个生效,排序该前就行项目文件夹右键->settings->Overlay Handlers->Start register editor->一下文件重命名,前面多加点空格,保证排名在前面就行->任务栏右键任务管理器->Windows资源管理器->右键重新启动-…

用一个静态图片实现出怪路线提示

用一个静态图片实现下面的效果

Windows平台调试器原理与编写01.调试框架

调试器原理与编写01.调试框架-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net 调试框架 调试器最基本功能: 断点,单步 断点分为三类软件断点 硬件断点 内存断点window提供了一套机制,帮助用户来实现一套3环的调试器 事件驱动 : 窗口的各种操作(外在的想要对窗…

本地部署DeepSeek-R1-AWQ

一、部署环境准备 系统信息:主机名为 10-200-3-23 IP 地址为 10.200.3.23 操作系统为 ubuntu 22.04 配备 8 卡 A100。二、驱动与桥接器安装安装 gcc执行命令 apt-get update -y apt install build-essential -y安装驱动下载驱动 wget https://us.download.nvidia.com/tesla/…

k8s部署nfs+sc

1.下载软件包nfs-subdir-external-provisioner-4.0.18.tgz 该软件包内文件截图 修改values.yaml 在目录内执行 helm install nfs-provisioner -f values.yaml . #老重要了2.查看执行是否成功 3.测试是否成功 编写yaml apiVersion: v1kind: PersistentVolumeClaimmetadata…

P2661 [NOIP 2015 提高组] 信息传递——染色做法

原题 本来想当水题刷的,结果被水题刷了。。。70到80到90到100,必须写个题解记录一下(doge) 题目分析一句话:求一个无权有向图中的最短环路(确保有环) tip:每一个点出度为一,那么必然有环,以样例为例如下。思路没必要每轮模拟全部的传送,只看某一个人的传送过程: 就…