学习笔记golang之gorm学习笔记(代码片段)

棉花糖灬 棉花糖灬     2022-12-07     373

关键词:

一、模型定义

1. 模型定义

模型是标准的 struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成,如:

type User struct 
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivedAt    sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time

gorm默认情况下约定使用结构体的名字作为表名,结构体字段名作为列名,使用ID作为主键。

gorm.Model是gorm定义的一个结构体,它包含了几个常见的字段名。

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

2. 字段级权限控制

对于可导出的字段,可以使用结构体标签来控制字段的权限。

type User struct 
  Name string `gorm:"<-:create"` // 允许读和创建
  Name string `gorm:"<-:update"` // 允许读和更新
  Name string `gorm:"<-"`        // 允许读和写(创建和更新)
  Name string `gorm:"<-:false"`  // 允许读,禁止写
  Name string `gorm:"->"`        // 只读(除非有自定义配置,否则禁止写)
  Name string `gorm:"->;<-:create"` // 允许读和写
  Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
  Name string `gorm:"-"`  // 读写操作均会忽略该字段

3. 创建 / 更新时间追踪

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

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

4. 嵌入结构体

当一个结构体中嵌入了一个匿名结构体时,匿名结构体的字段也被认为是父结构体的字段。而如果是嵌入了一个普通结构体时,可以使用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

5. 字段标签

gorm支持以下tag,tag名大小写不敏感,但建议使用驼峰命名。

标签名说明
column指定 db 列名
type列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INSTREMENT
size指定列大小,例如:size:256
primaryKey指定列为主键
unique指定列为唯一
default指定列的默认值
precision指定列的精度
scale指定列大小
not null指定列为 NOT NULL
autoIncrement指定列为自动增长
embedded嵌套字段
embeddedPrefix嵌入字段的列名前缀
autoCreateTime创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime创建 / 更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex与 index 相同,但创建的是唯一索引
check创建检查约束,例如 check:age > 13,查看 约束 获取详情
<-设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
->设置字段读的权限,->:false 无读权限
-忽略该字段,- 无读写权限

二、连接数据库

以连接MySQL数据库为例。

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

func main() 
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config)

连接数据库的高级配置

db, err := gorm.Open(mysql.New(mysql.Config    
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
  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)

gorm允许通过一个现有的数据库连接来初始化*gorm.DB

import (
  "database/sql"
  "gorm.io/gorm"
)

sqlDB, err := sql.Open("mysql", "mydb_dsn")
gormDB, err := gorm.Open(mysql.New(mysql.Config
  Conn: sqlDB,
), &gorm.Config)

连接池

sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

三、CRUD接口

1. 创建记录

(1) 创建记录

user := UserName: "Jinzhu", Age: 18, Birthday: time.Now()

result := db.Create(&user) // 通过数据的指针来创建

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

(2) 用指定字段创建记录

创建记录并更新给出的字段。用Select,指定你想从数据库中检索出的字段

db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")

创建记录并更新未给出的字段

db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")

(3) 批量插入

要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。

var users = []UserName: "jinzhu1", Name: "jinzhu2", Name: "jinzhu3"
db.Create(&users)

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


使用 CreateInBatches 创建时,你还可以指定创建的数量

var users = []Username: "jinzhu_1", ...., Name: "jinzhu_10000"

db.CreateInBatches(users, len(users))

(4) 根据map创建

GORM 支持根据 map[string]interface[]map[string]interface 创建记录,例如:

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

注意: 根据 map 创建记录时,association 不会被调用,且主键也不会自动填充

2. 查询

(1) 检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件。若没有找到记录时,它会返回 ErrRecordNotFound 错误

// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

FirstLast 方法会根据主键查找到第一个、最后一个记录, 它仅在通过 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键,则按第一个字段排序。

var user User

// 可以
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// 可以
result := map[string]interface
db.Model(&User).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// 不行
result := map[string]interface
db.Table("users").First(&result)

// 但可以配合 Take 使用
result := map[string]interface
db.Table("users").Take(&result)

// 根据第一个字段排序
type Language struct 
  Code string
  Name string

db.First(&Language)
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

(2) 根据主键检索

db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;

db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;

db.Find(&users, []int1,2,3)
// SELECT * FROM users WHERE id IN (1,2,3);

(3) 检索全部对象

// 获取全部记录
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // 返回找到的记录数,相当于 `len(users)`
result.Error        // returns error

(4) 条件

string条件

// 获取第一条匹配的记录
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// 获取全部匹配的记录
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string"jinzhu", "jinzhu 2").Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

struct或map条件

// Struct
db.Where(&UserName: "jinzhu", Age: 20).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;

// Map
db.Where(map[string]interface"name": "jinzhu", "age": 20).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;

// 主键切片条件
db.Where([]int6420, 21, 22).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);

**注意:**当使用结构体作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为0''false 或其他零值,该字段不会被用于构建查询条件。

(5) 内联条件

// SELECT * FROM users WHERE id = 23;
// 根据主键获取记录,如果是非整型主键
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key';

// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";

db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;

// Struct
db.Find(&users, UserAge: 20)
// SELECT * FROM users WHERE age = 20;

// Map
db.Find(&users, map[string]interface"age": 20)
// SELECT * FROM users WHERE age = 20;

(6) not条件

db.Not("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;

// Not In
db.Not(map[string]interface"name": []string"jinzhu", "jinzhu 2").Find(&users)
// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");

// Struct
db.Not(UserName: "jinzhu", Age: 18).First(&user)
// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;

// 不在主键切片中的记录
db.Not([]int641,2,3).First(&user)
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

(7) or条件

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

// Struct
db.Where("name = 'jinzhu'").Or(UserName: "jinzhu 2", Age: 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

// Map
db.Where("name = 'jinzhu'").Or(map[string]interface"name": "jinzhu 2", "age": 18).Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);

(8) 选择特定字段

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

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

db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;

(9) 排序

db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

// 多个 order
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;

db.Clauses(clause.OrderBy
  Expression: clause.ExprSQL: "FIELD(id,?)", Vars: []interface[]int1, 2, 3, WithoutParentheses: true,
).Find(&User)
// SELECT * FROM users ORDER BY FIELD(id,1,2,3)

(10) limit & offset

Limit 指定获取记录的最大数量 Offset 指定在开始返回记录之前要跳过的记录数量

db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;

// 通过 -1 消除 Limit 条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)

db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;

db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;

// 通过 -1 消除 Offset 条件
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)

(11) group & having

db.Model(&User).Select("name, sum(age) as total").Group("name").查看详情  

go学习golang底层学习笔记(代码片段)

1.1.1.Go编译词法与语法分析意义:解析源代码文件,将文件中字符串序列转换成Token序列把执行词法分析的程序称为词法解析器(lexer)语法解析的结果就是抽象语法树(AST)每个AST都对应一个单独的Go语言文件,这个抽象语法树中包括当前... 查看详情

golang学习笔记6——并发(代码片段)

goroutinegolang里面没有线程的概念,取而代之的是一种叫做goroutine的东西,它是由golang的运行时去调度的,可以完成并发操作。使用goroutine很简单,直接使用go关键字就行,如下面的代码:packagemainimport( "f... 查看详情

学习笔记之microsoftazure(代码片段)

World‘sMostPopularAPIFramework|Swaggerhttps://swagger.io/Swaggeristheworld’slargestframeworkofAPIdevelopertoolsfortheOpenAPISpecification(OAS),enablingdevelopmentacrosstheentireAPIlifecycle,from 查看详情

golang学习笔记1——基础知识(代码片段)

golang变量的声明声明变量有两种方式:使用var声明变量//声明一个整型数据varaint//声明一个字符串varsstring//声明一个浮点数组varc[2]float32//声明一个函数vardfunc(int)bool//声明一个结构体varestruct xint使用:=声明变量并赋值例如&#x... 查看详情

《golang高级编程》学习笔记(代码片段)

一、数组、字符串、切片1、数组定义方式:vara[3]int//定义长度为3的int型数组,元素全部为0varb=[...]int1,2,3//定义长度为3的int型数组,元素为1,2,3varc=[...]int2:3,1:2//定义长度为3的int型数组,元素为0,2,3vard=[...]int1,2,4:5,6//定义... 查看详情

mysql高级学习笔记(代码片段)

文章目录MySQL基础篇学习笔记SQL性能下降的原因SQL的执行顺序索引索引的优劣势索引的分类索引的创建索引结构判断是否需要创建索引EXPLAINEXPLAIN之idEXPLAIN之select_typeEXPLAIN之tableEXPLAIN之typeEXPLAIN之possible_keysEXPLAIN之keyEXPLAIN之key_lenEX... 查看详情

golang学习笔记3——函数(代码片段)

函数函数的形式func函数名称(函数参数参数类型)返回值例如:packagemainimport"fmt"//一个简单的add函数,传入两个整数,返回两个数的和//参数类型都是int,所以合并写参数类型funcadd(a,bint)int returna+b//参数类... 查看详情

尹成学院golang学习快速笔记表达式(代码片段)

2.1保留字语⾔言设计简练,保留字不多。breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypecontinueforimportreturnvar2.2运算符全部运算符、分隔符,以及其他符号。+&+ 查看详情

尹成学院golang学习快速笔记表达式(代码片段)

2.1保留字语⾔言设计简练,保留字不多。breakdefaultfuncinterfaceselectcasedefergomapstructchanelsegotopackageswitchconstfallthroughifrangetypecontinueforimportreturnvar2.2运算符全部运算符、分隔符,以及其他符号。+&+ 查看详情

golang修仙记之gorm(代码片段)

学习了如何连接数据库、简单的错误处理、关闭数据库、创建表、创建表中的一条记录、读取表的记录、更新表的记录、删除标的记录packagemainimport("github.com/jinzhu/gorm"_"github.com/jinzhu/gorm/dialects/mysql""time")typeUserstructgorm.ModelNamestring... 查看详情

golang学习笔记(代码片段)

mysql支持插件式的存储引擎。myisam和innodb。myisam查询速度快,只支持表锁,不支持事务。innodb整体速度快,支持表锁和行锁,支持事务。事务的特点:acid:原子性,一致性(事务开始和结束之间的... 查看详情

golang学习笔记(代码片段)

一、Go语言的知识图谱上图表示了go的应用领域,包括容器如k8s,服务发现如consul,kv存储如etcd,中间件如codis,存储如minio,分布式数据库tidb,此外还有devops、区块链、人工智能、web框架、微服务等等领域的应用... 查看详情

golang学习笔记4——结构体(代码片段)

结构体格式golang中的结构体格式如下:type结构体名称struct 字段名字段类型 字段名字段类型下面定义一个结构体Point,有坐标x,y两个整型字段:typePointstruct xint yint同种类型的字段可以写在一行,如下代码:typeC... 查看详情

golang学习笔记-基础数据类型(代码片段)

今天开始更新go语言学习的内容,一方面可以记录自己的学习日常,还可以督促自己的学习。本博客中大部分内容,均是看来《go圣经》之后的总结。go语言中有四种数据类型:基础类型,复合类型,引用类... 查看详情

golang学习笔记-基础数据类型(代码片段)

今天开始更新go语言学习的内容,一方面可以记录自己的学习日常,还可以督促自己的学习。本博客中大部分内容,均是看来《go圣经》之后的总结。go语言中有四种数据类型:基础类型,复合类型,引用类... 查看详情

golang学习笔记2——容器和流程控制(代码片段)

golang容器golang中的容器主要有这几类:数组切片MapList下面分别记录相关用法。数组数组的定义与初始化数组的定义与初始化,用下面的代码来说明://数组定义与初始化的第一种方式vararr[2]intarr[0]=1arr[1]=20//输出... 查看详情

学习笔记单元测试之mockito学习笔记(代码片段)

Mockito库能够Mock对象、验证结果以及打桩(stubbing)。比如在测试时,可以用mockito模拟查询数据库的操作,即将查询数据库的方法拦截,并人工设置其返回值,这样就不用真正去数据库中拿取数据了。此外还可以对某... 查看详情