GORM 基础 -- Gen
Posted chinusyan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GORM 基础 -- Gen相关的知识,希望对你有一定的参考价值。
1、GEN Guides
GEN:友好和更安全的代码生成
1.1 概述
- 来自动态原始SQL的惯用和可重用API
- 100%类型安全的DAO API,不使用
interface
- Database To Struct遵循GORM约定
- 底层的GORM,支持GORM支持的所有功能,插件,DBMS
1.2 安装
go get -u gorm.io/gen
1.3 Quick start
在你的应用程序中使用gen
是非常简单的,下面是它的工作原理:
1.3.1 用golang编写配置
package main
import "gorm.io/gen"
// Dynamic SQL
type Querier interface
// SELECT * FROM @@table WHERE name = @nameif role !="" AND role = @roleend
FilterWithNameAndRole(name, role string) ([]gen.T, error)
func main()
g := gen.NewGenerator(gen.Config
OutPath: "../query",
Mode: gen.WithoutContext|gen.WithDefaultQuery|gen.WithQueryInterface, // generate mode
)
// gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(gormdb) // reuse your gorm db
// Generate basic type-safe DAO API for struct `model.User` following conventions
g.ApplyBasic(model.User)
// Generate Type Safe API with Dynamic SQL defined on Querier interface for `model.User` and `model.Company`
g.ApplyInterface(func(Querier), model.User, model.Company)
// Generate the code
g.Execute()
1.3.1 Generate Code
go run main.go
1.3.3 在项目中使用生成的代码
import "your_project/query"
func main()
// Basic DAO API
user, err := query.User.Where(u.Name.Eq("modi")).First()
// Dynamic SQL API
users, err := query.User.FilterWithNameAndRole("modi", "admin")
2、动态SQL (Dynamic SQL)
Gen允许从原始SQL(Raw SQL
)生成完全类型安全的惯用Go代码,它使用在接口上的注释,这些接口可以在代码生成期间应用于多个模型(model)。
不仅您调优的SQL查询,而且SQL片段也允许共享和重用,让我们举一个例子:
2.1 Overview
2.1.1 Raw SQL
type Querier interface
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (gen.T, error) // GetByID query data by id and return it as *struct*
// GetUsersByRole query data by roles and return it as *slice of pointer*
// (The below blank line is required to comment for the generated method)
//
// SELECT * FROM @@table WHERE role IN @rolesName
GetByRoles(rolesName ...string) ([]*gen.T, error)
// InsertValue insert value
//
// INSERT INTO @@table (name, age) VALUES (@name, @age)
InsertValue(name string, age int) error
g := gen.NewGenerator(gen.Config
// ... some config
)
// Apply the interface to existing `User` and generated `Employee`
g.ApplyInterface(func(Querier) , model.User, g.GenerateModel("employee"))
g.Execute()
运行上面的配置程序为你的应用程序生成查询接口代码,并使用生成的代码如下:
import "your_project/query"
func main()
user, err := query.User.GetByID(10)
employees, err := query.Employee.GetByRoles("admin", "manager")
err := query.User.InsertValue("modi", 18)
2.1.2 代码片段
代码段通常与DAO接口一起使用
type Querier interface
// FindByNameAndAge query data by name and age and return it as map
//
// where("name=@name AND age=@age")
FindByNameAndAge(name string, age int) (gen.M, error)
g := gen.NewGenerator(gen.Config
// ... some config
)
// Apply the interface to existing `User` and generated `Employee`
g.ApplyInterface(func(Querier) , model.User, g.GenerateModel("employee"))
g.Execute()
use:
import "your_project/query"
func main()
userMap, err := query.User.Where(query.User.Name.Eq("modi")).FilterWithNameAndRole("modi", "admin")
2.1.3 More control
Gen支持有条件的注解和自定义返回结果,参考注解了解更多
2.2 注解语法(Annotation Syntax)
注解(Annotation )是在接口的方法上的注释,Gen将解析它们并为应用的结构体生成 查询API
。
Gen为动态有条件SQL支持提供了一些约定,让我们从三个方面介绍它们:
- 返回的结果
- 模板占位符(Template Placeholder)
- 模板表达式(Template Expression)
2.2.1 返回的结果
Gen允许配置返回的结果类型,它目前支持以下四种基本类型
Option | Description |
---|---|
gen.T | returns struct |
gen.M | returns map |
gen.RowsAffected | returns rowsAffected returned from database (type: int64) |
error | returns error if any |
e.g:
type Querier interface
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (gen.T, error) // returns struct and error
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) gen.T // returns data as struct
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (gen.M, error) // returns map and error
// INSERT INTO @@table (name, age) VALUES (@name, @age)
InsertValue(name string, age int) (gen.RowsAffected, error) // returns affected rows count and error
这些基本类型可以与其他符号组合,如*
,[]
,例如:
type Querier interface
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (*gen.T, error) // returns data as pointer and error
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) (*[]gen.T, error) // returns data as pointer of slice and error
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) ([]*gen.T, error) // returns data as slice of pointer and error
// SELECT * FROM @@table WHERE id=@id
GetByID(id int) ([]gen.M, error) // returns data as slice of map and error
2.2.2 模板占位符
Gen提供了一些占位符来生成动态和安全的SQL
Name | Description |
---|---|
@@table | escaped & quoted table name |
@@ | escaped & quoted table/column name from params |
@ | SQL query params from params |
e.g:
type Filter interface
// SELECT * FROM @@table WHERE @@column=@id
FilterWithColumn(column string, value string) (gen.T, error)
// Apply the `Filter` interface to `User`, `Company`
g.ApplyInterface(func(Filter) , model.User, model.Company)
生成代码后,可以在应用程序中像这样使用它:
import "your_project/query"
func main()
user, err := query.User.FilterWithColumn("name", "jinzhu")
// similar like db.Exec("SELECT * FROM `users` WHERE `name` = ?", "jinzhu")
company, err := query.Company.FilterWithColumn("name", "tiktok")
// similar like db.Exec("SELECT * FROM `companies` WHERE `name` = ?", "tiktok")
2.2.3 表达式模板
Gen为动态条件SQL提供了强大的表达式支持,目前支持以下表达式:
- if/else
- where
- set
- for
if/else
if/else
表达式允许使用golang语法作为条件,它可以写成这样:
if cond1
// do something here
else if cond2
// do something here
else
// do something here
end
例如:
type Querier interface
// SELECT * FROM users WHERE
// if name !=""
// username=@name AND
// end
// role="admin"
QueryWith(name string) (gen.T,error)
一个更复杂的例子:
type Querier interface
// SELECT * FROM users
// if user != nil
// if user.ID > 0
// WHERE id=@user.ID
// else if user.Name != ""
// WHERE username=@user.Name
// end
// end
QueryWith(user *gen.T) (gen.T, error)
如何使用:
query.User.QueryWith(&UserName: "zhangqiang")
// SELECT * FROM users WHERE username="zhangqiang"
where
where
表达式让你更容易为SQL查询编写where
子句,让我们以一个简单的例子为例:
type Querier interface
// SELECT * FROM @@table
// where
// id=@id
// end
Query(id int) gen.T
使用生成的代码,你可以像这样使用它:
query.User.Query(10)
// SELECT * FROM users WHERE id=10
这里是另一个复杂的情况,在这种情况下,您将了解到WHERE
子句只在任何子表达式匹配时插入,并且它可以巧妙地修剪WHERE
子句中不必要的and
, or
, xor
,,
。
type Querier interface
// SELECT * FROM @@table
// where
// if !start.IsZero()
// created_time > @start
// end
// if !end.IsZero()
// AND created_time < @end
// end
// end
FilterWithTime(start, end time.Time) ([]gen.T, error)
生成的代码可以像这样使用:
var (
since = time.Date(2022, 10, 1, 0, 0, 0, 0, time.UTC)
end = time.Date(2022, 10, 10, 0, 0, 0, 0, time.UTC)
zero = time.Time
)
query.User.FilterWithTime(since, end)
// SELECT * FROM `users` WHERE created_time > "2022-10-01" AND created_time < "2022-10-10"
query.User.FilterWithTime(since, zero)
// SELECT * FROM `users` WHERE created_time > "2022-10-01"
query.User.FilterWithTime(zero, end)
// SELECT * FROM `users` WHERE created_time < "2022-10-10"
query.User.FilterWithTime(zero, zero)
// SELECT * FROM `users`
set
用于为SQL查询生成set子句的set
表达式,它将自动删除不必要的,
, 例如:
// UPDATE @@table
// set
// if user.Name != "" username=@user.Name, end
// if user.Age > 0 age=@user.Age, end
// if user.Age >= 18 is_adult=1 else is_adult=0 end
// end
// WHERE id=@id
Update(user gen.T, id int) (gen.RowsAffected, error)
生成的代码可以像这样使用:
query.User.Update(UserName: "jinzhu", Age: 18, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10
query.User.Update(UserName: "jinzhu", Age: 0, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10
query.User.Update(UserAge: 0, 10)
// UPDATE users SET is_adult=0 WHERE id=10
for
for表达式遍历一个切片以生成SQL,让我们通过示例进行解释:
// SELECT * FROM @@table
// where
// for _,user:=range user
// if user.Name !="" && user.Age >0
// (username = @user.Name AND age=@user.Age AND role LIKE concat("%",@user.Role,"%")) OR
// end
// end
// end
Filter(users []gen.T) ([]gen.T, error)
使用:
query.User.Filter([]User
Name: "jinzhu", Age: 18, Role: "admin",
Name: "zhangqiang", Age: 18, Role: "admin",
Name: "modi", Age: 18, Role: "admin",
Name: "songyuan", Age: 18, Role: "admin",
)
// SELECT * FROM users WHERE
// (username = "jinzhu" AND age=18 AND role LIKE concat("%","admin","%")) OR
// (username = "zhangqiang" AND age=18 AND role LIKE concat("%","admin","%"))
// (username = "modi" AND age=18 AND role LIKE concat("%","admin","%")) OR
// (username = "songyuan" AND age=18 AND role LIKE concat("%","admin","%"))
3、DAO
3.1 Overview
Gen遵循配置即代码(Configuration As Code
)实践来生成DAO
接口,下面是对配置的介绍。
3.1.1 配置
您需要将配置编写为可运行的golang程序,通常,该程序将被组织在应用程序的子目录中。
/ configuration.go
package main
import (
"gorm.io/gen"
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
func main()
// Initialize the generator with configuration
g := gen.NewGenerator(gen.Config
OutPath: "../dal", // output directory, default value is ./query
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
FieldNullable: true,
)
// Initialize a *gorm.DB instance
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config)
// Use the above `*gorm.DB` instance to initialize the generator,
// which is required to generate structs from db when using `GenerateModel/GenerateModelAs`
g.UseDB(db)
// Generate default DAO interface for those specified structs
g.ApplyBasic(model.Customer, model.CreditCard, model.Bank, model.Passport)
// Generate default DAO interface for those generated structs from database
companyGenerator := g.GenerateModelAs("company", "MyCompany"),
g.ApplyBasic(
g.GenerateModel("users"),
companyGenerator,
g.GenerateModelAs("people", "Person",
gen.FieldIgnore("deleted_at"),
gen.FieldNewTag("age", `json:"-"`),
),
)
// Execute the generator
g.Execute()
运行上面的程序,它将生成代码到目录../dal
,你可以在你的应用程序中导入dal
包,并使用它的接口来查询数据:
3.1.2 gen.Config
type Config struct
OutPath string // query code path
OutFile string // query code file name, default: gen.go
ModelPkgPath string // generated model code's package name
WithUnitTest bool // generate unit test for query code
FieldNullable bool // generate pointer when field is nullable
FieldCoverable bool // generate pointer when field has default value, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values
FieldSignable bool // detect integer field's unsigned type, adjust generated data type
FieldWithIndexTag bool // generate with gorm index tag
FieldWithTypeTag bool // generate with gorm column type tag
Mode GenerateMode // generator modes
输出选项
Option Name | Description |
---|---|
OutPath | Output destination folder for the generator, default value: ./query |
OutFile | Query code file name, default value: gen.go |
ModelPkgPath | Generated DAO package’s package name, default value: model |
WithUnitTest | Generate unit tests for the DAO package, default value: false |
生成结构体选项
Option Name | Description |
---|---|
FieldNullable | 如果列在数据库中可为空(nullable ),则生成字段类型的指针 |
FieldCoverable | 如果列在数据库中有默认值,则生成字段类型的指针, 避免零值问题, e.g: https://gorm.io/docs/create.html#Default-Values |
FieldSignable | 基于列的数据库数据类型,使用有符号类型作为字段类型 |
FieldWithIndexTag | Generate with gorm index tag |
FieldWithTypeTag | Generate with gorm type tag, for example: gorm:"type:varchar(12)" , default value: false |
请参考数据库到结构体以获取更多选项
生成器模式
Tag Name | Description |
---|---|
gen.WithDefaultQuery | Generate global variable Q as DAO interface, then you can query data like: dal.Q.User.First() |
gen.WithQueryInterface | 生成查询api接口而不是结构体,通常用于模拟测试 |
gen.WithoutContext | 在没有上下文约束的情况下生成代码,然后您可以在不使用上下文的情况下查询数据,如: dal.User.First() , or you have to query with the context, e.g: dal.User.WithContext(ctx).First() |
DAO Interface
一个生成DAO查询接口的示例
type IUserDo interface
// Create
Create(values ...*model.User) error
CreateInBatches(values []*model.User, batchSize int) error
Save(values ...*model.User) error
// Query
Clauses(conds ...clause.Expression) IUserDo
As(alias string) gen.Dao
Columns(cols ...field.Expr) gen.Columns
Not(conds ...gen.Condition) IUserDo
Or(conds ...gen.Condition) IUserDo
Select(conds ...field.Expr) IUserDo
Where(conds ...gen.Condition) IUserDo
Order(conds ...field.Expr) IUserDo
Distinct(cols ...field.Expr) IUserDo
Omit(cols ...field.Expr) IUserDo
Join(table schema.Tabler, on ...field.Expr) IUserDo
LeftJoin(table schema.Tabler, on ...field.Expr) IUserDo
RightJoin(table schema.Tabler, on ...field.Expr) IUserDo
Group(cols ...field.Expr) IUserDo
Having(conds ...gen.Condition) IUserDo
Limit(limit int) IUserDo
Offset(offset int) IUserDo
Scopes(funcs ...func(gen.Dao) gen.Dao) IUserDo
Unscoped() IUserDo
Pluck(column field.Expr, dest interface) error
Attrs(attrs ...field.AssignExpr) IUserDo
Assign(attrs ...field.AssignExpr) IUserDo
Joins(fields ...field.RelationField) IUserDo
Preload(fields ...field.RelationField) IUserDo
Count() (count int64, err error)
FirstOrInit() (*model.User1、Create
1.1 创建纪录
user := UserName: "Jinzhu", Age: 18, Birthday: time.Now()
result := db.Create(&user) // pass pointer of data to Create
user.ID // 回填插入数据的主键
result.Error // 返回的 error 信息
result.RowsAffected // 返回插入记录的个数
1.2 用选定的字段创建记录
创建一个记录并为指定的字段分配值
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")
1.3 Batch Insert
为了有效地插入大量的记录,将一个切片传递给Create
方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法(hook methods
)也将被调用。
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"
// batch size 100
db.CreateInBatches(users, 100)
批量插入也支持 Upsert和Create With Associations时,
用CreateBatchSize
选项初始化GORM,所有INSERT
在创建记录和关联时都会遵守这个选项
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config
CreateBatchSize: 1000,
)
db := db.Session(&gorm.SessionCreateBatchSize: 1000)
users = [5000]UserName: "jinzhu", Pets: []Petpet1, pet2, pet3...
db.Create(&users)
// INSERT INTO users xxx (5 batches)
// INSERT INTO pets xxx (15 batches)
1.4 创建钩子(Hooks)
GORM允许用户定义钩子实现BeforeSave
, BeforeCreate
, AfterSave
, AfterCreate
。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参阅钩子
func (u *User) BeforeCreate(tx *gorm.DB) (err error)
u.UUID = uuid.New()
if u.Role == "admin"
return errors.New("invalid role")
return
如果你想跳过Hooks
方法,你可以使用SkipHooks
会话模式(Session mode),例如:
DB.Session(&gorm.SessionSkipHooks: true).Create(&user)
DB.Session(&gorm.SessionSkipHooks: true).Create(&users)
DB.Session(&gorm.SessionSkipHooks: true).CreateInBatches(users, 100)
1.5 Create From 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创建时,钩子不会被调用,关联不会被保存,主键值也不会被返回填充
1.6 从 SQL Expression/Context Valuer 创建
GORM允许用SQL表达式插入数据,有两种方法来实现这个目标,从map[string]interface
或自定义数据类型创建,例如:
// Create from map
db.Model(User).Create(map[string]interface
"Name": "jinzhu",
"Location": clause.ExprSQL: "ST_PointFromText(?)", Vars: []interface"POINT(100 100)",
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"));
// Create from customized data type
type Location struct
X, Y int
// Scan implements the sql.Scanner interface
func (loc *Location) Scan(v interface) error
// Scan a value into struct from database driver
func (loc Location) GormDataType() string
return "geometry"
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr
return clause.Expr
SQL: "ST_PointFromText(?)",
Vars: []interfacefmt.Sprintf("POINT(%d %d)", loc.X, loc.Y),
type User struct
Name string
Location Location
db.Create(&User
Name: "jinzhu",
Location: LocationX: 100, Y: 100,
)
// INSERT INTO `users` (`name`,`location`) VALUES ("jinzhu",ST_PointFromText("POINT(100 100)"))
1.7 进阶
1.7.1 Create With Associations
在创建带有关联的数据时,如果其关联值不是零,则这些关联将被插入,并且将调用其Hooks
方法。
type CreditCard struct
gorm.Model
Number string
UserID uint
type User struct
gorm.Model
Name string
CreditCard CreditCard
db.Create(&User
Name: "jinzhu",
CreditCard: CreditCardNumber: "411111111111"
)
// INSERT INTO `users` ...
// INSERT INTO `credit_cards` ...
您可以使用Select
、Omit
跳过保存关联,例如:
db.Omit("CreditCard").Create(&user)
// skip all associations
db.Omit(clause.Associations).Create(&user)
1.7.2 默认值
你可以为使用default
标签为字段定义默认值,例如:
type User struct
ID int64
Name string `gorm:"default:galeone"`
Age int64 `gorm:"default:18"`
然后,将零值字段插入数据库时将使用默认值
任何零值,如0
,"
,false
将不会保存到数据库中那些字段定义的默认值,你可能想使用指针类型或Scanner/Valuer
来避免这种情况,例如:
type User struct
gorm.Model
Name string
Age *int `gorm:"default:18"`
Active sql.NullBool `gorm:"default:true"`
你必须为数据库中有默认值或虚拟/生成值的字段设置默认标记,如果你想在迁移时跳过默认值定义,你可以使用default:(-)
,例如:
type User struct
ID string `gorm:"default:uuid_generate_v3()"` // db func
FirstName string
LastName string
Age uint8
FullName string `gorm:"->;type:GENERATED ALWAYS AS (concat(firstname,' ',lastname));default:(-);"`
当使用虚拟/生成(virtual/generated)值时,你可能需要禁用它的创建/更新权限,检查字段级权限
1.8 Upsert / On Conflict
GORM为不同的数据库提供了兼容的Upsert
支持
import "gorm.io/gorm/clause"
// Do nothing on conflict
db.Clauses(clause.OnConflictDoNothing: true).Create(&user)
// Update columns to default value on `id` conflict
db.Clauses(clause.OnConflict
Columns: []clause.ColumnName: "id",
DoUpdates: clause.Assignments(map[string]interface"role": "user"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET ***; SQL Server
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE ***; MySQL
// Use SQL expression
db.Clauses(clause.OnConflict
Columns: []clause.ColumnName: "id",
DoUpdates: clause.Assignments(map[string]interface"count": gorm.Expr("GREATEST(count, VALUES(count))")),
).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `count`=GREATEST(count, VALUES(count));
// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict
Columns: []clause.ColumnName: "id",
DoUpdates: clause.AssignmentColumns([]string"name", "age"),
).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL
// Update all columns to new value on conflict except primary keys and those columns having default values from sql func
db.Clauses(clause.OnConflict
UpdateAll: true,
).Create(&users)
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age", ...;
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age), ...; MySQL
在高级查询中查看FirstOrInit
, FirstOrCreate
更多细节请检出原始SQL和SQL Builder
2、Query
2.1 检索单个对象
GORM提供了First
、Take
、Last
方法来从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1
条件,如果没有找到记录,它将返回错误ErrRecordNotFound
。
// Get the first record ordered by primary key
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// Get one record, no specified order
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// Get last record, ordered by primary key desc
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // returns count of records found
result.Error // returns error or nil
// check error ErrRecordNotFound
errors.Is(result.Error, gorm.ErrRecordNotFound)
如果你想避免ErrRecordNotFound
错误,你可以像db.Limit(1).Find(&user)
一样使用Find
, Find
方法同时接受结构数据和切片数据
使用 Find
而不限制单个对象db.Find(&user)
将查询整个表并只返回第一个非性能和不确定性的对象
First
和Last
方法将根据主键顺序分别查找第一个和最后一个记录。只有当指向目标结构的指针作为参数传递给方法时,或者当使用db.Model()
指定模型时,它们才有效。此外,如果相关模型没有定义主键,则模型将按第一个字段排序。例如:
var user User
var users []User
// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// works because model is specified using `db.Model()`
result := map[string]interface
db.Model(&User).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
// doesn't work
result := map[string]interface
db.Table("users").First(&result)
// works with Take
result := map[string]interface
db.Table("users").Take(&result)
// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct
Code string
Name string
db.First(&Language)
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
用主键检索多个对象
如果主键是数字,则可以通过内联条件使用主键检索对象。当使用字符串时,需要特别注意避免SQL注入;详细信息请查看安全部分。
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);
如果主键是一个字符串(例如,像uuid),查询将被写成如下:
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
当目标对象有一个主键时,主键将被用于构建条件,例如:
var user = UserID: 10
db.First(&user)
// SELECT * FROM users WHERE id = 10;
var result User
db.Model(UserID: 10).First(&result)
// SELECT * FROM users WHERE id = 10;
2.2 检索所有对象
// Get all records
result := db.Find(&users)
// SELECT * FROM users;
result.RowsAffected // returns found records count, equals `len(users)`
result.Error // returns error
2.3 条件(Conditions)
2.3.1 String Conditions
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
// Get all matched records
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';
如果已经设置了对象的主键,则条件查询不会覆盖主键的值,而是将其用作’ and '条件。例如:
var user = UserID: 10
db.Where("id = ?", 20).First(&user)
// SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
查询将给出record not found
错误。因此,在你想使用变量(如user
)从数据库获取新值之前,将主键属性(如id
)设置为nil
。
2.3.2 Struct & Map Conditions
// 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;
// Slice of primary keys
db.Where([]int6420, 21, 22).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22
当使用struct
查询时,GORM只会查询非零字段,这意味着如果你的字段值为0
,'
',false
或其他零值,它将不会被用来构建查询条件,例如:
db.Where(&UserName: "jinzhu", Age: 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
要在查询条件中包含零值,您可以使用map
,它将包括所有键值作为查询条件,例如:
db.Where(map[string]interface"Name": "jinzhu", "Age": 0).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
有关详细信息,请参见Specify Struct search fields。
2.3.3 指定Struct搜索字段
当使用struct
进行搜索时,你可以在查询条件中通过指定使用struct
中的特定值来将相关的字段名或dbname传递给Where()
,例如:
db.Where(&UserName: "jinzhu", "name", "Age").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(&UserName: "jinzhu", "Age").Find(&users)
// SELECT * FROM users WHERE age = 0;
以上是关于GORM 基础 -- Gen的主要内容,如果未能解决你的问题,请参考以下文章