GORM -- golang object relation model. Библиотека для работы с реляционными БД.
package main
import (
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("при подключении к БД")
}
// Автомиграция схемы
db.AutoMigrate(&Product{})
// Создание записи в таблице
db.Create(&Product{Code: "D42", Price: 100})
// Чтение записи из таблицы
var product Product
db.First(&product, 1) // Поиск продукта по первичному ключу
db.First(&product, "code = ?", "D42") // Поиск продукта по коду D42
}
// Определение gorm.Model
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
Список далеко не полный:
bool, int, uint, float, string, time, bytes
, which works for all databases, and can be used with other tags together, like not null, size, autoIncrement… specified database data type like varbinary(8) also supported, when using specified database data type, it needs to be a full database data type, for example: MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT
serializer:json/gob/unixtime
. . .
<-:create
create-only field,<-:update
update-only field,<-:false
no write permission,<-
create and update permission->:false
no read permission-
no read/write permission,-:migration
no migrate permission,-:all
no read/write/migrate permissioncomment
add comment for field when migrationuser := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // ОБЯЗАТЕЛЬНО передавать ссылку на Create
users := []*User{
User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(users) // Передача нескольких записей сразу
// Создать только с указанными полями
db.Select("Name", "Age", "CreatedAt").Create(&user)
// Инорировать при создании указанные поля
db.Omit("Name", "Age", "CreatedAt").Create(&user)
Более сложный пример при вставке с решением конфликтов (и др.) есть в документации.
// Получить первую запись
db.First(&user)
// Получить последнюю запись
db.Last(&user)
// Получить по индексу
db.First(&user, 10)
// Получить несколько по указанным индексам в список
var users []User
db.Find(&users, []int{1,2,3})
// Сделать выборку по условию
db.Where("name <> ?", "jinzhu").Find(&users)
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
db.Where("name LIKE ?", "%jin%").Find(&users)
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// С доаолнительным условием
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// По указанным полям
db.Select("name", "age").Find(&users)
// Отсортировать по полям
db.Order("age desc, name").Find(&users)
// Ограничить тремя записями
db.Limit(3).Find(&users)
// Ограничить 10 записей со смещением 5
db.Limit(10).Offset(5).Find(&users)
Также можно объединять данные из разных таблиц
type result struct {
Name string
Email string
}
// Из таблицы users выбрать имя, из таблицы emails выбрать email, где user.id совпал
db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
// Решение объединения данных с предзагрузкой данных
db.Joins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
// Внутреннее объединение
db.InnerJoins("Company").Find(&users)
// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name`
Есть гораздо более сложные примеры, можно найти в документации
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
var user User
// ScanRows метод в `gorm.DB`, может быть использован для сканирования записей
db.ScanRows(rows, &user)
// Что-то делаем
}
// работа почками записей по 100 штук
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// пакетная обработка найденных записей
}
tx.Save(&results)
// tx.RowsAffected // число записей в этой пачке
// batch // Пачка 1, 2, 3
// returns error will stop future batches
return nil
})
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.Role == "" {
u.Role = "user"
}
return
}
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// Принудительное обновление поля
db.Save(&User{Name: "jinzhu", Age: 100})
// Обновление по условию
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
// Обновление нескольких полей
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// Обновление только указанных полей (в SELECT)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// Обновление с подзапросом (обновить имя копании у пользователя)
db.Table("users as u").Where("name = ?", user.Name).Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
Перехватчик перед обновлением:
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}
// Простое удаление. где ID=`10` (уже в структуре)
db.Delete(&email)
// Удаление с условием
db.Where("name = ?", user.Name).Delete(&email)
// Удаление с явным указанием ID
db.Delete(&User{}, 10)
// Удаление пачкой
var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&user
// Удаление пачкой
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// Физическое удаление
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10
Перехватчик перед удалением:
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to delete")
}
return
}
db.Table("deleted_users").Count(&count)
// SELECT count(1) FROM deleted_users;
type Result struct {
ID int
Name string
Age int
}
// Выборка по условию
var result Result
db.Raw("SELECT id, name, age FROM users WHERE id = ?", 3).Scan(&result)
// Выполнить что-то на севере по условию
db.Exec("UPDATE orders SET shipped_at = ? WHERE id IN ?", time.Now(), []int64{1, 2, 3})
// Посмотреть какой запрос сгенерирован
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
// SELECT * FROM "users" WHERE id = 100 AND "users"."deleted_at" IS NULL ORDER BY age desc LIMIT 10
type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
// Прямая предзагрузка
db.Preload("Orders").Find(&users)
// Предзагрузка с помощью левого соединения
db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2,3,4,5})
// Предзагрузка с условиями
db.Where("state = ?", "active").Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
db.WithContext(ctx).Find(&users)
У автоиграции масса методов, все можно найти в документации.
Ниже только часть из них:
// Создать таблицу, если не существует (самое простое)
db.AutoMigrate(&User{})
// Автомиграция по нескольким таблицам сразу (самое простое)
db.AutoMigrate(&User{}, &Product{}, &Order{})
// Создать таблицу для структуры `User`
db.Migrator().CreateTable(&User{})
// Переименовать таблицу
db.Migrator().RenameTable(&User{}, &UserInfo{})
// Добавить колонку
db.Migrator().AddColumn(&User{}, "Name")
// Удалить колонку
db.Migrator().DropColumn(&User{}, "Name")
// Запрос на создание отображения
query := db.Model(&User{}).Where("age > ?", 20)
db.Migrator().CreateView("users_pets", gorm.ViewOption{Query: query})
db.Migrator().DropView("users_pets")
type User struct {
gorm.Model
Name string
}
// SetName -- устанавливает имя пользователя
func (sf *User)SetName(name string){
sf.Name = name
db.Model(sf).Update("name", sf.Name)
}