gen: GORM Gen 代码生成

[](#gormgen)GORM/GEN

GoVersion Release Go.Dev reference Go Report Card MIT license OpenIssue ClosedIssue TODOs

基于 GORM, 更安全更友好的ORM工具。

[](#overview)Overview

  • 自动生成CRUD和DIY方法
  • 自动根据表结构生成model
  • 完全兼容GORM
  • 更安全、更友好
  • 多种生成代码模式

[](#contents)Contents

[](#%E5%AE%89%E8%A3%85)安装

安装GEN前,需要安装好GO并配置你的工作环境。

1.安装完Go(version 1.14+)之后,通过下面的命令安装gen。

go get -u gorm.io/gen

2.导入到你的工程:

import "gorm.io/gen"

[](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)快速开始

注⚠️: 这里所有的教程都是在 WithContext 模式下写的. 如果你用的是WithoutContext 模式,则可以删除所有的 WithContext(ctx) ,这样代码看起来会更简洁.

# assume the following code in generate.go file
$ cat generate.go

package main
import "gorm.io/gen"
// generate code
func main() {
// specify the output directory (default: "./query")
// ### if you want to query without context constrain, set mode gen.WithoutContext ###
g := gen.NewGenerator(gen.Config{
OutPath: "../dal/query",
/* Mode: gen.WithoutContext|gen.WithDefaultQuery*/
//if you want the nullable field generation property to be pointer type, set FieldNullable true
/* FieldNullable: true,*/
//if you want to generate index tags from database, set FieldWithIndexTag true
/* FieldWithIndexTag: true,*/
//if you want to generate type tags from database, set FieldWithTypeTag true
/* FieldWithTypeTag: true,*/
//if you need unit tests for query code, set WithUnitTest true
/* WithUnitTest: true, */
})

// reuse the database connection in Project or create a connection here
// if you want to use GenerateModel/GenerateModelAs, UseDB is necessray or it will panic
// db, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(db)

// apply basic crud api on structs or table models which is specified by table name with function
// GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute.
g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))

// apply diy interfaces on structs or table models
g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))
// execute the action of code generation
g.Execute()
}

生成Model:

  • gen.WithoutContextWithContext 模式生成
  • gen.WithDefaultQuery 生成默认全局查询变量

