当前位置: 首页 > 工具软件 > GORM Gen > 使用案例 >

GORM[学习记录]

凤自珍
2023-12-01

gorm地址

1. 入门示例

  1. 在 GOPATH 下,创建目录 project → gormlearn → gormtest 文件夹。
  2. 在 gormlearn 下,执行 go work init
  3. 在 gormtest 下,执行 go mod init test.com/gormtest
  4. 在 gormlearn 下,执行 go work use ./gormtest
  5. 在 gormtest 下,安装 mysql 驱动,go get -u gorm.io/driver/mysql
  6. 在 gormtest 下,安装 gorm 包,go get -u gorm.io/gorm
  7. 在 gormtest 下,安装 gin,go get -u github.com/gin-gonic/gin
  8. 在 gormtest 下,新建 main.go。
package main
import (
	"github.com/gin-gonic/gin"
	"log"
	"test.com/gormtest/router"
)
/* 主函数 */
func main() {
	r := gin.Default()    // 初始化一个 Engine 实例
	router.InitRouter(r)  // 初始化 路由器
	err := r.Run(":8080") // 运行
	if err != nil {
		log.Fatalln("运行 引擎 失败", err)
	}
}
  1. 在 gormtest/router 下,新建 router.go 负责初始化路由器。
package router
import (
	"github.com/gin-gonic/gin"
	"test.com/gormtest/api"
)
/* 初始化路由器 */
func InitRouter(r *gin.Engine) { // 初始化路由
	api.RegisterRouter(r)		 // 注册路由器, 在 api/router 中实现.
}
  1. 在 gormtest/api 下,新建 router.go 负责获取函数相关路径。
package api
import "github.com/gin-gonic/gin"
/* 注册路由器 */
func RegisterRouter(r *gin.Engine) {
	r.GET("/save", SaveUser)
	r.GET("/get", GetUser)
	r.GET("/update", UpdateUser)
	r.GET("/delete", DeleteUser)
}
  1. 在 gormtest/api 下,新建 user.go 负责实现接口函数。
package api
import (
	"github.com/gin-gonic/gin"
	"test.com/gormtest/dao"
	"time"
)
/* 保存数据 */
func SaveUser(c *gin.Context) {
	user := &dao.User{Username: "tom", Password: "123456", CreateTime: time.Now().Unix()}
	dao.SaveUser(user)
	c.JSON(200, user)
}
/* 获取数据 */
func GetUser(c *gin.Context) {
	user := dao.GetByID(1)
	c.JSON(200, user)
}
/* 更新数据 */
func UpdateUser(c *gin.Context) {
	dao.UpdateUser(1)
	user := dao.GetByID(1)
	c.JSON(200, user)
}
/* 删除数据 */
func DeleteUser(c *gin.Context) {
	dao.DeleteUser(1)
	user := dao.GetByID(1)
	c.JSON(200, user)
}
  1. 在 gormtest/dao 下,新建 gorm.go 连接数据库。
package dao
import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
)
/* 数据库 */
var DB *gorm.DB
/* 初始化连接数据库 */
func init() {
	// 配置 Mysql 连接参数
	username := "root"   // 账号
	password := "123456" // 密码
	host := "127.0.0.1"  // ip
	port := 3306		 // 端口
 	Dbname := "itcase"	 // 数据库
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", username, password, host, port, Dbname)
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		log.Fatalln("db connected error", err)
	}
	DB = db
}
  1. 在 gormtest/dao 下,新建 user.go 实现 gormtest/api 中接口所使用的函数。
package dao
import "log"
/* 数据库结构体 */
type User struct {
	ID         int64  // 主键
	Username   string `gorm:"column:username"`
	Password   string `gorm:"column:password"`
	CreateTime int64  `gorm:"column:createtime"`
}
/* 获取表名 */
func (u User) TableName() string {
	return "user"
}
/* 保存数据 */
func SaveUser(user *User) {
	err := DB.Create(user).Error
	if err != nil {
		log.Println("insert user error", err)
	}
}
/* 获取数据 */
func GetByID(id int64) User {
	var user User
	err := DB.Where("id=?", id).First(&user).Error
	if err != nil {
		log.Println("get user by id error", err)
	}
	return user
}
/* 获取所有数据 */
func GetAll() []User {
	var user []User
	err := DB.Find(&user).Error
	if err != nil {
		log.Println("get user all", err)
	}
	return user
}
/* 更新数据 */
func UpdateUser(id int64) {
	err := DB.Model(&User{}).Where("id=?", id).Update("username", "lisi")
	if err != nil {
		log.Println("update user by id error", err)
	}
}
/* 删除数据 */
func DeleteUser(id int64) {
	err := DB.Where("id=?", id).Delete(&User{})
	if err != nil {
		log.Println("update user by id error", err)
	}
}
  1. 进入如 http://localhost:8080/save 网页,即可完成保存操作,其他操作类似。

模型定义

  • 在案例中,定义了 User 结构体 和 数据表 user 做映射,User 结构体 被称之为 数据模型,在 gorm 框架中,操作数据需要预先定义模型。
  • 底层都是使用 go 的 database 标准库,利用反射原理,执行读写操作时,将结构体翻译为 sql 语句,并将结构体转为对应的模型。

假设有一个商品表

CREATE TABLE goods (
    id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID,商品Id',
    name varchar(30) NOT NULL COMMENT '商品名',
    price decimal(10,2) unsigned  NOT NULL COMMENT '商品价格',
    type_id int(10) unsigned NOT NULL COMMENT '商品类型Id',
    createtime int(10) NOT NULL DEFAULT 0 COMMENT '创建时间',
    PRIMARY KEY (id)
) COMMENT '商品表' ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

将上述表翻译为模型后

type Good struct {
	Id         int  //表字段名为:id
	Name       string //表字段名为:name
	Price      float64 //表字段名为:price
	TypeId     int  //表字段名为:type_id
	CreateTime int64 `gorm:"column:createtime"`  //表字段名为:createtime
}

模型标签

标签定义:

`gorm:"标签内容"`

标签定义部分,多个标签定义可以使用分号(;)分隔

gorm常用标签如下:

标签说明例子
column指定列名`gorm:“column:createtime”`
primaryKey指定主键`gorm:“column:id; PRIMARY_KEY”`
-忽略字段`gorm:“-”` 可以忽略 struct 字段,被忽略的字段不参与gorm的读写操作

表名映射

  • 复数表名,比如结构体User,默认的表名为users
  • 实现Tabler接口 (TableName 不支持动态变化,它会被缓存下来以便后续使用。)
type Tabler interface {
    TableName() string
}
// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
  return "profiles"
}
  • 动态表名,使用Scopes
func UserTable(user User) func (tx *gorm.DB) *gorm.DB {
  return func (tx *gorm.DB) *gorm.DB {
    if user.Admin {
      return tx.Table("admin_users")
    }
    return tx.Table("users")
  }
}
db.Scopes(UserTable(user)).Create(&user)
  • 临时表名
db.Table("deleted_users")

Model

GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt

// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间。

要使用不同名称的字段,您可以配置 autoCreateTime、autoUpdateTime 标签

如果想要保存 UNIX(毫/纳)秒时间戳,而不是 time,只需简单地将 time.Time 修改为 int 即可。

type User struct {
  CreatedAt time.Time // 默认创建时间字段, 在创建时,如果该字段值为零值,则使用当前时间填充
  UpdatedAt int       // 默认更新时间字段, 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 自定义字段, 使用时间戳填纳秒数充更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"` //自定义字段, 使用时间戳毫秒数填充更新时间
  Created   int64 `gorm:"autoCreateTime"`      //自定义字段, 使用时间戳秒数填充创建时间
}

可以将它嵌入到您的结构体中,以包含这几个字段,比如

type User struct {
  gorm.Model		// 继承
  Name string
}
// 等效于
type User struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}

对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:

type Author struct {
    Name  string
    Email string 
}

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded"`
  Upvotes int32
}
// 等效于
type Blog struct {
  ID    int64
  Name  string
  Email string
  Upvotes  int32
}

可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:

type Blog struct {
  ID      int
  Author  Author `gorm:"embedded;embeddedPrefix:author_"`
  Upvotes int32
}
// 等效于
type Blog struct {
  ID          int64
  AuthorName  string
  AuthorEmail string
  Upvotes     int32
}

5. 数据库连接

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server。

连接数据库主要是两个步骤:

  • 配置 DSN (Data Source Name)。
  • 使用 gorm.Open 连接数据库。

5.1 DSN

gorm 库使用 dsn 作为连接数据库的参数,dsn 翻译过来就叫数据源名称,用来描述数据库连接信息。一般都包含数据库连接地址,账号,密码之类的信息。

格式:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

示例:

// mysql dsn格式
// 涉及参数:
// username   数据库账号
// password   数据库密码
// host       数据库连接地址,可以是Ip或者域名
// port       数据库端口
// Dbname     数据库名
username:password@tcp(host:port)/Dbname?charset=utf8mb4&parseTime=True&loc=Local

// 填上参数后的例子
// username = root
// password = 123456
// host     = "127.0.0.1"
// port     = 3306
// Dbname   = "itcase"
// 后面 K/V 键值对参数含义为:
// charset=utf8mb4 客户端字符集为 utf8mb4
// parseTime=true 支持把数据库 datetime 和 date 类型转换为 golang 的 time.Time 类型
// loc=Local 使用系统本地时区
root:123456@tcp(127.0.0.1:3306)/itcase?charset=utf8mb4&parseTime=True&loc=Local

5.2 连接数据库

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

func main() {
  	dsn := "root:123456@tcp(127.0.0.1:3306)/itcase?charset=utf8mb4&parseTime=True&loc=Local"
  	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

MySQL 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:

db, err := gorm.Open(mysql.New(mysql.Config{
  	DSN: "root:123456@tcp(127.0.0.1:3306)/itcase?charset=utf8mb4&parseTime=True&loc=Local", // DSN
  	DefaultStringSize: 256, // string 类型字段的默认长度
  	DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
	DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  	DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
  	SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})

5.3 调试模式

db.Debug()

5.4 连接池配置

sqlDB, _ := db.DB()
// 设置数据库连接池参数
sqlDB.SetMaxOpenConns(100)   //设置数据库连接池最大连接数
sqlDB.SetMaxIdleConns(20)   //连接池最大允许的空闲连接数,如果没有sql任务需要执行的连接数大于20,超过的连接会被连接池关闭

2. 增删改查

2.1 插入数据

user := User{
	Username:"zhangsan",
	Password:"123456",
	CreateTime:time.Now().Unix(),
}
db.Create(&user)

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

用指定的字段创建

db.Select("username","password").Create(&user)

忽略字段

db.Omit("username").Create(&user)

批量插入

var users = []User{{Username: "jinzhu1"}, {Username: "jinzhu2"}, {Username: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  	user.ID // 1,2,3
}

使用 map 创建

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},
})

使用原生 sql 创建

db.Exec("insert into users (username,password,createtime) values (?,?,?)", user.Username, user.Password, user.CreateTime)

2.2 更新数据

创建一个商品表:

CREATE TABLE `goods` (
  	`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id',
  	`title` varchar(100) NOT NULL COMMENT '商品名',
  	`price` decimal(10, 2) NULL DEFAULT 0.00 COMMENT '商品价格',
  	`stock` int(11) DEFAULT '0' COMMENT '商品库存',
  	`type` int(11) DEFAULT '0' COMMENT '商品类型',
  	`create_time` datetime NOT NULL COMMENT '商品创建时间',
  	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
package dao
import "time"

type Goods struct {
	Id         int
	Title      string
	Price      float64
	Stock      int
	Type       int
	CreateTime time.Time
}

func (v Goods) TableName() string {
	return "goods"
}

func SaveGoods(goods Goods) {
	DB.Create(&goods)
}
package dao
import (
	"testing"
	"time"
)

func TestSaveGoods(t *testing.T) {
	goods := Goods{
		Title:      "毛巾",
		Price:      25,
		Stock:      100,
		Type:       0,
		CreateTime: time.Now(),
	}
	SaveGoods(goods)
}

保存数据:

goods := Goods{}
DB.Where("id = ?", 1).Take(&goods)

goods.Price = 100
//UPDATE `goods` SET `title`='毛巾',`price`=100.000000,`stock`=100,`type`=0,`create_time  `='2022-11-25 13:03:48' WHERE `id` = 1
DB.Save(&goods)

更新单个列:

goods := Goods{}
DB.Where("id = ?", 2).Take(&goods)
DB.Model(&goods).Update("title", "hello")

更新多个列:

goods := Goods{}
DB.Where("id = ?", 2).Take(&goods)
//更新非零值的字段  也可以使用map
DB.Model(&goods).Updates(Goods{
	Title: "hello",
	Stock: 200,
})

更新选定字段:

goods := Goods{}
DB.Where("id = ?", 2).Take(&goods)
DB.Model(&goods).Select("title").Updates(Goods{
	Title: "hello",
	Stock: 200,
})

表达式:

db.Model(&goods).Update("stock", gorm.Expr("stock + 1"))
db.Model(&goods).Update(map[string]interface{}{"stock": gorm.Expr("stock + 1")})

子查询更新:

goods := Goods{}
DB.Where("id = ?", 2).Take(&goods)
DB.Model(&goods).Update("title", DB.Model(&User{}).Select("username").Where("id=?", 2))

gorm 更新:

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")

2.3 删除数据

goods := Goods{}
DB.Where("id = ?", 2).Take(&goods)
DB.Delete(&goods)

根据主键删除:

DB.Delete(&Goods{}, 1)

gorm 删除:

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})

2.4 查询数据

查询函数:

  • Take:查询一条记录
db.Take(&goods)
  • First: 根据主键正序排序后,查询第一条数据:
db.First(&goods)
  • Last:根据主键倒序排序后,查询最后一条记录:
db.Last(&goods)
  • Find:查询多条记录:
db.Find(&goods)
  • Pluck:查询一列值:
var titles []string
db.Model(&Goods{}).Pluck("title", &titles)

where:

通过 db.Where 函数设置条件
函数说明:db.Where(query interface{}, args ...interface{})

  • query:sql 语句的 where 子句, where 子句中使用问号 (?) 代替参数值,则表示通过 args 参数绑定参数。
  • args:where 子句绑定的参数,可以绑定多个参数
db.Where("id in (?)", []int{1,2,5,6}).Take(&goods)

select:

设置select子句, 指定返回的字段

var goods Goods
DB.Select("id", "title").Find(&goods)

order:

var goods []Goods
DB.Order("id desc").Find(&goods)
 类似资料: