GORM 基础 -- Gen

2023-10-27

https://gorm.io/gen/
github

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 = @name{{if role !=""}} AND role = @role{{end}}
  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(&User{Name: "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(User{Name: "jinzhu", Age: 18}, 10)
// UPDATE users SET username="jinzhu", age=18, is_adult=1 WHERE id=10

query.User.Update(User{Name: "jinzhu", Age: 0}, 10)
// UPDATE users SET username="jinzhu", is_adult=0 WHERE id=10

query.User.Update(User{Age: 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.User, error)
  FirstOrCreate() (*model.User, error)
  Returning(value interface{}, columns ...string) IUserDo

  First() (*model.User, error)
  Take() (*model.User, error)
  Last() (*model.User, error)
  Find() ([]*model.User, error)
  FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.User, err error)
  FindInBatches(result *[]*model.User, batchSize int, fc func(tx gen.Dao, batch int) error) error
  FindByPage(offset int, limit int) (result []*model.User, count int64, err error)
  ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
  Scan(result interface{}) (err error)

  // Update
  Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
  UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
  Updates(value interface{}) (info gen.ResultInfo, err error)
  UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
  UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
  UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
  UpdateFrom(q gen.SubQuery) gen.Dao

  // Delete
  Delete(...*model.User) (info gen.ResultInfo, err error)

  // Common
  Debug() IUserDo
  WithContext(ctx context.Context) IUserDo
  WithResult(fc func(tx gen.Dao)) gen.ResultInfo

  ReadDB() IUserDo
  WriteDB() IUserDo
}

3.1.3 使用示例

如果启用了gen.WithDefaultQuery,则使用全局变量Q

import "your_project/dal"

func main() {
  // Initialize a *gorm.DB instance
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

  dal.SetDefault(db)

  // query the first user
  user, err := dal.Q.User.First()
}

初始化DAO查询接口

import "your_project/dal"

var Q dal.Query

func main() {
  // Initialize a *gorm.DB instance
  db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})

  Q = dal.Use(db)

  // query the first user
  user, err := Q.User.First()
}

如需更多使用详情,请查看下节

3.2 Create

3.2.1 创建纪录

您可以使用类型安全的Create方法插入记录,该方法在创建数据时只接受当前模型的指针

// u refer to query.user
user := model.User{Name: "Modi", Age: 18, Birthday: time.Now()}

u := query.User
err := u.WithContext(ctx).Create(&user) // pass pointer of data to Create

err // returns error

3.2.2 用选定的字段创建记录

您可以在创建数据时使用Select,它只会插入那些选定的字段

u := query.User
u.WithContext(ctx).Select(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`name`,`age`) VALUES ("modi", 18)

ignore fields with Omit:

u := query.User
u.WithContext(ctx).Omit(u.Name, u.Age).Create(&user)
// INSERT INTO `users` (`address`, `birthday`) VALUES ("2021-08-17 20:54:12.000", 18)

3.2.3 批量插入

为了有效地插入大量的记录,将一个切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值。

var users = []*model.User{{Name: "modi"}, {Name: "zhangqiang"}, {Name: "songyuan"}}
query.User.WithContext(ctx).Create(users...)

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

你可以在创建CreateInBatches时指定批大小(batch size ),例如:

var users = []*User{{Name: "modi_1"}, ...., {Name: "modi_10000"}}

// batch size 100
query.User.WithContext(ctx).CreateInBatches(users, 100)

如果你也可以在gorm.Config / gorm.Session中设置CreateBatchSize

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  CreateBatchSize: 1000,
})
// OR
db = db.Session(&gorm.Session{CreateBatchSize: 1000})

u := query.NewUser(db)

var users = []User{{Name: "modi_1"}, ...., {Name: "modi_5000"}}

u.WithContext(ctx).Create(&users)
// INSERT INTO users xxx (5 batches)

3.2.4 Upsert / On Conflict

Gen为不同的数据库提供了兼容的Upsert支持

import "gorm.io/gorm/clause"

// Do nothing on conflict
err := query.User.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

// Update columns to default value on `id` conflict
err := query.User.WithContext(ctx).Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "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

err := query.User.WithContext(ctx).Clauses(clause.OnConflict{Columns: []string{"Name", "Age"}}).Create(&user).Error
// 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, except primary keys, to new value on conflict
err := query.User.WithContext(ctx).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

3.3 Query

3.3.1 检索单个对象

生成的代码提供了FirstTakeLast方法来从数据库中检索单个对象,它在查询数据库时添加了LIMIT 1条件,如果没有找到记录,它将返回错误ErrRecordNotFound

u := query.User

// Get the first record ordered by primary key
user, err := u.WithContext(ctx).First()
// SELECT * FROM users ORDER BY id LIMIT 1;

// Get one record, no specified order
user, err := u.WithContext(ctx).Take()
// SELECT * FROM users LIMIT 1;

// Get last record, ordered by primary key desc
user, err := u.WithContext(ctx).Last()
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

// select by write db
user, err := u.WithContext(ctx).WriteDB().Last()

// check error ErrRecordNotFound
errors.Is(err, gorm.ErrRecordNotFound)

3.3.2 用主键检索对象

u := query.User

user, err := u.WithContext(ctx).Where(u.ID.Eq(10)).First()
// SELECT * FROM users WHERE id = 10;

users, err := u.WithContext(ctx).Where(u.ID.In(1,2,3)).Find()
// SELECT * FROM users WHERE id IN (1,2,3);

如果主键是一个字符串(例如,像uuid),查询将被写成如下:

user, err := u.WithContext(ctx).Where(u.ID.Eq("1b74413f-f3b8-409f-ac47-e8c062e3472a")).First()
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

3.3.3 检索所有对象

u := query.User

// Get all records
users, err := u.WithContext(ctx).Find()
// SELECT * FROM users;

3.3.4 条件(Conditions)

字段查询接口

Gen 为每个字段生成类型安全的接口,可以使用它们生成SQL表达式

Field Type Supported Interface
generic IsNull/IsNotNull/Count/Eq/Neq/Gt/Gte/Lt/Lte/Like/Value/Sum/IfNull
int Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/Mod/FloorDiv/RightShift/LeftShift/BitXor/BitAnd/BitOr/BitFlip/Value/Zero/Sum/IfNull
uint same with int
float Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/FloorDiv/Floor/Value/Zero/Sum/IfNull
string Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Like/NotLike/Regexp/NotRegxp/FindInSet/FindInSetWith/Value/Zero/IfNull
bool Not/Is/And/Or/Xor/BitXor/BitAnd/BitOr/Value/Zero
time Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Add/Sub/Date/DateDiff/DateFormat/Now/CurDate/CurTime/DayName/MonthName/Month/Day/Hour/Minute/Second/MicroSecond/DayOfWeek/DayOfMonth/FromDays/FromUnixtime/Value/Zero/Sum/IfNull

下面是一些用法示例:

u := query.User

// Get first matched record
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).First()
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;

// Get all matched records
users, err := u.WithContext(ctx).Where(u.Name.Neq("modi")).Find()
// SELECT * FROM users WHERE name <> 'modi';

// IN
users, err := u.WithContext(ctx).Where(u.Name.In("modi", "zhangqiang")).Find()
// SELECT * FROM users WHERE name IN ('modi','zhangqiang');

// LIKE
users, err := u.WithContext(ctx).Where(u.Name.Like("%modi%")).Find()
// SELECT * FROM users WHERE name LIKE '%modi%';

// AND
users, err := u.WithContext(ctx).Where(u.Name.Eq("modi"), u.Age.Gte(17)).Find()
// SELECT * FROM users WHERE name = 'modi' AND age >= 17;

// Time
users, err := u.WithContext(ctx).Where(u.Birthday.Gt(birthTime).Find()
// SELECT * FROM users WHERE birthday > '2000-01-01 00:00:00';

// BETWEEN
users, err := u.WithContext(ctx).Where(u.Birthday.Between(lastWeek, today)).Find()
// SELECT * FROM users WHERE birthday BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

Not 条件

建造Not 条件,类似于Where

u := query.User

user, err := u.WithContext(ctx).Not(u.Name.Eq("modi")).First()
// SELECT * FROM users WHERE NOT name = "modi" ORDER BY id LIMIT 1;

// Not In
users, err := u.WithContext(ctx).Not(u.Name.In("modi", "zhangqiang")).Find()
// SELECT * FROM users WHERE name NOT IN ("modi", "zhangqiang");

// Not In slice of primary keys
user, err := u.WithContext(ctx).Not(u.ID.In(1,2,3)).First()
// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;

Or 条件

u := query.User

users, err := u.WithContext(ctx).Where(u.Role.Eq("admin")).Or(u.Role.Eq("super_admin")).Find()
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';

Group 条件

使用Group 条件更容易编写复杂的SQL查询

p := query.Pizza
pd := p.WithContext(ctx)

pizzas, err := pd.Where(
    pd.Where(p.Pizza.Eq("pepperoni")).
        Where(pd.Where(p.Size.Eq("small")).Or(p.Size.Eq("medium"))),
).Or(
    pd.Where(p.Pizza.Eq("hawaiian")).Where(p.Size.Eq("xlarge")),
).Find()

// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")

选择特定字段

Select允许您指定要从数据库检索的字段。否则,GORM将默认选择所有字段。

u := query.User

users, err := u.WithContext(ctx).Select(u.Name, u.Age).Find()
// SELECT name, age FROM users;

u.WithContext(ctx).Select(u.Age.Avg()).Rows()
// SELECT Avg(age) FROM users;

元组查询 (Tuple Query)

u := query.User

users, err := u.WithContext(ctx).Where(u.WithContext(ctx).Columns(u.ID, u.Name).In(field.Values([][]interface{}{{1, "modi"}, {2, "zhangqiang"}}))).Find()
// SELECT * FROM `users` WHERE (`id`, `name`) IN ((1,'humodi'),(2,'tom'));

JSON Query

u := query.User

users, err := u.WithContext(ctx).Where(gen.Cond(datatypes.JSONQuery("attributes").HasKey("role"))...).Find()
// SELECT * FROM `users` WHERE JSON_EXTRACT(`attributes`,'$.role') IS NOT NULL;

Order

从数据库检索记录时指定顺序

u := query.User

users, err := u.WithContext(ctx).Order(u.Age.Desc(), u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;

// Multiple orders
users, err := u.WithContext(ctx).Order(u.Age.Desc()).Order(u.Name).Find()
// SELECT * FROM users ORDER BY age DESC, name;

通过字符串获取字段

u := query.User

orderCol, ok := u.GetFieldByName(orderColStr) // maybe orderColStr == "id"
if !ok {
  // User doesn't contains orderColStr
}

users, err := u.WithContext(ctx).Order(orderCol).Find()
// SELECT * FROM users ORDER BY age;

// OR Desc
users, err := u.WithContext(ctx).Order(orderCol.Desc()).Find()
// SELECT * FROM users ORDER BY age DESC;

Limit & Offset

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

u := query.User

urers, err := u.WithContext(ctx).Limit(3).Find()
// SELECT * FROM users LIMIT 3;

// Cancel limit condition with -1
users, err := u.WithContext(ctx).Limit(10).Limit(-1).Find()
// SELECT * FROM users;

users, err := u.WithContext(ctx).Offset(3).Find()
// SELECT * FROM users OFFSET 3;

users, err := u.WithContext(ctx).Limit(10).Offset(5).Find()
// SELECT * FROM users OFFSET 5 LIMIT 10;

// Cancel offset condition with -1
users, err := u.WithContext(ctx).Offset(10).Offset(-1).Find()
// SELECT * FROM users;

Group By & Having

u := query.User

var users []struct {
    Name  string
    Total int
}
err := u.WithContext(ctx).Select(u.Name, u.ID.Count().As("total")).Group(u.Name).Scan(&users)
// SELECT name, count(id) as total FROM `users` GROUP BY `name`

err := u.WithContext(ctx).Select(u.Name, u.Age.Sum().As("total")).Where(u.Name.Like("%modi%")).Group(u.Name).Scan(&users)
// SELECT name, sum(age) as total FROM `users` WHERE name LIKE "%modi%" GROUP BY `name`

err := u.WithContext(ctx).Select(u.Name, u.Age.Sum().As("total")).Group(u.Name).Having(u.Name.Eq("group")).Scan(&users)
// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"

rows, err := u.WithContext(ctx).Select(u.Birthday.As("date"), u.Age.Sum().As("total")).Group(u.Birthday).Rows()
for rows.Next() {
  ...
}

o := query.Order

rows, err := o.WithContext(ctx).Select(o.CreateAt.Date().As("date"), o.Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Rows()
for rows.Next() {
  ...
}

var results []struct {
    Date  time.Time
    Total int
}

o.WithContext(ctx).Select(o.CreateAt.Date().As("date"), o.WithContext(ctx).Amount.Sum().As("total")).Group(o.CreateAt.Date()).Having(u.Amount.Sum().Gt(100)).Scan(&results)

Distinct

从模型中选择不同的值

u := query.User

users, err := u.WithContext(ctx).Distinct(u.Name, u.Age).Order(u.Name, u.Age.Desc()).Find()

Distinct 也可以和 Pluck and Count 一起用

Joins

指定连接条件

q := query
u := q.User
e := q.Email
c := q.CreditCard

type Result struct {
    Name  string
    Email string
    ID    int64
}

var result Result

err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Scan(&result)
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id

// self join
var result Result
u2 := u.As("u2")
err := u.WithContext(ctx).Select(u.Name, u2.ID).LeftJoin(u2, u2.ID.EqCol(u.ID)).Scan(&result)
// SELECT users.name, u2.id FROM `users` left join `users` u2 on u2.id = users.id

//join with sub query
var result Result
e2 := e.As("e2")
err := u.WithContext(ctx).Select(u.Name, e2.Email).LeftJoin(e.WithContext(ctx).Select(e.Email, e.UserID).Where(e.UserID.Gt(100)).As("e2"), e2.UserID.EqCol(u.ID)).Scan(&result)
// SELECT users.name, e2.email FROM `users` left join (select email,user_id from emails  where user_id > 100) as e2 on e2.user_id = users.id

rows, err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Rows()
for rows.Next() {
  ...
}

var results []Result

err := u.WithContext(ctx).Select(u.Name, e.Email).LeftJoin(e, e.UserID.EqCol(u.ID)).Scan(&results)

// multiple joins with parameter
users := u.WithContext(ctx).Join(e, e.UserID.EqCol(u.id), e.Email.Eq("modi@example.org")).Join(c, c.UserID.EqCol(u.ID)).Where(c.Number.Eq("411111111111")).Find()

New Field Expiression

有时您可能需要为动态SQL生成创建一个动态字段

Field Type Create Function
generic NewField
int NewInt/NewInt8/…/NewInt64
uint NewUint/NewUint8/…/NewUint64
float NewFloat32/NewFloat64
string NewString/NewBytes
bool NewBool
time NewTime

用例:

通用字段(Generic Fields)
import "gorm.io/gen/field"

// create a new generic field map to `generic_a`
f := field.NewField("table_name", "generic")
// `table_name`.`generic` IS NULL
f.IsNull()

// compare fields
id := field.NewField("user", "id")
anotherID := field.NewField("another", "id")
// `user`.`id` = `another`.`id`
id.EqCol(anotherID)
int/uint/float Fields
// int field
f := field.NewInt("user", "id")
// `user`.`id` = 123
f.Eq(123)
// `user`.`id` DESC
f.Desc()
// `user`.`id` AS `user_id`
f.As("user_id")
// COUNT(`user`.`id`)
f.Count()
// SUM(`user`.`id`)
f.Sum()
// SUM(`user`.`id`) > 123
f.Sum().Gt(123)
// ((`user`.`id`+1)*2)/3
f.Add(1).Mul(2).Div(3),
// `user`.`id` <<< 3
f.LeftShift(3)
String Fields
name := field.NewStirng("user", "name")
// `user`.`name` = "modi"
name.Eq("modi")
// `user`.`name` LIKE %modi%
name.Like("%modi%")
// `user`.`name` REGEXP .*
name.Regexp(".*")
// `user`.`name` FIND_IN_SET(`name`,"modi,jinzhu,zhangqiang")
name.FindInSet("modi,jinzhu,zhangqiang")
// `uesr`.`name` CONCAT("[",name,"]")
name.Concat("[", "]")
Time Fields
birth := field.NewStirng("user", "birth")
// `user`.`birth` = ? (now)
birth.Eq(time.Now())
// DATE_ADD(`user`.`birth`, INTERVAL ? MICROSECOND)
birth.Add(time.Duration(time.Hour).Microseconds())
// DATE_FORMAT(`user`.`birth`, "%W %M %Y")
birth.DateFormat("%W %M %Y")
Bool Fields
active := field.NewBool("user", "active")
// `user`.`active` = TRUE
active.Is(true)
// NOT `user`.`active`
active.Not()
// `user`.`active` AND TRUE
active.And(true)

3.3.5 SubQuery

子查询可以嵌套在查询中,GEN可以在使用Dao对象作为参数时生成子查询

o := query.Order
u := query.User

orders, err := o.WithContext(ctx).Where(o.WithContext(ctx).Columns(o.Amount).Gt(o.WithContext(ctx).Select(o.Amount.Avg())).Find()
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");

subQuery := u.WithContext(ctx).Select(u.Age.Avg()).Where(u.Name.Like("name%"))
users, err := u.WithContext(ctx).Select(u.Age.Avg().As("avgage")).Group(u.Name).Having(u.WithContext(ctx).Columns(u.Age.Avg()).Gt(subQuery).Find()
// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")

// Select users with orders between 100 and 200
subQuery1 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(100))
subQuery2 := o.WithContext(ctx).Select(o.ID).Where(o.UserID.EqCol(u.ID), o.Amount.Gt(200))
u.WithContext(ctx).Exists(subQuery1).Not(u.WithContext(ctx).Exists(subQuery2)).Find()
// SELECT * FROM `users` WHERE EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 100 AND `orders`.`deleted_at` IS NULL) AND NOT EXISTS (SELECT `orders`.`id` FROM `orders` WHERE `orders`.`user_id` = `users`.`id` AND `orders`.`amount` > 200 AND `orders`.`deleted_at` IS NULL) AND `users`.`deleted_at` IS NULL

From SubQuery

GORM允许使用方法TableFROM子句中使用子查询,例如

u := query.User
p := query.Pet

users, err := gen.Table(u.WithContext(ctx).Select(u.Name, u.Age).As("u")).Where(u.Age.Eq(18)).Find()
// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18

subQuery1 := u.WithContext(ctx).Select(u.Name)
subQuery2 := p.WithContext(ctx).Select(p.Name)
users, err := gen.Table(subQuery1.As("u"), subQuery2.As("p")).Find()
db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})
// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p

3.4 Update

3.4.1 更新单列

当使用Update更新单个列时,它需要有任何条件,否则将引发错误ErrMissingWhereClause,例如:

u := query.User

// Update with conditions
u.WithContext(ctx).Where(u.Activate.Is(true)).Update(u.Name, "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// Update with conditions
u.WithContext(ctx).Where(u.Activate.Is(true)).Update(u.Age, u.Age.Add(1))
// or
u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Add(1))
// UPDATE users SET age=age+1, updated_at='2013-11-17 21:34:10' WHERE active=true;

u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Zero())
// UPDATE users SET age=0, updated_at='2013-11-17 21:34:10' WHERE active=true;

3.4.2 更新多列

Updates支持使用structmap[string]interface{}进行更新,当使用struct进行更新时,默认只更新非零字段

u := query.User

// Update attributes with `map`
u.WithContext(ctx).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// Update attributes with `struct`
u.WithContext(ctx).Where(u.ID.Eq(111)).Updates(model.User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// Update with expression
u.WithContext(ctx).Where(u.ID.Eq(111)).UpdateSimple(u.Age.Add(1), u.Number.Add(1))
// UPDATE users SET age=age+1,number=number+1, updated_at='2013-11-17 21:34:10' WHERE id=111;

u.WithContext(ctx).Where(u.Activate.Is(true)).UpdateSimple(u.Age.Value(17), u.Number.Zero(), u.Birthday.Null())
// UPDATE users SET age=17, number=0, birthday=NULL, updated_at='2013-11-17 21:34:10' WHERE active=true;

当使用struct更新时,GEN将只更新非零字段,您可能希望使用map更新属性或使用Select指定要更新的字段

3.4.3 更新所选字段

如果要更新所选字段或在更新时忽略某些字段,可以使用Select, Omit

u := query.User

// Select with Map
// User's ID is `111`:
u.WithContext(ctx).Select(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

u.WithContext(ctx).Omit(u.Name).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

result, err := u.WithContext(ctx).Where(u.ID.Eq(111)).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

result.RowsAffected // affect rows number
err                 // error

3.4.5 从SubQuery更新

使用SubQuery更新表

u := query.User
c := query.Company

u.WithContext(ctx).Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyID)))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);