[](#%E9%A1%B9%E7%9B%AE%E8%B7%AF%E5%BE%84)项目路径

最佳实践项目模板:

demo
├── cmd
│ └── generate
│ └── generate.go # execute it will generate codes
├── dal
│ ├── dal.go # create connections with database server here
│ ├── model
│ │ ├── method.go # DIY method interfaces
│ │ └── model.go # store struct which corresponding to the database table
│ └── query # generated code's directory
| ├── user.gen.go # generated code for user
│ └── gen.go # generated code
├── biz
│ └── query.go # call function in dal/gorm_generated.go and query databases
├── config
│ └── config.go # DSN for database server
├── generate.sh # a shell to execute cmd/generate
├── go.mod
├── go.sum
└── main.go

[](#api-%E7%A4%BA%E4%BE%8B)API 示例

[](#%E7%94%9F%E6%88%90)生成

[](#%E7%94%9F%E6%88%90model)生成Model

// generate a model struct map to table `people` in database
g.GenerateModel("people")
// generate a struct and specify struct's name
g.GenerateModelAs("people", "People")
// add option to ignore field
g.GenerateModel("people", gen.FieldIgnore("address"), gen.FieldType("id", "int64"))
// generate all tables, ex: g.ApplyBasic(g.GenerateAllTable()...)
g.GenerateAllTable()

字段生成 Options

FieldNew // create new field
FieldIgnore // ignore field
FieldIgnoreReg // ignore field (match with regexp)
FieldRename // rename field in struct
FieldType // specify field type
FieldTypeReg // specify field type (match with regexp)
FieldTag // specify gorm and json tag
FieldJSONTag // specify json tag
FieldGORMTag // specify gorm tag
FieldNewTag // append new tag
FieldNewTagWithNS // specify new tag with name strategy
FieldTrimPrefix // trim column prefix
FieldTrimSuffix // trim column suffix
FieldAddPrefix // add prefix to struct member's name
FieldAddSuffix // add suffix to struct member's name
FieldRelate // specify relationship with other tables
FieldRelateModel // specify relationship with exist models

[](#%E7%B1%BB%E5%9E%8B%E6%98%A0%E5%B0%84)类型映射

自定义数据库字段类型和go类型的映射关系.

dataMap := map[string]func(detailType string) (dataType string){
"int": func(detailType string) (dataType string) { return "int64" },
// bool mapping
"tinyint": func(detailType string) (dataType string) {
if strings.HasPrefix(detailType, "tinyint(1)") {
return "bool"
}
return "int8"
},
}
g.WithDataTypeMap(dataMap)

[](#%E5%AD%97%E6%AE%B5%E8%A1%A8%E8%BE%BE%E5%BC%8F)字段表达式

[](#%E5%88%9B%E5%BB%BA%E5%AD%97%E6%AE%B5)创建字段

实际上你需要手动创建字段,因为都会在生成代码自动创建。

Field Type

Detail Type

Create Function

Supported Query Method

generic

field

NewField

IsNull/IsNotNull/Count/Eq/Neq/Gt/Gte/Lt/Lte/Like

int

int/int8/.../int64

NewInt/NewInt8/.../NewInt64

Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/Mod/FloorDiv/RightShift/LeftShift/BitXor/BitAnd/BitOr/BitFlip

uint

uint/uint8/.../uint64

NewUint/NewUint8/.../NewUint64

same with int

float

float32/float64

NewFloat32/NewFloat64

Eq/Neq/Gt/Gte/Lt/Lte/In/NotIn/Between/NotBetween/Like/NotLike/Add/Sub/Mul/Div/FloorDiv

string

string/[]byte

NewString/NewBytes

Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In(val/NotIn(val/Like/NotLike/Regexp/NotRegxp/FindInSet/FindInSetWith

bool

bool

NewBool

Not/Is/And/Or/Xor/BitXor/BitAnd/BitOr

time

time.Time

NewTime

Eq/Neq/Gt/Gte/Lt/Lte/Between/NotBetween/In/NotIn/Add/Sub

创建字段示例:

import "gorm.io/gen/field"
// create a new generic field map to `generic_a`
a := field.NewField("table_name", "generic_a")
// create a field map to `id`
i := field.NewInt("user", "id")
// create a field map to `address`
s := field.NewString("user", "address")
// create a field map to `create_time`
t := field.NewTime("user", "create_time")

[](#crud-%E6%8E%A5%E5%8F%A3)CRUD 接口

生成基础model userDB.

// generated code
// generated code
// generated code
package query
import "gorm.io/gen"
// struct map to table `users`
type user struct {
gen.DO
ID field.Uint
Name field.String
Age field.Int
Address field.Field
Birthday field.Time
}
// struct collection
type DB struct {
db *gorm.DB
User *user
}

[](#%E5%88%9B%E5%BB%BA)创建

[](#%E5%88%9B%E5%BB%BA%E8%AE%B0%E5%BD%95)创建记录

// u refer to query.user
user := model.User{Name: "Modi", Age: 18, Birthday: time.Now()}
u := query.Use(db).User
err := u.WithContext(ctx).Create(&user) // pass pointer of data to Create
err // returns error

[](#%E9%80%89%E6%8B%A9%E5%AD%97%E6%AE%B5%E5%88%9B%E5%BB%BA)选择字段创建

自定义哪些字段需要插入。

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

自定义创建时需要忽略的字段。

u := query.Use(db).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)

[](#%E6%89%B9%E9%87%8F%E5%88%9B%E5%BB%BA)批量创建

Create 方法支持批量创建,参数只要是对应model的slice就可以. GORM会通过一条语句高效创建并返回所有的主键赋值给slice的Model.

var users = []model.User{{Name: "modi"}, {Name: "zhangqiang"}, {Name: "songyuan"}}
query.Use(db).User.WithContext(ctx).Create(&users)
for _, user := range users {
user.ID // 1,2,3
}

CreateInBatches可以指定批量创建的大小, e.g:

var users = []User{{Name: "modi_1"}, ...., {Name: "modi_10000"}}
// batch size 100
query.Use(db).User.WithContext(ctx).CreateInBatches(users, 100)

也可以通过全局配置方式,在初始化gorm时设置 CreateBatchSize in gorm.Config / gorm.Session

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)

[](#%E6%9F%A5%E8%AF%A2)查询

[](#%E5%8D%95%E4%B8%AA%E6%95%B0%E6%8D%AE%E6%9F%A5%E8%AF%A2)单个数据查询

自动生成 First, Take, Last 三个查询单条数据的方法。 执行的sql后面会自动添加 LIMIT 1 ,如果没有查到数据会返回错误: ErrRecordNotFound

u := query.Use(db).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;
// check error ErrRecordNotFound
errors.Is(err, gorm.ErrRecordNotFound)

[](#%E6%A0%B9%E6%8D%AE%E4%B8%BB%E9%94%AE%E6%9F%A5%E8%AF%A2%E6%95%B0%E6%8D%AE)根据主键查询数据

u := query.Use(db).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);

如果是string类型的主键,比如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";

[](#%E6%9F%A5%E8%AF%A2%E6%89%80%E6%9C%89%E6%95%B0%E6%8D%AE)查询所有数据

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

[](#%E6%9D%A1%E4%BB%B6)条件
[](#%E5%9F%BA%E7%A1%80%E6%9F%A5%E8%AF%A2)基础查询

u := query.Use(db).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

u := query.Use(db).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)Or

u := query.Use(db).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

组合where或or 构建复杂查询

p := query.Use(db).Pizza
pizzas, err := p.WithContext(ctx).Where(
p.WithContext(ctx).Where(p.Pizza.Eq("pepperoni")).
Where(p.Where(p.Size.Eq("small")).Or(p.Size.Eq("medium"))),
).Or(
p.WithContext(ctx).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")

[](#%E6%8C%87%E5%AE%9A%E5%AD%97%E6%AE%B5%E6%9F%A5%E8%AF%A2)指定字段查询

通过Select 可以选择你要查询的字段,否则就是查询所有字段。

u := query.Use(db).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;

[](#%E5%85%83%E7%BB%84%E6%9F%A5%E8%AF%A2)元组查询

例如多字段IN

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

[](#json-%E6%9F%A5%E8%AF%A2)JSON 查询

u := query.Use(db).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)Order

指定查询的排序方式

u := query.Use(db).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;

[](#limit--offset)Limit & Offset

分页查询,Limit限制最大条数,Offset指定数据的其实位置。

u := query.Use(db).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)Group By & Having

u := query.Use(db).User
type Result struct {
Date time.Time
Total int
}
var result Result
err := u.WithContext(ctx).Select(u.Name, u.Age.Sum().As("total")).Where(u.Name.Like("%modi%")).Group(u.Name).Scan(&result)
// 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(&result)
// 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.Use(db).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 []Result
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)Distinct

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

Distinct works with Pluck and Count too

[](#joins)Joins

联表查询,Join是指inner join,还有LeftJoinRightJoin

u := query.Use(db).User
e := query.Use(db).Email
c := query.Use(db).CreditCard
type Result struct {
Name string
Email string
}
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
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()

[](#%E5%AD%90%E6%9F%A5%E8%AF%A2)子查询

o := query.Use(db).Order
u := query.Use(db).User
orders, err := o.WithContext(ctx).Where(u.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.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%")

[](#from-%E5%AD%90%E6%9F%A5%E8%AF%A2)From 子查询

通过Table方法构建出的子查询,可以直接放到From语句中:

u := query.Use(db).User
p := query.Use(db).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

[](#%E6%9B%B4%E6%96%B0%E5%AD%90%E6%9F%A5%E8%AF%A2)更新子查询

通过子查询更新表字段

u := query.Use(db).User
c := query.Use(db).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)))

[](#%E5%A4%9A%E5%AD%97%E6%AE%B5%E6%9B%B4%E6%96%B0%E5%AD%90%E6%9F%A5%E8%AF%A2)多字段更新子查询

针对mysql提供同时更新多个字段的子查询:

u := query.Use(db).User
c := query.Use(db).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`

[](#%E4%BA%8B%E5%8A%A1)事务

多个操作需要在一个事务中完成的情况.

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

[](#%E5%B5%8C%E5%A5%97%E4%BA%8B%E5%8A%A1)嵌套事务

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(tx2 *query.Query) error {
tx2.User.WithContext(ctx).Create(&user3)
return nil
})
return nil
})
// Commit user1, user3

[](#%E6%89%8B%E5%8A%A8%E4%BA%8B%E5%8A%A1)手动事务

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

For example:

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

[](#%E4%BF%9D%E5%AD%98%E7%82%B9)保存点

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

[](#advanced-query)Advanced Query
[](#%E8%BF%AD%E4%BB%A3)迭代

GEN支持通过Row迭代取值

u := query.Use(db).User
do := u.WithContext(ctx)
rows, err := do.Where(u.Name.Eq("modi")).Rows()
defer rows.Close()
for rows.Next() {
var user User
// ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct
do.ScanRows(rows, &user)
// do something
}

[](#%E6%89%B9%E9%87%8F%E6%9F%A5%E8%AF%A2)批量查询

u := query.Use(db).User
// batch size 100
err := u.WithContext(ctx).Where(u.ID.Gt(9)).FindInBatches(&results, 100, func(tx gen.Dao, batch int) error {
for _, result := range results {
// batch processing found records
}

// build a new `u` to use it's api
// queryUsery := query.NewUser(tx.UnderlyingDB())
tx.Save(&results)
batch // Batch 1, 2, 3
// returns error will stop future batches
return nil
})

[](#pluck)Pluck

从数据库中查询单个列并扫描成一个切片或者基础类型

u := query.Use(db).User
var ages []int64
u.WithContext(ctx).Pluck(u.Age, &ages)
var names []string
u.WithContext(ctx).Pluck(u.Name, &names)
// Distinct Pluck
u.WithContext(ctx).Distinct().Pluck(u.Name, &names)
// SELECT DISTINCT `name` FROM `users`
// Requesting more than one column, use `Scan` or `Find` like this:
db.WithContext(ctx).Select(u.Name, u.Age).Scan(&users)
users, err := db.Select(u.Name, u.Age).Find()

[](#scopes)Scopes

可以声明一些常用的或者公用的条件方法,然后通过Scopes 查询

o := query.Use(db).Order
func AmountGreaterThan1000(tx gen.Dao) gen.Dao {
return tx.Where(o.Amount.Gt(1000))
}
func PaidWithCreditCard(tx gen.Dao) gen.Dao {
return tx.Where(o.PayModeSign.Eq("C"))
}
func PaidWithCod(tx gen.Dao) gen.Dao {
return tx.Where(o.PayModeSign.Eq("C"))
}
func OrderStatus(status []string) func (tx gen.Dao) gen.Dao {
return func (tx gen.Dao) gen.Dao {
return tx.Where(o.Status.In(status...))
}
}
orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find()
// Find all credit card orders and amount greater than 1000
orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, PaidWithCod).Find()
// Find all COD orders and amount greater than 1000
orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find()
// Find all paid, shipped orders that amount greater than 1000

[](#count)Count

u := query.Use(db).User
count, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Or(u.Name.Eq("zhangqiang")).Count()
// SELECT count(1) FROM users WHERE name = 'modi' OR name = 'zhangqiang'
count, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Count()
// SELECT count(1) FROM users WHERE name = 'modi'; (count)
// Count with Distinct
u.WithContext(ctx).Distinct(u.Name).Count()
// SELECT COUNT(DISTINCT(`name`)) FROM `users`

[](#firstorinit)FirstOrInit

获取匹配的第一条数据或用给定条件初始化一个实例

u := query.Use(db).User
// User not found, initialize it with give conditions
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrInit()
// user -> User{Name: "non_existing"}
// Found user with `name` = `modi`
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).FirstOrInit()
// user -> User{ID: 1, Name: "modi", Age: 17}

如果希望初始化的实例包含一些非查询条件的属性,则可以通过Attrs指定

u := query.Use(db).User
// User not found, initialize it with give conditions and Attrs
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).FirstOrInit()
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}
// User not found, initialize it with give conditions and Attrs
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).FirstOrInit()
// SELECT * FROM USERS WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// user -> User{Name: "non_existing", Age: 20}
// Found user with `name` = `modi`, attributes will be ignored
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Attrs(u.Age.Value(20)).FirstOrInit()
// SELECT * FROM USERS WHERE name = modi' ORDER BY id LIMIT 1;
// user -> User{ID: 1, Name: "modi", Age: 17}

Assign 则是无论有没有找到记录,都用指定的属性进行覆盖已有的属性

// User not found, initialize it with give conditions and Assign attributes
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Assign(u.Age.Value(20)).FirstOrInit()
// user -> User{Name: "non_existing", Age: 20}
// Found user with `name` = `modi`, update it with Assign attributes
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Assign(u.Age.Value(20)).FirstOrInit()
// SELECT * FROM USERS WHERE name = modi' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "modi", Age: 20}

[](#firstorcreate)FirstOrCreate

获取第一条匹配的记录或在给定条件下创建一条新记录

u := query.Use(db).User
// User not found, create a new record with give conditions
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrCreate()
// INSERT INTO "users" (name) VALUES ("non_existing");
// user -> User{ID: 112, Name: "non_existing"}
// Found user with `name` = `modi`
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).FirstOrCreate()
// user -> User{ID: 111, Name: "modi", "Age": 18}

如果希望创建的实例包含一些非查询条件的属性,则可以通过Attrs指定

u := query.Use(db).User
// User not found, create it with give conditions and Attrs
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Attrs(u.Age.Value(20)).FirstOrCreate()
// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}
// Found user with `name` = `modi`, attributes will be ignored
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Attrs(u.Age.Value(20)).FirstOrCreate()
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;
// user -> User{ID: 111, Name: "modi", Age: 18}

Assign 则是无论有没有找到记录,都用指定的属性进行覆盖并且入库

u := query.Use(db).User
// User not found, initialize it with give conditions and Assign attributes
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Assign(u.Age.Value(20)).FirstOrCreate()
// SELECT * FROM users WHERE name = 'non_existing' ORDER BY id LIMIT 1;
// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);
// user -> User{ID: 112, Name: "non_existing", Age: 20}
// Found user with `name` = `modi`, update it with Assign attributes
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).Assign(u.Age.Value(20)).FirstOrCreate(&user)
// SELECT * FROM users WHERE name = 'modi' ORDER BY id LIMIT 1;
// UPDATE users SET age=20 WHERE id = 111;
// user -> User{ID: 111, Name: "modi", Age: 20}

[](#%E5%85%B3%E8%81%94)关联

GEN将像GORM一样自动保存关联((BelongsTo/HasOne/HasMany/Many2Many) 。

[](#relation)Relation

There are 4 kind of relationship.

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
)

[](#%E5%85%B3%E8%81%94%E5%B7%B2%E7%BB%8F%E5%AD%98%E5%9C%A8%E7%9A%84model)关联已经存在的Model

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{
...
}

[](#%E5%92%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%E5%85%B3%E8%81%94)和数据库表关联

必须使用 gen.FieldRelate声明

card := g.GenerateModel("credit_cards")
customer := g.GenerateModel("customers", gen.FieldRelate(field.HasMany, "CreditCards", b,
&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"`
}

如果是已经存在的关联model, 则可以用gen.FieldRelateModel 声明.

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

[](#%E5%85%B3%E8%81%94%E9%85%8D%E7%BD%AE)关联配置

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
}

[](#%E6%93%8D%E4%BD%9C)操作
[](#%E8%B7%B3%E8%BF%87%E8%87%AA%E5%8A%A8%E5%88%9B%E5%BB%BA%E5%85%B3%E8%81%94)跳过自动创建关联

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

Method Field will join a serious field name with ''.", for example: u.BillingAddress.Field("Address1", "Street") equals to BillingAddress.Address1.Street

[](#%E6%9F%A5%E8%AF%A2%E5%85%B3%E8%81%94)查询关联

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

[](#%E6%B7%BB%E5%8A%A0%E5%85%B3%E8%81%94)添加关联

u := query.Use(db).User
u.Languages.Model(&user).Append(&languageZH, &languageEN)
u.Languages.Model(&user).Append(&Language{Name: "DE"})
u.CreditCards.Model(&user).Append(&CreditCard{Number: "411111111111"})

[](#%E6%9B%BF%E6%8D%A2%E5%85%B3%E8%81%94)替换关联

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

[](#%E5%88%A0%E9%99%A4%E5%85%B3%E8%81%94)删除关联

删除存在的关联,不会删除数据

u := query.Use(db).User
u.Languages.Model(&user).Delete(&languageZH, &languageEN)
u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)

[](#%E6%B8%85%E6%A5%9A%E5%85%B3%E8%81%94)清楚关联

清楚所有的关联,不会删除数据

u.Languages.Model(&user).Clear()

[](#%E7%BB%9F%E8%AE%A1%E5%85%B3%E8%81%94)统计关联

u.Languages.Model(&user).Count()

[](#%E5%88%A0%E9%99%A4%E6%8C%87%E5%AE%9A%E5%85%B3%E8%81%94)删除指定关联

删除制定条件数据并删除关联数据:

u := query.Use(db).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)

[](#%E9%A2%84%E5%8A%A0%E8%BD%BD)预加载
[](#preload)Preload

GEN 支持通过 Preload加载关联数据:

type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
q := query.Use(db)
u := q.User
o := q.Order
// Preload Orders when find users
users, err := u.WithContext(ctx).Preload(u.Orders).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4);
users, err := u.WithContext(ctx).Preload(u.Orders).Preload(u.Profile).Preload(u.Role).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to

[](#preload-all)Preload All

clause.Associations 通过Preload 预加载所有的关联数据:

type User struct {
gorm.Model
Name string
CompanyID uint
Company Company
Role Role
Orders []Order
}
users, err := u.WithContext(ctx).Preload(field.Associations).Find()

clause.Associations 不会加载嵌套关联, 潜逃关联家在可以用 Nested Preloading e.g:

users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find()

[](#%E6%A0%B9%E6%8D%AE%E6%9D%A1%E4%BB%B6%E9%A2%84%E5%8A%A0%E8%BD%BD)根据条件预加载

q := query.Use(db)
u := q.User
o := q.Order
// Preload Orders with conditions
users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) AND state NOT IN ('cancelled');
users, err := u.WithContext(ctx).Where(u.State.Eq("active")).Preload(u.Orders.On(o.State.NotIn("cancelled")).Find()
// SELECT * FROM users WHERE state = 'active';
// SELECT * FROM orders WHERE user_id IN (1,2) AND state NOT IN ('cancelled');
users, err := u.WithContext(ctx).Preload(u.Orders.Order(o.ID.Desc(), o.CreateTime).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) Order By id DESC, create_time;
users, err := u.WithContext(ctx).Preload(u.Orders.On(o.State.Eq("on")).Order(o.ID.Desc()).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) AND state = "on" Order By id DESC;
users, err := u.WithContext(ctx).Preload(u.Orders.Clauses(hints.UseIndex("idx_order_id"))).Find()
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2) USE INDEX (`idx_order_id`);

[](#%E6%BD%9C%E9%80%83%E9%A2%84%E5%8A%A0%E8%BD%BD)潜逃预加载

db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)
// Customize Preload conditions for `Orders`
// And GEN won't preload unmatched order's OrderItems then
db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users)

[](#%E6%9B%B4%E6%96%B0)更新

[](#%E6%9B%B4%E6%96%B0%E5%8D%95%E5%AD%97%E6%AE%B5)更新单字段

Update方法更新单个字段。需要注意的是必须指定更新条件否则会报错ErrMissingWhereClause:

u := query.Use(db).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;

[](#%E6%9B%B4%E6%96%B0%E5%A4%9A%E5%AD%97%E6%AE%B5)更新多字段

Updates 支持 structmap[string]interface{}类型,更新多个字段,但是会忽略其中的零值属性

u := query.Use(db).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;

NOTE When update with struct, GEN will only update non-zero fields, you might want to use map to update attributes or use Select to specify fields to update
[](#%E9%80%89%E6%8B%A9%E6%9B%B4%E6%96%B0%E7%9A%84%E5%AD%97%E6%AE%B5)选择更新的字段

通过 Select, Omit选择需要更新的字段或者需要忽略更新的字段

u := query.Use(db).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

[](#%E5%88%A0%E9%99%A4)删除

[](#%E5%88%A0%E9%99%A4%E8%AE%B0%E5%BD%95)删除记录

e := query.Use(db).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

[](#%E6%A0%B9%E6%8D%AE%E4%B8%BB%E9%94%AE%E5%88%A0%E9%99%A4)根据主键删除

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

[](#%E6%89%B9%E9%87%8F%E5%88%A0%E9%99%A4)批量删除

没有指定主键会删除松油匹配的数据

e := query.Use(db).Email
e.WithContext(ctx).Where(e.Name.Like("%modi%")).Delete()
// DELETE from emails where email LIKE "%modi%";

[](#%E8%BD%AF%E5%88%A0%E9%99%A4)软删除

如果你的model中有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;

If you don’t want to include gorm.Model, you can enable the soft delete feature like:

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

[](#%E6%9F%A5%E8%AF%A2%E5%8C%85%E5%90%AB%E8%BD%AF%E5%88%A0%E9%99%A4%E7%9A%84%E8%AE%B0%E5%BD%95)查询包含软删除的记录

可以通过 Unscoped实现

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

[](#%E6%B0%B8%E4%B9%85%E5%88%A0%E9%99%A4)永久删除

通过 Unscoped可以直接删除数据,而不是标记删除

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

[](#diy-%E6%96%B9%E6%B3%95)DIY 方法

[](#%E6%8E%A5%E5%8F%A3%E5%AE%9A%E4%B9%89)接口定义

自定义方法,需要通过接口定义。在方上通过注释的方式描述具体的查询逻辑,复杂的可以直接用sql(),简单的直接用where()。 如果想要写一些原始注释,可以先写注释然后换行,在写sql或者where

type Method interface {
// where("name=@name and age=@age")
SimpleFindByNameAndAge(name string, age int) (gen.T, error)
// FindUserToMap query by id and return id->instance
//
// sql(select * from users where id=@id)
FindUserToMap(id int) (gen.M, error)

// InsertValue insert into users (name,age) values (@name,@age)
InsertValue(age int, name string) error
}

返回值可以是 gen.T/gen.M/gen.RowsAffectedgen.T 表示单个model, []gen.T表示的是slice model,gen.M表示的是map[string]interface{},当然也可以是其他类型Gen不会转换。除了返回一个值外,还可以返回一个err。

[](#%E8%AF%AD%E6%B3%95)语法
[](#%E5%8D%A0%E4%BD%8D%E7%AC%A6)占位符
  • gen.T represents specified struct or table
  • gen.M represents map[string]interface
  • gen.RowsAffected represents SQL executed rowsAffected (type:int64)
  • @@table represents table's name (if method's parameter doesn't contains variable table, GEN will generate table from model struct)
  • @@<columnName> represents column's name or table's name
  • @<name> represents normal query variable
[](#%E6%A8%A1%E6%9D%BF)模板

逻辑操作必须包裹在{{}}中,如{{if}},结束语句必须是 {{end}}, 所有的语句都可以嵌套。

  • if/else if/else the condition accept a bool parameter or operation expression which conforms to Golang syntax.
  • where The where clause will be inserted only if the child elements return something. The key word and or or in front of clause will be removed. And and will be added automatically when there is no junction keyword between query condition clause.
  • Set The set clause will be inserted only if the child elements return something. The , in front of columns array will be removed.And , will be added automatically when there is no junction keyword between query coulmns.
  • ... Coming soon
[](#if-clause)If clause

{{if cond1}}
// do something here
{{else if cond2}}
// do something here
{{else}}
// do something here
{{end}}

Use case in raw SQL:

// select * from users where {{if name !=""}} name=@name{{end}}
methond(name string) (gen.T,error)

Use case in raw SQL template:

select * from @@table where
{{if age>60}}
status="older"
{{else if age>30}}
status="middle-ager"
{{else if age>18}}
status="younger"
{{else}}
{{if sex=="male"}}
status="boys"
{{else}}
status="girls"
{{end}}
{{end}}

[](#where-clause)Where clause

{{where}}
// do something here
{{end}}

Use case in raw SQL

// select * from {{where}}id=@id{{end}}
methond(id int) error

Use case in raw SQL template

select * from @@table
{{where}}
{{if cond}}id=@id {{end}}
{{if name != ""}}@@key=@value{{end}}
{{end}}

[](#set-clause)Set clause

{{set}}
// sepecify update expression here
{{end}}

Use case in raw SQL

// update users {{set}}name=@name{{end}}
methond() error

Use case in raw SQL template

update @@table
{{set}}
{{if name!=""}} name=@name {{end}}
{{if age>0}} age=@age {{end}}
{{end}}
where id=@id

[](#method-interface-example)Method interface example

type Method interface {
// Where("name=@name and age=@age")
SimpleFindByNameAndAge(name string, age int) (gen.T, error)

// select * from users where id=@id
FindUserToMap(id int) (gen.M, error)

// sql(insert into @@table (name,age) values (@name,@age) )
InsertValue(age int, name string) error

// select name from @@table where id=@id
FindNameByID(id int) string

// select * from @@table
// {{where}}
// id>0
// {{if cond}}id=@id {{end}}
// {{if key!="" && value != ""}} or @@key=@value{{end}}
// {{end}}
FindByIDOrCustom(cond bool, id int, key, value string) ([]gen.T, error)

// update @@table
// {{set}}
// update_time=now()
// {{if name != ""}}
// name=@name
// {{end}}
// {{end}}
// {{where}}
// id=@id
// {{end}}
UpdateName(name string, id int) (gen.RowsAffected,error)
}

[](#%E6%99%BA%E8%83%BD%E9%80%89%E6%8B%A9%E5%AD%97%E6%AE%B5)智能选择字段

GEN 查询的时候会自动选择你的model定义的字段

type User struct {
ID uint
Name string
Age int
Gender string
// hundreds of fields
}
type APIUser struct {
ID uint
Name string
}
type Method interface{
// select * from user
FindSome() ([]APIUser, error)
}
apiusers, err := u.WithContext(ctx).Limit(10).FindSome()
// SELECT `id`, `name` FROM `users` LIMIT 10

[](#%E9%AB%98%E7%BA%A7%E6%95%99%E7%A8%8B)高级教程

[](#hints)Hints

Hints可以用来优化查询计划,比如指定索引后者强制索引等。

import "gorm.io/hints"
u := query.Use(db).User
users, err := u.WithContext(ctx).Clauses(hints.New("MAX_EXECUTION_TIME(10000)")).Find()
// SELECT /+ MAX_EXECUTION_TIME(10000) */ FROM `users`

import "gorm.io/hints"
u := query.Use(db).User
users, err := u.WithContext(ctx).Clauses(hints.UseIndex("idx_user_name")).Find()
// SELECT * FROM `users` USE INDEX (`idx_user_name`)
users, err := u.WithContext(ctx).Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find()
// SELECT * FROM `users` FORCE INDEX FOR JOIN (`idx_user_name`,`idx_user_id`)"

[](#%E5%91%BD%E4%BB%A4%E5%B7%A5%E5%85%B7)命令工具

安装gen命令行工具:

go install gorm.io/gen/tools/gentool@latest

使用参数:

$ gentool -h
Usage of gentool:
-db string
input mysql or postgres or sqlite or sqlserver. consult[https://gorm.io/docs/connecting_to_the_database.html] (default "mysql")
-dsn string
consult[https://gorm.io/docs/connecting_to_the_database.html]
-fieldNullable
generate with pointer when field is nullable
-fieldWithIndexTag
generate field with gorm index tag
-fieldWithTypeTag
generate field with gorm column type tag
-modelPkgName string
generated model code's package name
-outFile string
query code file name, default: gen.go
-outPath string
specify a directory for output (default "./dao/query")
-tables string
enter the required data table or leave it blank
-withUnitTest
generate unit test for query code

示例:

gentool -dsn "user:pwd@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local" -tables "orders,doctor"

[](#maintainers)Maintainers

@riverchu @idersec @qqxhb @dino-ma

@jinzhu

[](#contributing)Contributing

You can help to deliver a better GORM/GEN

[](#license)License

Released under the MIT License


原网址: 访问
创建于: 2022-08-12 17:45:10
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论