GORM/GEN
На основе GORM, более безопасный и удобный инструмент ORM.
Для установки GEN необходимо сначала установить GO и настроить рабочую среду.
go get -u gorm.io/gen
import "gorm.io/gen"
Внимание: все учебные пособия здесь написаны в режиме WithContext
. Если вы используете режим WithoutContext
, то можете удалить все WithContext(ctx)
, чтобы код выглядел более лаконично.
# предположим, что следующий код находится в файле generate.go
$ cat generate.go
package main
import "gorm.io/gen"
// генерируем код
func main() {
// указываем выходной каталог (по умолчанию: "./query")
/* если вы хотите выполнять запросы без ограничений контекста, установите режим gen.WithoutContext */
g := gen.NewGenerator(gen.Config{
OutPath: "../dal/query",
/* Mode: gen.WithoutContext|gen.WithDefaultQuery*/
// если вы хотите, чтобы свойство генерации поля с нулевым значением было типом указателя, установите FieldNullable true
/* FieldNullable: true,*/
// если вы хотите генерировать теги индекса из базы данных, установите FieldWithIndexTag true
/* FieldWithIndexTag: true,*/
// если вы хотите генерировать теги типа из базы данных, установите FieldWithTypeTag true
/* FieldWithTypeTag: true,*/
// если вам нужны модульные тесты для кода запроса, установите WithUnitTest true
/* WithUnitTest: true, */
})
// повторно используйте соединение с базой данных в проекте или создайте соединение здесь
// если вы хотите использовать GenerateModel/GenerateModelAs, UseDB является обязательным, иначе произойдет паника
// db, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
g.UseDB(db)
// применяем базовые API CRUD к структурам или табличным моделям, которые указаны по имени таблицы с помощью функции
// GenerateModel/GenerateModelAs. Генератор сгенерирует код моделей таблиц при вызове Excute.
g.ApplyBasic(model.User{}, g.GenerateModel("company"), g.GenerateModelAs("people", "Person", gen.FieldIgnore("address")))
// применяем DIY интерфейсы к структурам или табличным моделям
g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company"))
// выполняем действие генерации кода
g.Execute()
}
Генерация модели:
gen.WithoutContext
— режим без WithContext
генерируетgen.WithDefaultQuery
— генерирует глобальную переменную запроса по умолчаниюРекомендуемая структура проекта:
demo
├── cmd
│ └── generate
│ └── generate.go # выполнить его, чтобы сгенерировать коды
├── dal
│ ├── dal.go # создать здесь соединения с сервером базы данных
│ ├── model
│ │ ├── method.go # DIY интерфейсы методов
│ │ └── model.go # сохранить структуру, соответствующую таблице базы данных
│ └── query # каталог сгенерированных кодов
| ├── user.gen.go # сгенерированный код для пользователя
│ └── gen.go # сгенерированный код
├── biz
│ └── query.go # вызвать функцию в dal/gorm_generated.go и запросить базы данных
├── config
│ └── config.go # DSN для сервера базы данных
├── generate.sh # оболочка для выполнения cmd/generate
├── go.mod
├── go.sum
└── main.go
// сгенерировать модель структуры, сопоставленную с таблицей `people` в базе данных
g.GenerateModel("people")
// сгенерировать структуру и указать имя структуры
g.GenerateModelAs("people", "People")
// добавить опцию игнорирования поля
g.GenerateModel("people", gen.FieldIgnore("адрес"), gen.FieldType("id", "int64"))
// сгенерировать все таблицы, например: g.ApplyBasic(g.GenerateAllTable()...)
g.GenerateAllTable()
Опции генерации полей:
Сопоставление пользовательских типов полей в базе данных и типов 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)
Выражение поля
Фактически вам нужно вручную создавать поля, потому что все они будут автоматически созданы при генерации кода.
Тип поля | Детальный тип | Функция создания | Поддерживаемый метод запроса |
---|---|---|---|
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 | То же, что и 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"
// создать новую общую карту полей для `generic_a`
a := field.NewField("table_name", "generic_a")
// создать карту поля для `id`
i := field.NewInt("user", "id")
// создать карту поля для `address`
s := field.NewString("user", "address")
// создать карту поля для `create_time`
t := field.NewTime("user", "create_time")
Интерфейс CRUD
Генерация базовой модели user
и DB
.
// сгенерированный код
// сгенерированный код
// сгенерированный код
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
}
Создание
// 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) // передать указатель данных в Create
err // возвращает ошибку
Пользовательские поля, которые необходимо вставить.
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)
Метод Create
поддерживает пакетное создание, и параметр должен быть только срезом соответствующей модели. GORM будет эффективно создавать и возвращать все первичные ключи, присвоенные значениям модели в срезе.
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
можно указать размер пакета, например:
var users = []User{{Name: "modi_1"}, ...., {Name: "modi_10000"}}
// batch size 100
query.Use(db).User.WithContext(ctx).CreateInBatches(users, 100)
Также можно использовать глобальную конфигурацию, чтобы установить CreateBatchSize
в gorm.Config
/ gorm.Session
при инициализации gorm.
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
CreateBatchSize: 1000,
})
// ИЛИ
db = db.Session(&gorm.Session{CreateBatchSize: 1000})
u := query.NewUser(db)
var users = []User{{Name:
``` **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**
Указывает способ сортировки запроса.
```go
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 указывает на смещение в наборе результатов.
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
Группирует результаты по указанному полю и фильтрует группы с помощью условия 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
Возвращает только уникальные значения указанного поля.
u := query.Use(db).User
users, err := u.WithContext(ctx).Distinct(u.Name, u.Age).Order(u.Name, u.Age.Desc()).Find()
Distinct работает с Pluck и Count.
Joins
Объединение таблиц. Join выполняет внутреннее соединение, также есть LeftJoin и RightJoin.
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()
Подзапросы
Позволяют использовать результаты одного запроса в другом.
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 подзапросы
Подзапросы, созданные с помощью метода 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
**Обновление подзапроса**
Пример обновления поля таблицы с помощью подзапроса:
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)))
**Многопольное обновление подзапроса**
Пример многопольного обновления с использованием 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
**Транзакции**
Примеры использования транзакций в запросах:
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 })
**Вложенные транзакции**
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
**Ручные транзакции**
Пример ручной транзакции:
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()
}
**Сохранение точки**
`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
**Расширенный запрос**
**Итерация**
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
}
**Пакетный запрос**
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
Извлекает отдельный столбец из базы данных и сканирует его в срез или базовый тип.
```go
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`
// Запрос более одного столбца, используйте Scan или Find следующим образом:
db.WithContext(ctx).Select(u.Name, u.Age).Scan(&users)
users, err := db.Select(u.Name, u.Age).Find()
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() // Найти все заказы по кредитной карте и сумму больше 1000
orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, PaidWithCod).Find() // Найти все заказы COD и сумму больше 1000
orders, err := o.WithContext(ctx).Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find() // Найти все оплаченные и отправленные заказы, сумма которых больше 1000
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
Получает соответствующую первую запись или инициализирует экземпляр с заданными условиями.
u := query.Use(db).User
// Пользователь не найден, инициализируем его с заданными условиями
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrInit() // user -> User{Name: "non_existing"}
// Находим пользователя с именем 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
// Пользователь не найден, инициализировать его с заданными условиями и 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}
// Пользователь не найден, инициализируйте его с заданными условиями и атрибутами 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{Имя: «non_existing», возраст: 20}
// Найден пользователь с именем modi, атрибуты будут проигнорированы
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{ID: 1, Имя: «modi», возраст: 17}
Assign — это атрибут, который используется для замены существующих атрибутов независимо от того, найдена ли запись.
// Пользователь не найден, инициализировать его с заданными условиями и Assign атрибутами
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).Assign(u.Age.Value(20)).FirstOrInit() // пользователь -> User{Имя: «non_existing», возраст: 20}
// Нашли пользователя с именем modi, обновляем его с помощью Assign атрибутов
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{ID: 111, имя: «modi», возраст: 20}
FirstOrCreate
Находит соответствующую первую запись или создаёт новую запись с заданными условиями.
u := query.Use(db).User
// Пользователь не найден, создаём новую запись с заданными условиями
user, err := u.WithContext(ctx).Where(u.Name.Eq("non_existing")).FirstOrCreate() // INSERT INTO "users" (name) VALUES ("non_existing");
// пользователь -> **Текст запроса написан на языке Go.**
Перевод текста:
Пользователь{ID: 112, Name: "non_existing"}
// Найден пользователь с `name` = `modi`
user, err := u.WithContext(ctx).Where(u.Name.Eq("modi")).FirstOrCreate()
// user -> User{ID: 111, Name: "modi", "Age": 18}
Если вы хотите создать экземпляр, который содержит некоторые атрибуты, не являющиеся условиями запроса, вы можете указать их с помощью Attrs.
```go
u := query.Use(db).User
// Пользователь не найден, создаём его с заданными условиями и атрибутами
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}
// Найден пользователь с `name` = `modi`, атрибуты будут проигнорированы
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, 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}
// Найден пользователь с `name` = `modi`, обновляем его с помощью Assign атрибутов
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}
GEN автоматически сохраняет связи (BelongsTo/HasOne/HasMany/Many2Many), как GORM.
Существует 4 вида отношений.
const (
HasOne RelationshipType = RelationshipType(schema.HasOne) // HasOneRel имеет одно отношение
HasMany RelationshipType = RelationshipType(schema.HasMany) // HasManyRel имеет много отношений
BelongsTo RelationshipType = RelationshipType(schema.BelongsTo) // BelongsToRel относится к отношениям
Many2Many RelationshipType = RelationshipType(schema.Many2Many) // Many2ManyRel — отношения «многие ко многим»
)
package model
// существующая модель
type Customer struct {
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:CustomerRefer"`
}
type CreditCard struct {
gorm.Model
Number string
CustomerRefer uint
}
GEN проверяет и преобразует эти связанные отношения:
// указываем модель
g.ApplyBasic(model.Customer{}, model.CreditCard{})
// ассоциации будут обнаружены и преобразованы в код
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", 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;text:deleted_at" json:"deleted_at"`
CustomerRefer int64
*Примечание: в тексте запроса присутствуют фрагменты кода, которые не удалось перевести. Это связано с тем, что они содержат синтаксические конструкции, специфичные для языка программирования Go, а также имена переменных и функций, которые невозможно однозначно интерпретировать без контекста.* ```
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)
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
}
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()
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"})
u.Languages.Model(&user).Replace(&languageZH, &languageEN)
Удаление существующих связанных данных, не удаляет данные.
u := query.Use(db).User
u.Languages.Model(&user).Delete(&languageZH, &languageEN)
u.Languages.Model(&user).Delete([]*Language{&languageZH, &languageEN}...)
Очистка всех связанных данных, не удаляет данные.
u.Languages.Model(&user).Clear()
u.Languages.Model(&user).Count()
Удаление определённых данных и связанных данных:
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)
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
clause.Associations
через Preload
предварительно загружает все связанные данные:
type User struct {
gorm.Model
Name string
CompanyID uint
Company Company
Role
``` **Роль**
Orders []Order
}
users, err := u.WithContext(ctx).Preload(field.Associations).Find()
clause.Associations
не будет загружать вложенные ассоциации, скрытые ассоциации можно использовать с помощью Nested Preloading, например:
users, err := u.WithContext(ctx).Preload(u.Orders.OrderItems.Product).Find()
Предварительная загрузка согласно условиям
q := query.Use(db)
u := q.User
o := q.Order
// Предварительная загрузка Orders с условиями
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`);
Скрытая предварительная загрузка
db.Preload(u.Orders.OrderItems.Product).Preload(u.CreditCard).Find(&users)
// Настройка условий предварительной загрузки для `Orders`
// И GEN не будет предварительно загружать несопоставленные элементы OrderItems тогда
db.Preload(u.Orders.On(o.State.Eq("paid"))).Preload(u.Orders.OrderItems).Find(&users)
Метод Update обновляет одно поле. Необходимо указать условия обновления, иначе будет выдана ошибка ErrMissingWhereClause:
u := query.Use(db).User
// Update с условиями
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 с условиями
u.WithContext(ctx).Where(u.Activate.Is(true)).Update(u.Age, u.Age.Add(1))
// или
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;
Updates поддерживает struct и map[string]interface{}, обновляя несколько полей, но игнорируя нулевые значения свойств:
u := query.Use(db).User
// Обновление атрибутов с помощью `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;
// Обновление атрибутов с помощью `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;
// Обновление с выражением
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 для указания полей для обновления.
С помощью Select, Omit можно выбрать поля, которые необходимо обновить, или поля, которые нужно игнорировать при обновлении:
u := query.Use(db).User
// Select с Map
// Идентификатор пользователя равен `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 **Текст запроса:**
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}}
Перевод текста запроса на русский язык:
Выберите * из @@table, где:
{{если возраст > 60}} статус = «старше» {{иначе если возраст > 30}} статус = «среднего возраста» {{иначе если возраст > 18}} статус = «моложе» {{иначе}} {{если пол == «мужской»}} статус = «мальчики» {{иначе}} статус = «девочки» {{конец}} {{конец}} @qqxhb @dino-ma
Вы можете помочь улучшить GORM/GEN.
Распространяется по лицензии MIT.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )