快速入门go的orm框架-gorm

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 快速入门go的orm框架-gorm

初次接触


原生SQL驱动包进行查询

gorm 操作mysql


数据库表:


-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, '琳儿');
INSERT INTO `users` VALUES (2, '长路');
INSERT INTO `users` VALUES (3, '小明');
SET FOREIGN_KEY_CHECKS = 1;



代码:


package main
import (
   "database/sql"  //导入sql驱动
   "fmt"
   _ "github.com/go-sql-driver/mysql"
   "log"
)
type User struct {
  id int64
  name string
}
func main() {
  //使用driver(指定mysql) + DSN初始化DB连接
  db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/hello")
  //rows, err := db.Query("select id,name from users where id = ?", 1)
  rows, err := db.Query("select id,name from users")
  if err != nil {
  log.Printf("连接异常")
  }
  defer func() {
  err = rows.Close()  //处理完毕,释放连接
  }()
  //定义用户数组
  var users []User
  for rows.Next() {
  var user User
  err := rows.Scan(&user.id, &user.name)
  if err != nil {
    log.Printf("解析有误")
  }
  users = append(users, user)
  }
  fmt.Println(users)
}



实战gorm查询单表所有记录

GORM增删改查、gorm普通的增删改查


数据库表:


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for food
-- ----------------------------
DROP TABLE IF EXISTS `food`;
CREATE TABLE `food`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID,商品Id',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名',
  `price` decimal(10, 2) UNSIGNED NOT NULL COMMENT '商品价格',
  `type_id` int(10) UNSIGNED NOT NULL COMMENT '商品类型Id',
  `createtime` datetime(0) NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of food
-- ----------------------------
INSERT INTO `food` VALUES (1, 'CHANGLU', 100.00, 125, '2022-05-10 16:01:04');
INSERT INTO `food` VALUES (2, 'LINER', 1000.00, 666, '2022-05-03 16:01:25');
SET FOREIGN_KEY_CHECKS = 1;


代码:


package main
import (
  "fmt"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "log"
  "testing"
  "time"
)
//默认gorm对struct字段名使用Snake Case命名风格转换成mysql表字段名(需要转换成小写字母)
type Food struct {
  Id         int  //表字段名为:id
  Name       string //表字段名为:name
  Price      float64 //表字段名为:price
  TypeId     int  //表字段名为:type_id
  //我们也可以进行自定义对应映射的字段名:字段定义后面使用两个反引号``包裹起来的字符串部分叫做标签定义,这个是golang的基础语法,不同的库会定义不同的标签,有不同的含义
  CreateTime time.Time `gorm:"column:createtime"`  //表字段名为:createtime  //若是数据库中是DateTime,那么对应这里使用time,之后序列化可以进行转换时间日期
}
//创建表默认是这个表名
//设置表名,可以通过给Food struct类型定义 TableName函数,返回一个字符串作为表名
func (v Food) TableName()string  {
  return "food"
}
func Test_gormImpl(t *testing.T)  {
  db, err := connect()
  if err != nil {
  log.Printf("")
  }
  //v2版本中db没有close()方法:https://www.cnblogs.com/chengqiang521/p/15122102.html
  sqlDB, _  := db.DB()
  var foods []Food
  //查询所有的food列表记录
  db.Find(&foods)
  fmt.Println(foods)
  //延时关闭数据库连接
  defer sqlDB.Close()
}
func connect()  (db *gorm.DB, err error)  {
  //配置MySQL连接参数
  username := "root"  //账号
  password := "123456" //密码
  host := "127.0.0.1" //数据库地址,可以是Ip或者域名
  port := 3306 //数据库端口
  Dbname := "hello" //数据库名
  timeout := "10s" //连接超时,10秒
  //拼接下dsn参数, dsn格式可以参考上面的语法,这里使用Sprintf动态拼接dsn参数,因为一般数据库连接参数,我们都是保存在配置文件里面,需要从配置文件加载参数,然后拼接dsn。
  dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
  //连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
  db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
  if err != nil {
  return db, err
  }
  return db,nil
}



一、知识点学习


1.1、gorm.Model


type Model struct {
   ID        uint `gorm:"primarykey"`
   CreatedAt time.Time
   UpdatedAt time.Time
   DeletedAt DeletedAt `gorm:"index"`
}
//其中DeletedAt的类型为sql.NullTime
//该属性涉及到之后软删除和硬删除
type DeletedAt sql.NullTime


1.2、CRUD大整合


配套代码:go-gormLearn/***


说明:我们只要建好数据库即可,其他表都可以进行自动生成。



init.go:
package db
import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "gorm.io/gorm/logger"
)
var DB *gorm.DB
func Init()  {
  var err error
  DB, err = gorm.Open(mysql.Open("root:123456@tcp(localhost:3306)/hello?charset=utf8&parseTime=True&loc=Local"),
  &gorm.Config{
    PrepareStmt:            true,
    SkipDefaultTransaction: true,
    Logger:logger.Default.LogMode(logger.Info),  //打印执行的sql
  },
  )
  if err != nil {
  panic(err)
  }
  //进行表的创建
  m := DB.Migrator()
  if !m.HasTable(&User{}) {
  if err = m.CreateTable(&User{}); err != nil {
    panic(err)
  }
  }
}



核心的增删改查方法在user.go中:下面的内容都在这个user.go中


package db
import (
  "context"
  "gorm.io/gorm"
)
type User struct {
  gorm.Model
  Name string     `gorm:"type:varchar(32);not null"`
  Password string `gorm:"type:varchar(32);not null"`
}
func (v User) TableName()string  {
  return "user"
}



添加


//创建用户
func CreateUser(ctx context.Context, users []*User) error {
  //INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`password`) VALUES ('2022-05-28 19:30:45.339','2022-05-28 19:30:45.339',NULL,'changlu','123456'),()
  return DB.WithContext(ctx).Create(users).Error
}




//查询用户(未被软删除的)
func QueryAllUsers(ctx context.Context)([]*User, error)  {
  var users []*User
  //SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL
  if err := DB.WithContext(ctx).Find(&users).Error; err != nil {
  return nil,err
  }
  return users, nil
}
//查询所有用户(包含软删除的)
func QueryAllIncludeDeletedUsers(ctx context.Context)([]*User, error)  {
  var users []*User
  //SELECT * FROM `user`
  if err := DB.WithContext(ctx).Unscoped().Find(&users).Error; err != nil {
  return nil,err
  }
  return users, nil
}
//只查询软删除用户
func QueryAllDeletedUsers(ctx context.Context)([]*User, error) {
  var users []*User
  //SELECT * FROM `user` WHERE deleted_at <> ''
  if err := DB.WithContext(ctx).Unscoped().Where("deleted_at <> ?", "").Find(&users).Error;err != nil {
  return nil, err
  }
  return users, nil
}
//分页查询
func PageQueryUser(ctx context.Context, pageNo, pageSize int)([]*User, error)  {
  tx := DB.WithContext(nil).Model(&User{})
  //...字段补充  tx.where()...
  var users []*User
  if err := tx.Offset((pageNo - 1) * pageSize).Limit(pageSize).Find(&users).Error; err != nil {
  return nil, err
  }
  return users, nil
}




两种方式修改:①struct更新。(非零不会更新)②select更新。(必更新)


当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作:


//修改用户
//方式一:通过struct来进行更新
func UpdateUser(ctx context.Context, user *User) error {
  //UPDATE `user` SET `updated_at`='2022-05-28 19:31:24.468',`name`='changlu111',`password`='12132232' WHERE `user`.`deleted_at` IS NULL AND `id` = 1
  return DB.WithContext(ctx).Model(&user).Select("Name", "Password").Updates(user).Error
}
//方式二:通过map进行更新
func UpdateUser2(ctx context.Context, ID uint, username *string, password *string )error  {
  params := make(map[string]interface{})
  if username != nil {
  params["Name"] = username
  }
  if password != nil {
  params["Password"] = password
  }
  //UPDATE `user` SET `name`='changlu666',`password`='122222',`updated_at`='2022-05-28 19:32:05.675' WHERE id = 1 AND `user`.`deleted_at` IS NULL
  return DB.WithContext(ctx).Model(&User{}).Where("id = ?", ID).Updates(params).Error
}
//恢复软删除
func RecoverUserDeleted(ctx context.Context, ID uint)error  {
  //UPDATE `user` SET `deleted_at`=NULL,`updated_at`='2022-05-28 19:16:19.579' WHERE `ID` = 1
  return DB.WithContext(ctx).Model(&User{}).
    Unscoped(). //针对已软删除的,此时就不会带上`user`.`deleted_at` IS NULL
    Where("ID", ID).Update("deleted_at", nil).Error
}




gorm软删除妙用-充值,软删除恢复,soft delete


//删除用户
//方式一:软删除
func SofeDeleteUser(ctx context.Context, ID uint) error {  //uint指的是无符号整数,其大小根据当前的平台来决定
  //UPDATE `user` SET `deleted_at`='2022-05-28 19:32:30.124' WHERE id = 1 AND `user`.`deleted_at` IS NULL
  return DB.WithContext(ctx).Where("id = ?", ID).Delete(&User{}).Error
}
//方式二:硬删除
func DeleteUser(ctx context.Context, user *User) error {
  //调用Unscoped即可表示硬删除
  //DELETE FROM `user` WHERE `user`.`id` = 1
  return DB.WithContext(ctx).Unscoped().Delete(user).Error
}


main.go:测试方法


package main
import (
  "fmt"
  "go-gormLearn/db"
)
func main()  {
  db.Init()
  //1、创建用户(无需填充gorm.model,即可自动进行插入更新值)
  //users := make([]*db.User, 2)
  //users[0] = &db.User{
  //  Model:    gorm.Model{},
  //  Name:     "changlu",
  //  Password: "123456",
  //}
  //users[1] = &db.User{
  //  Model:    gorm.Model{},
  //  Name:     "test",
  //  Password: "123456",
  //}
  //db.CreateUser(nil, users)
  //2、更新用户(会自动更新updatetime)
  //方式一:借助struct
  //u := &db.User{
  //  Model: gorm.Model{
  //  ID: 1,
  //  },
  //  Name:     "changlu111",
  //  Password: "12132232",
  //}
  //db.UpdateUser(nil, u) //通过!
  //方式二:借助map
  //username := "changlu666"
  //password := "122222"
  //db.UpdateUser2(nil, 1, &username, &password)//通过!
  //恢复软删除
  db.RecoverUserDeleted(nil, 1)
  //3、删除用户
  //3.1、软删除
  //db.SofeDeleteUser(nil, 1)
  //3.2、硬删除
  //db.DeleteUser(nil, &db.User{
  //  Model:    gorm.Model{
  //  ID: 1,
  //  },
  //})
  //4、查询用户
  //4.1、查询所有未软删除用户
  //users, _ := db.QueryAllUsers(nil)
  //for _, user := range users {
  //  fmt.Println(user)
  //}
  //4.2、查询所有用户(包含软删除的)
  //users, _ := db.QueryAllIncludeDeletedUsers(nil)
  //for _, user := range users {
  //  fmt.Println(user)
  //}
  //4.3、只查询软删除的用户
  //users, _ := db.QueryAllDeletedUsers(nil)
  //for _, user := range users {
  //  fmt.Println(user)
  //}
  //4.4、分页查询
  users, _ := db.PageQueryUser(nil, 1, 1)
  for _, user := range users {
  fmt.Println(user)
  }
}


二、性能提升


2.1、基本配置


gorm中文文档—事务


db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  ... //配置信息
})


①禁止默认事务:对于写操作(创建、更新、删除),为了确保数据的完整性,GORM 会将它们封装在事务内运行。但这会降低性能,你可以在初始化时禁用这种方式


SkipDefaultTransaction: true,


②缓存预编语句:执行任何 SQL 时都创建并缓存预编译语句,可以提高后续的调用速度


全局模式:


PrepareStmt: true,  //【若是使用hock或者关联创建的时候设置为false】


会话模式:


// 会话模式
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)


③Prepared Statement与原生sql使用


PrepareStmt: true,


//示例:
db.Raw("select sum(age) from users where role = ?", "admin").Scan(&age)
1
2
3
4
2.2、常用配置
import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)
var DB *gorm.DB
func Init()  {
  var err error
  DB, err = gorm.Open(mysql.Open("gorm:gorm@tcp(localhost:3306)/hello?charset=utf8&parseTime=True&loc=Local"),
  &gorm.Config{
    PrepareStmt:            true,  //预编译
    SkipDefaultTransaction: true,  //关闭默认事务
  },
  )
  if err != nil {
  panic(err)
  }
}


三、Migrator


gorm的Migrator是干什么的?


常用操作:表的创建,核心是用来进行表结构操作的。Gorm之gorm.Migrator接口详解


//进行表的创建
m := DB.Migrator()
if !m.HasTable(&User{}) {  //是否存在表
    if err = m.CreateTable(&User{}); err != nil {  //创建表
        panic(err)
    }
}


问题1:为什么设置的结构体的某个字段没有再表中创建?


原因:字段必须为大写,否则无法创建。


参考:关于 gorm AutoMigrate 不自动创建字段的解决方案
type User struct {
  gorm.Model
  Name string     `gorm:"type:varchar(32);not null"`   //使用gorm可以来进行定义字段的类型
  Password string `gorm:"type:varchar(32);not null"`
}


四、其他方面


4.1、执行sql时打印日志


如何让gorm输出执行的sql


1、打印所有的sql


&gorm.Config{
    Logger:logger.Default.LogMode(logger.Info),  //打印执行的sql
},
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
编译器 Go C++
必知的技术知识:go语言快速入门教程
必知的技术知识:go语言快速入门教程
|
12天前
|
Go 数据库
Go语言之GORM框架(四)——预加载,关联标签与多态关联,自定义数据类型与事务(完结篇)
Go语言之GORM框架(四)——预加载,关联标签与多态关联,自定义数据类型与事务(完结篇)
|
12天前
|
SQL Go
Go语言之GORM框架(三)——Hook(钩子)与Gorm的高级查询
Go语言之GORM框架(三)——Hook(钩子)与Gorm的高级查询
|
12天前
|
SQL Go 数据库
Go语言之GORM框架(二) ——GORM的单表操作
Go语言之GORM框架(二) ——GORM的单表操作
|
12天前
|
SQL 关系型数据库 MySQL
Go语言之Gorm框架(一) ——初窥Gorm框架
Go语言之Gorm框架(一) ——初窥Gorm框架
|
1天前
|
IDE Linux Go
记录一个go语言与IDE之间的问题
【7月更文挑战第1天】本文介绍在IDE中调试Go应用可能遇到的问题。当问题与IDE的自动完成有关,可以试着使用其他编辑器如Linux的vim是否无此问题。这可以验证表明IDE可能不完全兼容最新语言版本,建议使用无自动检测工具临时解决。
16 0
|
1天前
|
安全 Go
Go语言的iota关键字有什么用途?
**Go语言中的`iota`是常量生成器,用于在`const`声明中创建递增的常量。`iota`在每个新的`const`块重置为0,然后逐行递增,简化了枚举类型或常量序列的定义。例如,定义星期枚举:** ```markdown ```go type Weekday int const ( Sunday Weekday = iota // 0 Monday // 1 Tuesday // 2 ... ) ``` 同样,`iota`可用于定义不同组的常量,如状态码和标志位,保持各自组内的递增,提高代码可读性。
|
3天前
|
JSON 算法 测试技术
在go语言中调试程序
【6月更文挑战第29天】Go语言内置`testing`包支持单元测试、基准测试和模糊测试。`go test`命令可执行测试,如`-run`选择特定测试,`-bench`运行基准测试,`-fuzz`进行模糊测试。
16 2
在go语言中调试程序
|
5天前
|
编译器 Go 开发者