u.WithContext(ctx).Where(u.Name.Eq("modi")).Update(u.CompanyName, c.Select(c.Name).Where(c.ID.EqCol(u.CompanyID)))

3.4.6 从SubQuery更新多个列

使用SubQuery (用于MySQL)更新多个列:

u := query.User
c := query.Company

ua := u.As("u")
ca := u.As("c")

ua.WithContext(ctx).UpdateFrom(ca.WithContext(ctx).Select(c.ID, c.Address, c.Phone).Where(c.ID.Gt(100))).
Where(ua.CompanyID.EqCol(ca.ID)).
UpdateSimple(
  ua.Address.SetCol(ca.Address),
  ua.Phone.SetCol(ca.Phone),
)
// UPDATE `users` AS `u`,(
//   SELECT `company`.`id`,`company`.`address`,`company`.`phone`
//   FROM `company` WHERE `company`.`id` > 100 AND `company`.`deleted_at` IS NULL
// ) AS `c`
// SET `u`.`address`=`c`.`address`,`c`.`phone`=`c`.`phone`,`updated_at`='2021-11-11 11:11:11.111'
// WHERE `u`.`company_id` = `c`.`id`

3.5 Delete

3.5.1 删除记录

当删除一条记录时,它需要有任何条件,否则它将引发错误ErrMissingWhereClause,例如:

e := query.Email

// Email's ID is `10`
e.WithContext(ctx).Where(e.ID.Eq(10)).Delete()
// DELETE from emails where id = 10;

// Delete with additional conditions
e.WithContext(ctx).Where(e.ID.Eq(10), e.Name.Eq("modi")).Delete()
// DELETE from emails where id = 10 AND name = "modi";

result, err := e.WithContext(ctx).Where(e.ID.Eq(10), e.Name.Eq("modi")).Delete()

result.RowsAffected // affect rows number
err                 // error

3.5.2 用主键删除

GEN允许使用带有inline 条件的主键删除对象,它与数字一起工作。

u.WithContext(ctx).Where(u.ID.In(1,2,3)).Delete()
// DELETE FROM users WHERE id IN (1,2,3);

3.5.3 批量删除

指定的值没有主值GEN将执行批量删除,它将删除所有匹配的记录

e := query.Email

e.WithContext(ctx).Where(e.Name.Like("%modi%")).Delete()
// DELETE from emails where email LIKE "%modi%";

3.5.4 Soft Delete

如果你的模型包括一个gorm.DeletedAt字段(包含在gorm.Model中),它将自动获得软删除能力!

当调用Delete时,记录不会从数据库中删除,但是GORM会将DeletedAt的值设置为当前时间,并且数据不能再用普通的查询方法找到。

// Batch Delete
u.WithContext(ctx).Where(u.Age.Eq(20)).Delete()
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// Soft deleted records will be ignored when querying
users, err := u.WithContext(ctx).Where(u.Age.Eq(20)).Find()
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

如果你不想把``gorm.Model`包含进去的话。您也可以启用软删除功能,像这样:

type User struct {
    ID      int
    Deleted gorm.DeletedAt
    Name    string
}

3.5.5 查找软删除记录

你可以用Unscoped找到软删除的记录

users, err := db.WithContext(ctx).Unscoped().Where(u.Age.Eq(20)).Find()
// SELECT * FROM users WHERE age = 20;

3.5.6 永久删除(Delete permanently)

您可以使用Unscoped永久删除匹配的记录

o.WithContext(ctx).Unscoped().Where(o.ID.Eq(10)).Delete()
// DELETE FROM orders WHERE id=10;

Delete Associations

如果存在,删除源和参数(source & arguments )之间的关系,只删除引用,不会从DB中删除这些对象。

u := query.User

u.Languages.Model(&user).Delete(&languageZH, &languageEN)

u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)

Delete with Select

在删除记录时,可以删除选中的有has one/has many/many2many关系。例如:

u := query.User

// delete user's account when deleting user
u.Select(u.Account).Delete(&user)

// delete user's Orders, CreditCards relations when deleting user
db.Select(u.Orders.Field(), u.CreditCards.Field()).Delete(&user)

// delete user's has one/many/many2many relations when deleting user
db.Select(field.AssociationsFields).Delete(&user)

3.6 Transaction

使用事务函数的过程如下:

q := query.Use(db)

q.Transaction(func(tx *query.Query) error {
  if _, err := tx.User.WithContext(ctx).Where(tx.User.ID.Eq(100)).Delete(); err != nil {
    return err
  }
  if _, err := tx.Article.WithContext(ctx).Create(&model.User{Name:"modi"}); err != nil {
    return err
  }
  return nil
})

3.6.1 Nested Transactions

GEN支持嵌套事务,你可以回滚在一个大事务范围内执行的操作的子集,例如:

q := query.Use(db)

q.Transaction(func(tx *query.Query) error {
  tx.User.WithContext(ctx).Create(&user1)

  tx.Transaction(func(tx2 *query.Query) error {
    tx2.User.WithContext(ctx).Create(&user2)
    return errors.New("rollback user2") // Rollback user2
  })

  tx.Transaction(func(tx3 *query.Query) error {
    tx3.User.WithContext(ctx).Create(&user3)
    return nil
  })

  return nil
})

// Commit user1, user3

3.6.2 手动 Transactions

q := query.Use(db)

// begin a transaction
tx := q.Begin()

// do some database operations in the transaction (use 'tx' from this point, not 'db')
tx.User.WithContext(ctx).Create(...)

// ...

// rollback the transaction in case of error
tx.Rollback()

// Or commit the transaction
tx.Commit()

例如:

q := query.Use(db)

func doSomething(ctx context.Context, users ...*model.User) (err error) {
    tx := q.Begin()
    defer func() {
        if recover() != nil || err != nil {
            _ = tx.Rollback()
        }
    }()

    err = tx.User.WithContext(ctx).Create(users...)
    if err != nil {
        return
    }
    return tx.Commit()
}

3.6.3 SavePoint/RollbackTo

GEN提供了SavePoint, RollbackTo来保存点和回滚到一个保存点,例如:

tx := q.Begin()
txCtx = tx.WithContext(ctx)

txCtx.User.Create(&user1)

tx.SavePoint("sp1")
txCtx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1

3.7 关联 (Associations)

GEN会像GORM一样自动保存关联。关系(BelongsTo/HasOne/HasMany/Many2Many)重用GORM的标记(tag)。
该特性目前只支持现有模型。

3.7.1 关系 (Relation)

有四种关系。

const (
    HasOne    RelationshipType = RelationshipType(schema.HasOne)    // HasOneRel has one relationship
    HasMany   RelationshipType = RelationshipType(schema.HasMany)   // HasManyRel has many relationships
    BelongsTo RelationshipType = RelationshipType(schema.BelongsTo) // BelongsToRel belongs to relationship
    Many2Many RelationshipType = RelationshipType(schema.Many2Many) // Many2ManyRel many to many relationship
)

hasOne 正向关联,belongsTo 反向关联。
简单的讲就是,没有太大的区别,只是在逻辑上出现的思想的偏差(逻辑的合理性)。
belongsTo:可以理解为属于
hasOne:可以理解为拥有
首先,我们创建两张表。
user表 字段 id name password字段
user_address表 id user_id city字段
不难看出,user_address是基于user扩展出来的表。我们可以说,user拥有user_address的外键,user_address的外键属于user。
User模型中关联user_address表的时候使用hasOne
UserAddress模型中关联user表的时候使用belongsTo

关联现有模型

package model

// exist model
type Customer struct {
    gorm.Model
    CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer"`
}

type CreditCard struct {
    gorm.Model
    Number        string
    CustomerRefer uint
}

GEN将检测模型的关联:

// specify model
g.ApplyBasic(model.Customer{}, model.CreditCard{})

// assoications will be detected and converted to code 
package query

type customer struct {
    ...
    CreditCards customerHasManyCreditCards
}

type creditCard struct{
    ...
}

关联数据库中的表

关联必须由gen.FieldRelate指定

card := g.GenerateModel("credit_cards")
customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", card, 
    &field.RelateConfig{
        // RelateSlice: true,
        GORMTag: "foreignKey:CustomerRefer",
    }),
)

g.ApplyBasic(card, custormer)

GEN将生成带有相关字段的模型:

// customers
type Customer struct {
    ID          int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    CreatedAt   time.Time      `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    UpdatedAt   time.Time      `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    DeletedAt   gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
    CreditCards []CreditCard   `gorm:"foreignKey:CustomerRefer" json:"credit_cards"`
}


// credit_cards
type CreditCard struct {
    ID            int64          `gorm:"column:id;type:bigint(20) unsigned;primaryKey" json:"id"`
    CreatedAt     time.Time      `gorm:"column:created_at;type:datetime(3)" json:"created_at"`
    UpdatedAt     time.Time      `gorm:"column:updated_at;type:datetime(3)" json:"updated_at"`
    DeletedAt     gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3)" json:"deleted_at"`
    CustomerRefer int64          `gorm:"column:customer_refer;type:bigint(20) unsigned" json:"customer_refer"`
}

如果关联模型已经存在,gen.FieldRelateModel可以帮助您建立它们之间的关联。

customer := g.GenerateModel("customers", gen.FieldRelateModel(field.HasMany, "CreditCards", model.CreditCard{}, 
    &field.RelateConfig{
        // RelateSlice: true,
        GORMTag: "foreignKey:CustomerRefer",
    }),
)

g.ApplyBasic(custormer)

Relate Config

type RelateConfig struct {
    // specify field's type
    RelatePointer      bool // ex: CreditCard  *CreditCard
    RelateSlice        bool // ex: CreditCards []CreditCard
    RelateSlicePointer bool // ex: CreditCards []*CreditCard

    JSONTag      string // related field's JSON tag
    GORMTag      string // related field's GORM tag
    NewTag       string // related field's new tag
    OverwriteTag string // related field's tag
}

3.7.2 Operation

跳过自动创建/更新

user := model.User{
  Name:            "modi",
  BillingAddress:  Address{Address1: "Billing Address - Address 1"},
  ShippingAddress: Address{Address1: "Shipping Address - Address 1"},
  Emails:          []Email{
    {Email: "modi@example.com"},
    {Email: "modi-2@example.com"},
  },
  Languages:       []Language{
    {Name: "ZH"},
    {Name: "EN"},
  },
}

u := query.Use(db).User

u.WithContext(ctx).Select(u.Name).Create(&user)
// INSERT INTO "users" (name) VALUES ("jinzhu", 1, 2);

u.WithContext(ctx).Omit(u.BillingAddress.Field()).Create(&user)
// Skip create BillingAddress when creating a user

u.WithContext(ctx).Omit(u.BillingAddress.Field("Address1")).Create(&user)
// Skip create BillingAddress.Address1 when creating a user

u.WithContext(ctx).Omit(field.AssociationFields).Create(&user)
// Skip all associations when creating a user

方法Field将使用.连接一系列字段名。例如:u.BillingAddress.Field("Address1", "Street")等于BillingAddress.Address1.Street

找到关联

找到匹配的关联

u := query.Use(db).User

languages, err = u.Languages.Model(&user).Find()

找到带条件的关联

q := query.Use(db)
u := q.User

languages, err = u.Languages.Where(q.Language.Name.In([]string{"ZH","EN"})).Model(&user).Find()

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GORM 基础 -- Gen 的相关文章

  • 我应该使用平面表还是标准化数据库?

    我目前正在开发一个使用 MySQL 数据库作为后端的 Web 应用程序 在继续下一步之前 我需要知道什么更适合我的情况 简而言之 在这个应用程序中 用户将能够使用任何数字字段 他们决定 构建自己的表单 现在我将其全部存储在通过外键链接的几个
  • 如何将 mysql 转换为 mysqli? [复制]

    这个问题在这里已经有答案了 我厌倦了将 mysql 转换为 mysqli 但似乎收到了很多错误和警告 连接到数据库没有问题 但其余代码似乎错误 我做错了什么 sql
  • 当复选框条件更改时,如何使用ajax更新mysql数据库?

    我有一个在客户端按行显示的文章表 每篇文章都有一个唯一的 ID 并包含一个复选框以指示该文章是否被选中为收藏夹 如果它是最喜欢的 则该复选框已被选中 如果没有 则未选中 现在 如果特定于每一行的复选框条件发生变化 我需要 js 或 jque
  • oursql 中的参数化查询

    如果有人能告诉我是否可以使用命名占位符进行参数化查询 我将不胜感激oursql 一个用于与 MySQL 数据库交互的 python 模块 例如 我尝试了一种可以与 sqlite3 一起使用的查询 c execute select from
  • 在 django ORM 中查询时如何将 char 转换为整数?

    最近开始使用 Django ORM 我想执行这个查询 select student id from students where student id like 97318 order by CAST student id as UNSIG
  • MySQL 将表从 Latin1 转换为 utf8

    我需要将包含大量数据的表从 Latin1 转换为 utf8 以便它可以接受韩语字符 如何更改该表而不损坏其中的数据 我的 SQL 语句是什么 最好的方法是什么 ALTER TABLE database name table name CON
  • 使用连接池后如何处理过多的并发连接?

    Scenario 假设您有一个拥有大量流量的网站或应用程序 即使使用数据库连接池 性能也会受到真正的打击 站点 应用程序甚至可能崩溃 因为并发连接太多 Question 人们有什么选择来处理这个问题 我的想法 我在想有这个问题的人可以创建多
  • 如何为我的整个 Node.js 应用程序使用相同的 MySQL 连接?

    我有一个app js 我从那里运行我的整个应用程序 在 app js 内部 我require许多文件中都有代码 对于每个文件 我都这样做 var mysql require mysql var mclient mysql createCon
  • MySQL 左连接 WHERE table2.field = "X"

    我有以下表格 pages Field Type Null Key Default Extra page id int 11 NO PRI NULL auto increment type varchar 20 NO NULL
  • meta_query,如何使用关系 OR 和 AND 进行搜索?

    已解决 请参阅下面的答案 我有一个名为的自定义帖子类型BOOKS 它有几个自定义字段 名称为 TITLE AUTHOR GENRE RATING 我该如何修复我的meta query下面的代码以便仅books在自定义字段中包含搜索词 tit
  • MySQL正则表达式:如何将字符串中的数字与\d匹配?

    我有一个专栏release date它以字符串格式存储日期 不是 DATETIME 格式 因为它们有时可以是任何其他字符串文字 我想根据给定的月份和年份查找任意日期的所有记录 尝试遵循但对我不起作用 gt Post find all con
  • JDBC 错误:在结果集开始之前[重复]

    这个问题在这里已经有答案了 我在 Java Eclipse 中收到错误消息 我在 MySql 中有一个数据库 它有列 String user name int id time int id desk int user password 我想
  • 如何删除 MySQL 数据库?

    你可能从我的上一个问题中注意到一个问题引发了更多的问题 在 MySQL 监视器中阅读 MySQL 手册 https stackoverflow com questions 1081399 我的数据库现在无法使用 部分原因是我想破坏东西并且无
  • 显示和随机化 php 数组

    我有一个显示结果的数组 如下所示 Array 0 gt 71 1 gt 56 2 gt 64 3 gt 82 4 gt 90 5 gt 80 6 gt 65 7 gt 62 8 gt 14 9 gt 3 我的代码是 while row my
  • Mysql带限制的删除语句

    我试图从表中删除行 但出现错误 DELETE FROM chat messages ORDER BY timestamp DESC LIMIT 20 50 我在 50 时收到此错误 您的 SQL 语法有错误 检查与您的 MySQL 服务器版
  • MySql 视图脚本中的注释

    可以这样做吗 我尝试过多个 gui mysql workbench navicat toad for mysql 但没有一个保存这样的注释 something important select something else importan
  • jdbc4.MySQLSyntaxErrorException:数据库中不存在表

    我正在使用 SpringBoot 开发一个网络应用程序 这是我的application properties文件来指定访问数据库的凭据 spring datasource driverClassName com mysql jdbc Dri
  • Django 将 JSON 数据传递给静态 getJSON/Javascript

    我正在尝试从 models py 中获取数据并将其序列化为views py 中的 JSON 对象 模型 py class Platform models Model platformtype models CharField max len
  • 使用用户定义函数 MySql 时出错

    您好 请帮我解决这个问题 提前致谢 我在数据库中定义了这些函数 CREATE FUNCTION levenshtein s1 VARCHAR 255 s2 VARCHAR 255 RETURNS INT DETERMINISTIC BEGI
  • rake db 问题:迁移 -

    我无法为 Ruby on Rails 设置 MySQL 数据库 设置数据库并确保 config database yml 文件匹配后 我遇到了以下错误消息 U Rails alpha gt rake db migrate trace in

随机推荐

  • 软件测试/测试开发丨利用ChatGPT自动生成测试用例思维导图

    点此获取更多相关资料 简介 思维导图是一种用图形方式表示思维和概念之间关系的工具 有些公司会使用思维导图编写测试用例 这样做的优点是 1 可视化和结构化 2 易于理解 提高效率 而 ChatGPT 是无法直接生成 xmind 格式的文件的
  • webdriver.Chrome()报错:selenium.common.exceptions.WebDriverException: Message: 'chromedriver' ...

    使用selenium模块的webdriver打开谷歌浏览器的时候报错 源代码如下 from selenium import webdriver browser webdriver Chrome print type browser brow
  • 12-8 副作用与纯函数

    1 副作用 函数副作用 指当调用函数时 除了返回函数值之外 还对主调用函数产生附加的影响 例如修改全局变量 函数外的变量 或修改参数 表达式副作用 在表达式求值过程中 需要获取变量的值 但并不改变这些变量的值 这样的表达式称为无副作用的表达
  • 关于Visual Studio内登录microsoft账号白屏问题的解决办法

    如果连接常规Wi Fi无效的话可尝试以下方法 1 断开Wi Fi连接 关闭程序 2 打开手机热点 使电脑连接上 3 再次打开程序进行输入账号与密码的操作 4 此时尝试登录可有效避免白屏卡顿现象 亲测有效 PS 本人WiFi为中国电信 手机卡
  • 怎样使用Cubase进行人声消除

    所谓分离伴奏 指的就是消除人声 通常在一首歌曲的音频文件中 混音师一般都会将人声放在声像位置的正中间再输出为一个立体声音频文件 一般情况下是这样 但不代表全是这样 因此 人声的波形在该立体声音频文件的左声道和右声道中应该是相同或相似的 所以
  • 不好意思, Maven 该换了!

    相信使用Java的同学都用过Maven 这是一个非常经典好用的项目构建工具 但是如果你经常使用Maven 可能会发现Maven有一些地方用的让人不太舒服 一来Maven的配置文件是XML格式的 假如你的项目依赖的包比较多 那么XML文件就会
  • SLAM代码(三维重建)

    版权声明 本文为博主原创文章 遵循 CC 4 0 BY SA 版权协议 转载请附上原文出处链接和本声明 本文链接 https blog csdn net wendox article details 52719252 三维重建的一般步骤 特
  • 爬虫隐藏自身的ip并伪装成浏览器

    爬虫隐藏自身的ip并伪装成浏览器 使用代理访问 就是说使用代理 代理 访问url之后 再将网页的内容在传给本机的 使用代理访问 import urllib request import random url http www whatism
  • 堆排序【C语言】

    堆排序 基本思想 利用堆 小顶堆 进行排序的过程 首先把待排序序列 R1 R2 Rn 转换成一个堆 这时 根结点具有最小值 输出根结点 可以将其与堆数组中的末尾元素交换 此时末尾元素就是最小值 然后将剩下的n 1个结点重新调整为一个堆 反复
  • vue-element el-table 使用sortablejs拖拽排序

    需求描述 vue element admin开发过程中需要对el table行进行排序 即每一行可以上下移动 然后将排序后的数据传给后台更新数据 该表格无分页 问题分析 方法一 可以采用在每条数据中加两个上下移动的按钮 每次移动一行 该方法
  • 自制脚本语言(12) 作用域与符号表

    摘要 介绍了自制语言的编译器对符号表的处理 YF语言中 符号表的基本结构是hash表 每个AST 附带了3个hash表 变量表 类型表 函数表 例如
  • python Django项目点击run或debug时出现Type ‘manage.py help <subcommand>‘ for help on a specific subcommand.

    报错 D python3 7 python exe E code dailyfresh test1 test2 manage py Type manage py help
  • python requests cookies怎么转为_如何将requests.RequestsCookieJar转换为字符串

    新答案 好吧 所以我还是不知道你到底想达到什么目的 如果您想从requests RequestCookieJar对象中提取原始url 这样您就可以检查是否与给定的子域匹配 这是 据我所知 不可能的 不过 你也可以做些类似的事情 usr bi
  • Linux-线程的同步与互斥

    线程的同步与互斥 进程 线程间的互斥相关背景概念 互斥量 互斥量接口 互斥量的初始化 互斥量的销毁 加锁和解锁 改善抢票系统 互斥量原理 可重入与线程安全 重入和线程安全的概念 常见线程不安全情况 常见线程安全的情况 常见不可重入情况 常见
  • 【软件工程】-可行性研究报告

    GB8567 88 可行性研究报告 1引言 1 1编写目的 为了提高机房收费管理的灵活性和效率 减轻机房工作人员的工作负担 节约时间 对机房收费业务做到快速准确管理的目的 从而降低人力 经济的更各方面的消耗 本次编写主要是为了分析廊坊师范学
  • 电机速度曲线规划1:梯形速度曲线设计与实现

    电机驱动是很常见的应用 在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能 这就涉及到电机速度曲线规划的问题 在这篇中我们就来简单讨论一下电机的梯形曲线规划的问题 1 基本原理 梯形速度曲线控制算法是工业控制领域应用最为广泛的加
  • 在vc下环境变量的设置

    Error spawning cl exe 编译出错 有人说是没有设置 include环境变量 下面介绍在vc下如何设置环境变量 1 Microsoft Visual Studio下面3个子文件夹 Common VC98 My Projec
  • 1.嵌入式控制器EC学习,编译环境搭建

    工欲善其事 必先利其器 在学习EC相关知识之前 首先需要完成EC代码编译环境的搭建 需要如下内容 Keil C51 用于EC中C代码的编译器环境 EC源代码 我们使用从网上可以下载到的 ITE V12 4 Update 版的代码为例进行学习
  • JavaBean,List,Map转成json格式

    普通JavaBean 以User为例 转成json格式 1 转成JSONArray类型 User user new User user setUsername cxl user setPassword 1234 JSONArray json
  • GORM 基础 -- Gen

    https gorm io gen github 1 GEN Guides GEN 友好和更安全的代码生成 1 1 概述 来自动态原始SQL的惯用和可重用API 100 类型安全的DAO API 不使用 interface Database