Skip to content

Gorm Veritabanı ORM Kütüphanesi

Resmi Dokümantasyon: GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Depo: go-gorm/gorm: The fantastic ORM library for Golang, aims to be developer friendly (github.com)

Go topluluğunda veritabanı etkileşimi için iki kamp vardır. Bir kamp sqlx gibi daha basit kütüphaneleri tercih eder. Bunlar güçlü değildir ancak SQL üzerinde tam kontrole sahip olmanızı sağlar ve performansı sonuna kadar optimize etmenize izin verir. Diğer kamp geliştirme verimliliği için ORM'i tercih eder. Bu geliştirme sırasında birçok gereksiz sorundan kurtarabilir. ORM söz konusu olduğunda Go dil topluluğunda gorm kesinlikle kaçınılmazdır. Çok köklü bir ORM'dir. xorm ent gibi nispeten daha genç olanlara benzer. Bu makale gorm hakkındadır. Bu makale yalnızca temel tanıtım içeriğini açıklar. Başlangıç noktası olarak. Daha fazla ayrıntıyı öğrenmek için resmi dokümantasyonu okuyabilirsiniz. Çincede dokümantasyonu oldukça eksiksizdir ve yazar da gorm dokümantasyonunun çevirmenlerinden biridir.

Özellikler

  • Tam özellikli ORM
  • İlişkiler (Has One, Has Many, Belongs To, Many To Many, Polymorphism, Single-table inheritance)
  • Hooks (Before/After Create/Save/Update/Delete/Find)
  • Eager Loading ile Preloading (Preload, Joins)
  • İşlemler, İç İçe İşlemler, Save Points, Kaydedilen Noktalara Rollback
  • Context, Prepared Statement Modu, DryRun Modu
  • Toplu Ekleme, FindInBatches, Map ile Find/Create, SQL İfadeleri ile CRUD, Context Valuer
  • SQL Builder, Upsert, Lock, Optimizer/Index/Comment Hint, Named Parameters, SubQuery
  • Composite Primary Key, Index, Constraint
  • Auto Migration
  • Özel Logger
  • Esnek Genişletilebilir Plugin API: Database Resolver (Çoklu Veritabanları, Read/Write Splitting), Prometheus...
  • Her özellik titizlikle test edilmiştir
  • Geliştirici Dostu

Gorm'un bazı eksiklikleri vardır. Örneğin neredeyse tüm metod parametreleri boş interface türlerindendir. Dokümantasyona bakmadan ne geçeceğinizi muhtemelen bilemezsiniz. Bazen struct bazen string bazen map bazen slice geçebilirsiniz. Semantik biraz belirsizdir ve birçok durumda hala SQL'i elle yazmanız gerekir.

Alternatif olarak deneyebileceğiniz iki ORM vardır. İlki kısa bir süre önce açık kaynak yapılan aorm'dur. Artık elle tablo alan adları yazmanızı gerektirmez. Çoğunlukla zincir işlemlerini kullanır ve yansıma tabanlıdır. Yıldız sayısı büyük olmadığından bekleyip görebilirsiniz. İkincisi facebook tarafından açık kaynak yapılan ent'tir. Ayrıca zincir işlemlerini destekler ve çoğu durumda SQL'i elle yazmanız gerekmez. Tasarım felsefesi graflara dayanır (veri yapılarından) ve implementasyonu yansıma yerine kod üretimine dayanır (ki buna daha çok katılıyorum). Ancak dokümantasyon tamamen İngilizcedir ve belirli bir öğrenme eşiği vardır.

Kurulum

gorm kütüphanesini kurun:

sh
$ go get -u gorm.io/gorm

Bağlantı

Gorm şu anda aşağıdaki veritabanlarını destekler:

  • MySQL: "gorm.io/driver/mysql"
  • PostgreSQL: "gorm.io/driver/postgres"
  • SQLite: "gorm.io/driver/sqlite"
  • SQL Server: "gorm.io/driver/sqlserver"
  • TIDB: "gorm.io/driver/mysql", TIDB mysql protokolü ile uyumludur
  • ClickHouse: "gorm.io/driver/clickhouse"

Ayrıca oracle sürücüsü CengSin/oracle gibi üçüncü taraf geliştiriciler tarafından sağlanan başka veritabanı sürücüleri de vardır. Bu makale MySQL'i gösterim için kullanacaktır. Hangi veritabanını kullanırsanız kullanın ilgili sürücüyü kurmanız gerekir. Burada MySQL'in gorm sürücüsünü kuruyoruz:

sh
$ go get -u gorm.io/driver/mysql

Ardından dsn (data source name) kullanarak veritabanına bağlanın. Sürücü kütüphanesi dsn'yi otomatik olarak ilgili yapılandırmaya ayrıştırır:

go
package main

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "log/slog"
)

func main() {
  dsn := "root:123456@tcp(192.168.48.138:3306)/hello?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

Veya manuel olarak yapılandırma geçin:

go
package main

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "log/slog"
)

func main() {
  db, err := gorm.Open(mysql.New(mysql.Config{}))
  if err != nil {
    slog.Error("db connect error", err)
  }
  slog.Info("db connect success")
}

Her iki yöntem de eşdeğerdir. Kullanım tercihine bağlıdır.

Bağlantı Yapılandırması

gorm.Config yapılandırma struct'ını geçirerek gorm'un bazı davranışlarını kontrol edebiliriz:

go
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

Aşağıda bazı basit açıklamalar bulunmaktadır. İhtiyacınıza göre yapılandırabilirsiniz:

go
type Config struct {
  // Varsayılan işlemi atla. gorm veri tutarlılığını korumak için tek create ve update için işlem başlatacak
  SkipDefaultTransaction bool
  // Özel adlandırma stratejisi
  NamingStrategy schema.Namer
  // Tam ilişkileri kaydet
  FullSaveAssociations bool
  // Özel logger
  Logger logger.Interface
  // Özel nowfunc. CreatedAt ve UpdatedAt alanlarını enjekte etmek için kullanılır
  NowFunc func() time.Time
  // Yalnızca SQL oluştur. Yürütme
  DryRun bool
  // Hazırlanmış ifadeler kullan
  PrepareStmt bool
  // Bağlantı kurduktan sonra veritabanını ping et
  DisableAutomaticPing bool
  // Veritabanı taşırken foreign key'leri yoksay
  DisableForeignKeyConstraintWhenMigrating bool
  // Veritabanı taşırken ilişki referanslarını yoksay
  IgnoreRelationshipsWhenMigrating bool
  // İç içe işlemleri devre dışı bırak
  DisableNestedTransaction bool
  // Global update'ye izin ver. where clause olmadan update
  AllowGlobalUpdate bool
  // Tablonun tüm alanlarını sorgula
  QueryFields bool
  // Toplu oluşturma boyutu
  CreateBatchSize int
  // Hata çevirisini etkinleştir
  TranslateError bool

  // ClauseBuilders clause builder
  ClauseBuilders map[string]clause.ClauseBuilder
  // ConnPool db conn pool
  ConnPool ConnPool
  // Dialector veritabanı dialector
  Dialector
  // Plugins kayıtlı plugin'ler
  Plugins map[string]Plugin

  callbacks  *callbacks
  cacheStore *sync.Map
}

Model

gorm'da bir model bir veritabanı tablosuna karşılık gelir. Genellikle bir struct ile temsil edilir. Aşağıdaki struct gibi:

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Mom     string
  Dad     string
}

Struct'ın iç alanları temel veri türlerinden ve sql.Scanner ve sql.Valuer arayüzlerini uygulayan türlerden oluşabilir. Varsayılan olarak Person struct'ı tarafından eşlenen tablo adı persons'tır. Bu snake_case çoğul stilindedir. Alt çizgilerle ayrılır. Sütun adları da snake_case stilindedir. Örneğin Id sütun adı id'ye karşılık gelir. Gorm bunu yapılandırmak için bazı yollar da sağlar.

Sütun Adlarını Belirtme

Struct etiketleri aracılığıyla struct alanları için sütun adlarını belirtebiliriz. Bu şekilde varlık eşleme sırasında gorm belirtilen sütun adlarını kullanacaktır:

go
type Person struct {
  Id      uint   `gorm:"column:ID;"`
  Name    string `gorm:"column:Name;"`
  Address string
  Mom     string
  Dad     string
}

Tablo Adlarını Belirtme

Table arayüzünü uygulayarak tablo adını belirtebilirsiniz. Yalnızca bir metodu vardır ve tablo adını döndürür:

go
type Tabler interface {
  TableName() string
}

Uygulanan metodda person string'ini döndürür. Veritabanı taşıma sırasında gorm person adında bir tablo oluşturacaktır:

go
type Person struct {
  Id      uint   `gorm:"column:ID;"`
  Name    string `gorm:"column:Name;"`
  Address string
  Mom     string
  Dad     string
}

func (p Person) TableName() string {
  return "person"
}

Adlandırma stratejileri için bağlantı oluştururken kendi strateji implementasyonunuzu geçirebilir ve özel efektler elde edebilirsiniz.

Zaman İzleme

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

func (p Person) TableName() string {
  return "person"
}

CreatedAt veya UpdatedAt alanlarını içerdiğinde kayıt oluşturma veya güncelleme sırasında sıfır değerleri varsa gorm otomatik olarak zamanı ayarlamak için time.Now() kullanacaktır:

go
db.Create(&Person{
    Name:    "jack",
    Address: "usa",
    Mom:     "lili",
    Dad:     "tom",
  })

// INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom','2023-10-25 14:43:57.16','2023-10-25 14:43:57.16')

Gorm ayrıca zaman damgası izlemeyi de destekler:

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  // nanosaniye
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milisaniye
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Sonra Create yürütme sırasında aşağıdaki SQL'e eşdeğerdir:

sql
INSERT INTO `person` (`name`,`address`,`mom`,`dad`,`created_at`,`updated_at`) VALUES ('jack','usa','lili','tom',1698216540519000000,1698216540)

Gerçek durumlarda zaman izlemeye ihtiyacınız varsa arka uçta zaman damgalarını saklamanızı öneririm. Bu çapraz zaman dilimi senaryolarında işlemeyi daha basit hale getirir.

Model

Gorm bir önayarlı Model struct'ı sağlar. Bir ID birincil anahtarı iki zaman izleme alanını ve bir soft delete kayıt alanını içerir:

go
type Model struct {
    ID        uint `gorm:"primarykey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt DeletedAt `gorm:"index"`
}

Kullanmak için varlık modelinize embed edin:

go
type Order struct {
  gorm.Model
  Name string
}

Bu şekilde gorm.Model'in tüm özelliklerine otomatik olarak sahip olacaktır.

Birincil Anahtar

Varsayılan olarak Id adlı alan birincil anahtardır. Birincil anahtar alanını belirtmek için struct etiketlerini kullanabilirsiniz:

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

Birden fazla alan bileşik birincil anahtar oluşturur:

go
type Person struct {
  Id      uint `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string
  Mom     string
  Dad     string

  CreatedAt sql.NullTime
  UpdatedAt sql.NullTime
}

İndeks

index struct etiketi aracılığıyla sütun indekslerini belirtebilirsiniz:

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"primaryKey;"`
  Address string `gorm:"index:idx_addr,unique,sort:desc;"`
  Mom     string
  Dad     string

  // nanosaniye
  CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
  // milisaniye
  UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Yukarıdaki struct'ta Address alanı için benzersiz bir indeks oluşturulur. Aynı indeks adını kullanan iki alan bileşik bir indeks oluşturur:

go
type Person struct {
    Id      uint   `gorm:"primaryKey;"`
    Name    string `gorm:"primaryKey;"`
    Address string `gorm:"index:idx_addr,unique;"`
    School  string `gorm:"index:idx_addr,unique;"`
    Mom     string
    Dad     string

    // nanosaniye
    CreatedAt uint64 `gorm:"autoCreateTime:nano;"`
    // milisaniye
    UpdatedAt uint64 `gorm:"autoUpdateTime;milli;"`
}

Foreign Key

Bir struct'ta foreign key ilişkileri struct'ları embed ederek tanımlanır. Örneğin:

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string

  MomId uint
  Mom   Mom `gorm:"foreignKey:MomId;"`

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;"`
}

type Mom struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

type Dad struct {
  Id   uint
  Name string

  Persons []Person `gorm:"foreignKey:DadId;"`
}

Örnekte Person struct'ı sırasıyla Dad ve Mom struct'larının birincil anahtarlarına referans veren iki foreign key'e sahiptir. Varsayılan olarak birincil anahtara referans verir. Person'ın Dad ve Mom ile bire-bir ilişkisi vardır - bir kişinin yalnızca bir babası ve bir annesi olabilir. Dad ve Mom'un Person ile bire-çok ilişkisi vardır çünkü babaların ve annelerin birden fazla çocuğu olabilir.

go
Mom   Mom `gorm:"foreignKey:MomId;"`

Struct'ları embed etmenin amacı foreign key'leri ve referansları belirtmeyi kolaylaştırmaktır. Varsayılan olarak foreign key alan adı formatı ReferencedTypeName+Id'dir. Örneğin MomId. Varsayılan olarak birincil anahtara referans verir. Referans vermek için bir alanı struct etiketleri aracılığıyla belirtebilirsiniz:

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string

  MomId uint
  Mom   Mom `gorm:"foreignKey:MomId;references:Sid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`

  DadId uint
  Dad   Dad `gorm:"foreignKey:DadId;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type Mom struct {
  Id   uint
  Sid  uint `gorm:"uniqueIndex;"`
  Name string

  Persons []Person `gorm:"foreignKey:MomId;"`
}

Bunlardan constraint:OnUpdate:CASCADE,OnDelete:SET NULL; foreign key kısıtlamasını tanımlar.

Hooks

Bir varlık modeli hook'ları özelleştirebilir:

  • Create
  • Update
  • Delete
  • Query

İlgili arayüzler aşağıdaki gibidir:

go
// Create öncesi tetiklenir
type BeforeCreateInterface interface {
    BeforeCreate(*gorm.DB) error
}

// Create sonrası tetiklenir
type AfterCreateInterface interface {
    AfterCreate(*gorm.DB) error
}

// Update öncesi tetiklenir
type BeforeUpdateInterface interface {
    BeforeUpdate(*gorm.DB) error
}

// Update sonrası tetiklenir
type AfterUpdateInterface interface {
    AfterUpdate(*gorm.DB) error
}

// Save öncesi tetiklenir
type BeforeSaveInterface interface {
    BeforeSave(*gorm.DB) error
}

// Save sonrası tetiklenir
type AfterSaveInterface interface {
    AfterSave(*gorm.DB) error
}

// Delete öncesi tetiklenir
type BeforeDeleteInterface interface {
    BeforeDelete(*gorm.DB) error
}

// Delete sonrası tetiklenir
type AfterDeleteInterface interface {
    AfterDelete(*gorm.DB) error
}

// Query sonrası tetiklenir
type AfterFindInterface interface {
    AfterFind(*gorm.DB) error
}

Struct'lar bu arayüzleri uygulayarak davranışları özelleştirebilir.

Etiketler

Aşağıda gorm tarafından desteklenen bazı etiketler bulunmaktadır:

Etiket AdıAçıklama
columnVeritabanı sütun adını belirtir
typeSütun veri türü. Önerilen uyumlu genel türleri kullanmaktır. Örneğin tüm veritabanları bool, int, uint, float, string, time, bytes destekler ve diğer etiketlerle kullanılabilir. Örneğin not null, size, autoIncrement... varbinary(8) gibi belirtilen veritabanı veri türlerini belirtmek de desteklenir. Belirtilen veritabanı veri türlerini kullanırken tam veritabanı veri türü olmalıdır. Örneğin MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializerVeriyi veritabanına serileştirmek veya serileştirmeyi açmak için serializer belirtir. Örneğin serializer:json/gob/unixtime
sizeSütun veri türü boyutunu veya uzunluğunu tanımlar. Örneğin size: 256
primaryKeySütunu birincil anahtar olarak tanımlar
uniqueSütunu benzersiz anahtar olarak tanımlar
defaultSütun varsayılan değerini tanımlar
precisionSütun hassasiyetini belirtir
scaleSütun ölçeğini belirtir
not nullSütunu NOT NULL olarak belirtir
autoIncrementSütunu otomatik artan olarak belirtir
autoIncrementIncrementOtomatik artım adımı. Ardışık kayıtlar arasındaki aralığı kontrol eder
embeddedGömülü alan
embeddedPrefixGömülü alan için sütun adı öneki
autoCreateTimeOluşturma sırasında mevcut zamanı izler. int alanları için zaman damgası saniyelerini izler. Nanosaniye/milisaniye zaman damgasını izlemek için nano/milli kullanabilirsiniz. Örneğin autoCreateTime:nano
autoUpdateTimeOluşturma/güncelleme sırasında mevcut zamanı izler. int alanları için zaman damgası saniyelerini izler. Nanosaniye/milisaniye zaman damgasını izlemek için nano/milli kullanabilirsiniz. Örneğin autoUpdateTime:milli
indexParametrelere dayalı indeks oluşturur. Aynı adı kullanan birden fazla alan bileşik indeks oluşturur. Ayrıntılar için Indexes bölümüne bakın
uniqueIndexindex ile aynıdır ancak benzersiz indeks oluşturur
checkCheck kısıtlaması oluşturur. Örneğin check:age > 13. Ayrıntılar için Constraints bölümüne bakın
<-Alan yazma iznini ayarlar. <-:create yalnızca oluşturma, <-:update yalnızca güncelleme, <-:false yazma izni yok, <- oluşturma ve güncelleme izni
->Alan okuma iznini ayarlar. ->:false okuma izni yok
-Bu alanı yoksayar. - okuma/yazma yok, -:migration taşıma izni yok, -:all okuma/yazma/taşıma izni yok
commentTaşıma sırasında alan için yorum ekler
foreignKeyMevcut modelin sütununu join tablosu için foreign key olarak belirtir
referencesReferans alınan tablonun sütun adını belirtir. Join tablosu foreign key'i olarak eşlenir
polymorphicPolimorfik türü belirtir. Örneğin model adı
polymorphicValuePolimorfik değeri belirtir. Varsayılan tablo adı
many2manyJoin tablosu adını belirtir
joinForeignKeyJoin tablosundaki foreign key sütun adını belirtir. Mevcut tabloya eşlenir
joinReferencesJoin tablosundaki foreign key sütun adını belirtir. Referans alınan tabloya eşlenir
constraintİlişki kısıtlamaları. Örneğin OnUpdate, OnDelete

Migration

AutoMigrate metodu bizim için otomatik taşımaya yardımcı olur. Tabloları kısıtlamaları indeksleri foreign key'leri vb. oluşturacaktır:

go
func (db *DB) AutoMigrate(dst ...interface{}) error

Örneğin:

go
type Person struct {
  Id      uint   `gorm:"primaryKey;"`
  Name    string `gorm:"type:varchar(100);uniqueIndex;"`
  Address string
}

type Order struct {
  Id   uint
  Name string
}

db.AutoMigrate(Person{}, Order{})
// CREATE TABLE `person` (`id` bigint unsigned AUTO_INCREMENT,`name` varchar(100),`address` longtext,PRIMARY KEY (`id`),UNIQUE INDEX `idx_person_name` (`name`))
// CREATE TABLE `orders` (`id` bigint unsigned AUTO_INCREMENT,`name` longtext,PRIMARY KEY (`id`))

Veya Migrator metodu aracılığıyla Migrator arayüzüne erişerek manuel olarak çalıştırabiliriz:

go
func (db *DB) Migrator() Migrator

Aşağıdaki arayüz metodlarını destekler:

go
type Migrator interface {
  // AutoMigrate
  AutoMigrate(dst ...interface{}) error

  // Database
  CurrentDatabase() string
  FullDataTypeOf(*schema.Field) clause.Expr
  GetTypeAliases(databaseTypeName string) []string

  // Tables
  CreateTable(dst ...interface{}) error
  DropTable(dst ...interface{}) error
  HasTable(dst interface{}) bool
  RenameTable(oldName, newName interface{}) error
  GetTables() (tableList []string, err error)
  TableType(dst interface{}) (TableType, error)

  // Columns
  AddColumn(dst interface{}, field string) error
  DropColumn(dst interface{}, field string) error
  AlterColumn(dst interface{}, field string) error
  MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
  HasColumn(dst interface{}, field string) bool
  RenameColumn(dst interface{}, oldName, field string) error
  ColumnTypes(dst interface{}) ([]ColumnType, error)

  // Views
  CreateView(name string, option ViewOption) error
  DropView(name string) error

  // Constraints
  CreateConstraint(dst interface{}, name string) error
  DropConstraint(dst interface{}, name string) error
  HasConstraint(dst interface{}, name string) bool

  // Indexes
  CreateIndex(dst interface{}, name string) error
  DropIndex(dst interface{}, name string) error
  HasIndex(dst interface{}, name string) bool
  RenameIndex(dst interface{}, oldName, newName string) error
  GetIndexes(dst interface{}) ([]Index, error)
}

Metod listesi birden fazla boyutu içerir: veritabanı, tablo, sütun, görünüm, indeks, kısıtlama. Özelleştirme gereken kullanıcılar için daha ince taneli işlemlere izin verir.

Tablo Yorumlarını Belirtme

Taşıma sırasında tablo yorumları eklemek istiyorsanız aşağıdaki gibi ayarlayabilirsiniz:

go
db.Set("gorm:table_options", " comment 'person table'").Migrator().CreateTable(Person{})

AutoMigrate() metodunu taşıma için kullanırsanız ve struct'lar arasında referans ilişkileri varsa gorm önce referans alınan tabloları yinelemeli olarak oluşturacaktır. Bu hem referans alınan hem de referans eden tablolarda yinelenen yorumlara neden olacaktır. Bu nedenle CreateTable metodunu kullanmanız önerilir.

TIP

Tablo oluştururken CreateTable metodu referans edilen tablonun referans eden tablodan önce oluşturulmasını gerektirir. Aksi takdirde hata verir. AutoMigrate metodu bunu gerektirmez çünkü tabloları referans ilişkilerini takip ederek yinelemeli olarak oluşturur.

Create

Create

Yeni kayıtlar oluştururken Create metodu çoğu durumda kullanılır:

go
func (db *DB) Create(value interface{}) (tx *DB)

Verilen struct:

go
type Person struct {
  Id   uint `gorm:"primaryKey;"`
  Name string
}

Kayıt oluştur:

go
user := Person{
    Name: "jack",
}

// Referans geçirmelisiniz
db = db.Create(&user)

// Yürütme sırasında hata oluştu
err = db.Error
// Etkilenen satır sayısı
affected := db.RowsAffected

Oluşturmadan sonra gorm birincil anahtarı user struct'ına yazacaktır. Bu nedenle işaretçi geçirmeniz gerekir. Slice geçirdiyseniz toplu oluşturur:

go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Create(&user)

Benzer şekilde gorm birincil anahtarı slice'a yazar. Veri hacmi çok büyük olduğunda CreateInBatches metodunu kullanarak toplu olarak oluşturabilirsiniz. Çünkü oluşturulan INSERT INTO table VALUES (),() SQL ifadesi çok uzun hale gelir ve her veritabanının SQL uzunluğu için sınırları vardır. Bu nedenle gerektiğinde toplu oluşturmayı seçebilirsiniz:

go
db = db.CreateInBatches(&user, 50)

Ayrıca Save metodu da kayıt oluşturabilir. İşlevi birincil anahtar eşleştiğinde kaydı güncellemektir. Aksi takdirde ekler:

go
func (db *DB) Save(value interface{}) (tx *DB)
go
user := []Person{
    {Name: "jack"},
    {Name: "mike"},
    {Name: "lili"},
}

db = db.Save(&user)

Upsert

Save metodu yalnızca birincil anahtarları eşleştirebilir. Daha özel bir upsert'i Clause oluşturarak构建 edebiliriz. Örneğin bu kod satırı:

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: false,
    DoUpdates: clause.AssignmentColumns([]string{"address"}),
    UpdateAll: false,
}).Create(&p)

İşlevi şudur: name alanı çakıştığında address alanının değerini güncelle. Çakışma yoksa yeni kayıt oluştur. Çakışma olduğunda hiçbir şey yapmayabilirsiniz:

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    DoNothing: true,
}).Create(&p)

Veya doğrudan tüm alanları güncelleyin:

go
db.Clauses(clause.OnConflict{
    Columns:   []clause.Column{{Name: "name"}},
    UpdateAll: true,
}).Create(&p)

Upsert kullanmadan önce çakışma alanına indeks eklemeyi unutmayın.

Query

First

Sorgular için gorm oldukça fazla metod sağlar. İlki First metodudur:

go
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB)

İşlevi birincil anahtara göre artan sırada ilk kaydı bulmaktır. Örneğin:

go
var person Person
result := db.First(&person)
err := result.Error
affected := result.RowsAffected

Gorm'un sorgulanan veriyi struct'a eşlemesi için dest işaretçisi geçirin.

Veya sorgu tablosunu belirtmek için Table ve Model metodlarını kullanın. İlki string tablo adı alır. İkincisi varlık modeli alır:

db.Table("person").Find(&p)
db.Model(Person{}).Find(&p)

TIP

Geçirilen işaretçi elemanı bir varlık modeli içeriyorsa (struct işaretçisi veya struct slice işaretçisi gibi) hangi tabloyu sorgulayacağınızı manuel olarak belirtmenize gerek yoktur. Bu kural tüm CRUD işlemleri için geçerlidir.

Take

Take metodu First'a benzer. Farkı birincil anahtara göre sıralamamasıdır:

go
func (db *DB) Take(dest interface{}, conds ...interface{}) (tx *DB)
go
var person Person
result := db.Take(&person)
err := result.Error
affected := result.RowsAffected

Pluck

Pluck metodu bir tablodan tek bir sütunu toplu olarak sorgulamak için kullanılır. Sorgu sonuçları belirli bir türde slice'ta toplanabilir. Mutlaka varlık türü slice'ı olmak zorunda değildir:

go
func (db *DB) Pluck(column string, dest interface{}) (tx *DB)

Örneğin herkesin adresini string slice'ta topla:

go
var adds []string

// SELECT `address` FROM `person` WHERE name IN ('jack','lili')
db.Model(Person{}).Where("name IN ?", []string{"jack", "lili"}).Pluck("address", &adds)

Aslında şuna eşdeğerdir:

go
db.Select("address").Where("name IN ?", []string{"jack", "lili"}).Find(&adds)

Count

Count metodu varlık kayıt sayısını saymak için kullanılır:

go
func (db *DB) Count(count *int64) (tx *DB)

Kullanım örneğine bakın:

go
var count int64

// SELECT count(*) FROM `person`
db.Model(Person{}).Count(&count)

Find

Toplu sorgular için en yaygın kullanılan metod Find metodudur:

go
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

Verilen koşullarla eşleşen tüm kayıtları bulur:

go
// SELECT * FROM `person`
var ps []Person
db.Find(&ps)

Select

Varsayılan olarak gorm tüm alanları sorgular. Select metodu aracılığıyla alanları belirtebiliriz:

go
func (db *DB) Select(query interface{}, args ...interface{}) (tx *DB)

Örneğin:

go
// SELECT `address`,`name` FROM `person` ORDER BY `person`.`id` LIMIT 1
db.Select("address", "name").First(&p)

Şuna eşdeğerdir:

go
db.Select([]string{"address", "name"}).First(&p)

Aynı zamanda Omit metodunu kullanarak alanları yoksayabilirsiniz:

go
func (db *DB) Omit(columns ...string) (tx *DB)

Örneğin:

go
// SELECT `person`.`id`,`person`.`name` FROM `person` WHERE id IN (1,2,3,4)
db.Omit("address").Where("id IN ?", []int{1, 2, 3, 4}).Find(&ps)

Select ve Omit tarafından seçilen veya yoksayılan alanlar oluşturma ve güncelleme sorgularında da geçerli olacaktır.

Where

Koşullu sorgular Where metodunu kullanır:

go
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

İşte basit bir örnek:

go
var p Person

db.Where("id = ?", 1).First(&p)

Zincir işlemlerinde birden fazla Where kullanmak birden fazla AND ifadesi oluşturur. Örneğin:

go
// SELECT * FROM `person` WHERE id = 1 AND name = 'jack' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).Where("name = ?", "jack").First(&p)

Veya OR ifadeleri oluşturmak için Or metodunu kullanın:

go
func (db *DB) Or(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Where("address = ?", "usa").
    First(&p)

Ayrıca Not metodu da benzerdir:

go
func (db *DB) Not(query interface{}, args ...interface{}) (tx *DB)
go
// SELECT * FROM `person` WHERE id = 1 OR name = 'jack' AND NOT name = 'mike' AND address = 'usa' ORDER BY `person`.`id` LIMIT 1
db.Where("id = ?", 1).
    Or("name = ?", "jack").
    Not("name = ?", "mike").
    Where("address = ?", "usa").
    First(&p)

IN koşulları için slice'ı doğrudan Where metoduna geçirebilirsiniz:

go
db.Where("address IN ?", []string{"cn", "us"}).Find(&ps)

Veya çok sütunlu IN koşulları için parametreleri taşımak için [][]any türünü kullanmanız gerekir:

go
// SELECT * FROM `person` WHERE (id, name, address) IN ((1,'jack','uk'),(2,'mike','usa'))
db.Where("(id, name, address) IN ?", [][]any{{1, "jack", "uk"}, {2, "mike", "usa"}}).Find(&ps)

Gorm where gruplandırmayı destekler. Yukarıdaki ifadeleri birleştirir:

go
db.Where(
    db.Where("name IN ?", []string{"cn", "uk"}).Where("id IN ?", []uint{1, 2}),
  ).Or(
    db.Where("name IN ?", []string{"usa", "jp"}).Where("id IN ?", []uint{3, 4}),
  ).Find(&ps)
// SELECT * FROM `person` WHERE (name IN ('cn','uk') AND id IN (1,2)) OR (name IN ('usa','jp') AND id IN (3,4))

Order

Sıralama Order metodunu kullanır:

go
func (db *DB) Order(value interface{}) (tx *DB)

Kullanım örneğine bakın:

go
var ps []Person

// SELECT * FROM `person` ORDER BY name ASC, id DESC
db.Order("name ASC, id DESC").Find(&ps)

Birden fazla kez çağırabilirsiniz:

go
// SELECT * FROM `person` ORDER BY name ASC, id DESC, address
db.Order("name ASC, id DESC").Order("address").Find(&ps)

Limit

Limit ve Offset metodları genellikle sayfalama sorguları için kullanılır:

go
func (db *DB) Limit(limit int) (tx *DB)

func (db *DB) Offset(offset int) (tx *DB)

İşte basit bir sayfalama örneği:

go
var (
    ps   []Person
    page = 2
    size = 10
)

// SELECT * FROM `person` LIMIT 10 OFFSET 10
db.Offset((page - 1) * size).Limit(size).Find(&ps)

Group

Group ve Having metodları çoğunlukla gruplama işlemleri için kullanılır:

go
func (db *DB) Group(name string) (tx *DB)

func (db *DB) Having(query interface{}, args ...interface{}) (tx *DB)

Örneğe bakın:

go
var (
    ps []Person
)

// SELECT `address` FROM `person` GROUP BY `address` HAVING address IN ('cn','us')
db.Select("address").Group("address").Having("address IN ?", []string{"cn", "us"}).Find(&ps)

Distinct

Distinct metodu çoğunlukla tekrarları önlemek için kullanılır:

go
func (db *DB) Distinct(args ...interface{}) (tx *DB)

Örneğe bakın:

go
// SELECT DISTINCT `name` FROM `person` WHERE address IN ('cn','us')
db.Where("address IN ?", []string{"cn", "us"}).Distinct("name").Find(&ps)

SubQuery

Alt sorgu iç içe geçmiş bir sorgudur. Örneğin id değeri ortalamadan büyük olan tüm kişileri bulmak istiyorsanız:

go
// SELECT * FROM `person` WHERE id > (SELECT AVG(id) FROM `person`
db.Where("id > (?)", db.Model(Person{}).Select("AVG(id)")).Find(&ps)

Alt sorgudan:

go
// SELECT * FROM (SELECT * FROM `person` WHERE address IN ('cn','uk')) as p
db.Table("(?) as p", db.Model(Person{}).Where("address IN ?", []string{"cn", "uk"})).Find(&ps)

Lock

Gorm kilitleme desteği sağlamak için clause.Locking clause'unu kullanır:

go
// SELECT * FROM `person` FOR UPDATE
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&ps)

// SELECT * FROM `person` FOR SHARE NOWAIT
db.Clauses(clause.Locking{Strength: "SHARE", Options: "NOWAIT"}).Find(&ps)

Iteration

Rows metodu aracılığıyla iterator alabilirsiniz:

go
func (db *DB) Rows() (*sql.Rows, error)

Iterator'ı travers ederek ScanRows metodunu kullanarak her satır sonucunu struct'a tarayın:

go
rows, err := db.Model(Person{}).Rows()
if err != nil {
    return
}
defer rows.Close()

for rows.Next() {
    var p Person
    err := db.ScanRows(rows, &p)
    if err != nil {
        return
    }
}

Update

Save

Save metodu oluşturma sırasında belirtilmişti. Kayıtları güncellemek için de kullanılabilir. Tüm alanları güncelleyecektir. Bazı struct alanları sıfır değer olsa bile. Ancak birincil anahtar eşleşmezse ekleme işlemi yapar:

go
var p Person

db.First(&p)

p.Address = "poland"
// UPDATE `person` SET `name`='json',`address`='poland' WHERE `id` = 2
db.Save(&p)

Birincil anahtar hariç tüm alanları SET ifadesine eklediğini görebilirsiniz.

Update

Bu nedenle çoğu durumda Update metodunu kullanmanız önerilir:

go
func (db *DB) Update(column string, value interface{}) (tx *DB)

Öncelikle tek sütun alanını güncellemek için kullanılır:

go
var p Person

db.First(&p)

// UPDATE `person` SET `address`='poland' WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Update("address", "poland")

Updates

Updates metodu birden fazla sütunu güncellemek için kullanılır. Parametre olarak struct'ları ve map'leri kabul eder. Struct alanları sıfır değer olduğunda bu alanlar yoksayılır. Ancak map'lerde yoksayılmaz:

go
func (db *DB) Updates(values interface{}) (tx *DB)

İşte bir örnek:

go
var p Person

db.First(&p)

// UPDATE `person` SET `name`='jojo',`address`='poland' WHERE `id` = 2
db.Model(p).Updates(Person{Name: "jojo", Address: "poland"})

// UPDATE `person` SET `address`='poland',`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "address": "poland"})

SQL İfadeleri

Bazen alanlarda kendi kendine artırma veya azaltma ya da alanın kendisini içeren diğer işlemleri yapmanız gerekir. Genellikle önce sorgulama sonra hesaplama sonra güncelleme yaparsınız veya SQL ifadelerini kullanırsınız:

go
func Expr(expr string, args ...interface{}) clause.Expr

Aşağıdaki örneğe bakın:

go
// UPDATE `person` SET `age`=age + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age + age")})

// UPDATE `person` SET `age`=age * 2 + age,`name`='jojo' WHERE `id` = 2
db.Model(p).Updates(map[string]any{"name": "jojo", "age": gorm.Expr("age * 2 + age")})

Delete

gorm'da kayıt silme Delete metodunu kullanır. Doğrudan varlık struct'ı geçirebilir veya koşullar geçirebilir:

go
func (db *DB) Delete(value interface{}, conds ...interface{}) (tx *DB)

Örneğin doğrudan struct geçirin:

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&p)

Veya:

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Model(p).Delete(nil)

Veya koşulları belirtin:

go
// DELETE FROM `person` WHERE id = 2
db.Model(Person{}).Where("id = ?", p.Id).Delete(nil)

Veya şu şekilde basitleştirin:

go
var p Person

db.First(&p)

// DELETE FROM `person` WHERE id = 2
db.Delete(&Person{}, "id = ?", 2)

// DELETE FROM `person` WHERE `person`.`id` = 2
db.Delete(&Person{}, 2)

Toplu silme için slice geçirin:

go
// DELETE FROM `person` WHERE id IN (1,2,3)
db.Delete(&Person{}, "id IN ?", []uint{1, 2, 3})
// DELETE FROM `person` WHERE `person`.`id` IN (1,2,3)
db.Delete(&Person{}, []uint{1, 2, 3})

Soft Delete

Varlık modeliniz soft delete kullanıyorsa silme sırasında varsayılan olarak güncelleme işlemi yapar. Kalıcı olarak silmek istiyorsanız Unscoped metodunu kullanabilirsiniz:

go
db.Unscoped().Delete(&Person{}, []uint{1, 2, 3})

İlişki Tanımı

Gorm tablo ilişkisi etkileşim yetenekleri sağlar. Embed struct'lar ve alanlar aracılığıyla struct'lar arasındaki ilişkileri tanımlar.

Bire-Bir

Bire-bir ilişki en basitidir. Normalde bir kişinin yalnızca bir annesi olabilir. Aşağıdaki struct'a bakın:

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`
}

type Mom struct {
  Id   uint
  Name string
}

Person struct'ı Mom struct'ını embed ederek Mom türüne referans gerçekleştirir. Bunlardan Person.MomId referans alandır ve birincil anahtar Mom.Id referans alınan alandır. Bu bire-bir ilişkiyi tamamlar. Foreign key'leri referansları kısıtlamaları ve varsayılan foreign key kurallarını nasıl özelleştireceğiniz Foreign Key Tanımı bölümünde açıklanmıştır. Tekrarlanmayacaktır.

TIP

Foreign key alanları için sql paketi tarafından sağlanan türleri kullanmanız önerilir. Çünkü foreign key'ler varsayılan olarak NULL olabilir. Create ile kayıt oluştururken sıradan türler kullanırsanız sıfır değer 0 da oluşturulur. Mevcut olmayan foreign key oluşturmak açıkça izin verilmez.

Bire-Çok

Aşağıda bir school struct'ı ekleyin. Okul ve öğrenci arasındaki ilişki bire-çoktur. Bir okulun birden fazla öğrencisi vardır ancak bir öğrenci yalnızca bir okula gidebilir:

go
type Person struct {
    Id      uint
    Name    string
    Address string
    Age     uint

    MomId sql.NullInt64
    Mom   Mom `gorm:"foreignKey:MomId;"`

    SchoolId sql.NullInt64
    School   School `gorm:"foreignKey:SchoolId;"`
}

type Mom struct {
    Id   uint
    Name string
}


type School struct {
    Id   uint
    Name string

    Persons []Person `gorm:"foreignKey:SchoolId;"`
}

school.Persons []Person türündedir. Birden fazla öğrencisi olabileceğini gösterirken Person School'a referans veren bir foreign key içermelidir. Bu Person.SchoolId'dir.

Çoka-Çok

Bir kişi birçok eve sahip olabilir ve bir evde birçok kişi yaşayabilir. Bu çoka-çok ilişkidir:

go
type Person struct {
  Id      uint
  Name    string
  Address string
  Age     uint

  MomId sql.NullInt64
  Mom   Mom `gorm:"foreignKey:MomId;"`

  SchoolId sql.NullInt64
  School   School `gorm:"foreignKey:SchoolId;"`

  Houses []House `gorm:"many2many:person_house;"`
}

type Mom struct {
  Id   uint
  Name string
}

type School struct {
  Id   uint
  Name string

  Persons []Person
}

type House struct {
  Id   uint
  Name string

  Persons []Person `gorm:"many2many:person_house;"`
}

type PersonHouse struct {
  PersonId sql.NullInt64
  Person   Person `gorm:"foreignKey:PersonId;"`
  HouseId  sql.NullInt64
  House    House `gorm:"foreignKey:HouseId;"`
}

Person ve House çoka-çok ilişkiyi temsil etmek için birbirlerinin slice türlerini tutar. Çoka-çok ilişkiler genellikle many2many aracılığıyla belirtilen bir join tablosu oluşturmayı gerektirir. Join tablosunun foreign key'leri doğru şekilde belirtilmelidir.

Struct'ları oluşturduktan sonra gorm'un veritabanına otomatik olarak taşımasına izin verin:

go
tables := []any{
    School{},
    Mom{},
    Person{},
    House{},
    PersonHouse{},
}
for _, table := range tables {
    db.Migrator().CreateTable(&table)
}

Referans edilen ve referans eden tablolar arasındaki oluşturma sırasına dikkat edin.

İlişki İşlemleri

Yukarıdaki üç ilişkiyi oluşturduktan sonra sonraki adım ilişkileri kullanarak CRUD yapmaktır. Bu öncelikle Association metodunu kullanır:

go
func (db *DB) Association(column string) *Association

Bir ilişki parametresi alır. Değeri referans struct'ına embed edilen referans alınan türün alan adı olmalıdır:

go
db.Model(&person).Association("Mom").Find(&mom)

Örneğin bir kişinin annesini ilişki aracılığıyla bulmak için Association parametresi Mom'dur. Bu Person.Mom alan adıdır.

İlişki Oluştur

go
// Veri tanımla
jenny := Mom{
    Name: "jenny",
}

mit := School{
    Name:    "MIT",
    Persons: nil,
}

h1 := House{
    Id:      0,
    Name:    "h1",
    Persons: nil,
}

h2 := House{
    Name:    "h2",
    Persons: nil,
}

jack := Person{
    Name:    "jack",
    Address: "usa",
    Age:     18,
}

mike := Person{
    Name:    "mike",
    Address: "uk",
    Age:     20,
}

// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('jack','usa',18,NULL,NULL)
db.Create(&jack)
// INSERT INTO `schools` (`name`) VALUES ('MIT')
db.Create(&mit)

// Person-Mom ilişkisi ekle. Bire-bir ilişki
// INSERT INTO `moms` (`name`) VALUES ('jenny') ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=1 WHERE `id` = 1
db.Model(&jack).Association("Mom").Append(&jenny)

// School-Person ilişkisi ekle. Bire-çok ilişki
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('jack','usa',18,1,1,1),('mike','uk',20,NULL,1,DEFAULT) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
db.Model(&mit).Association("Persons").Append([]Person{jack, mike})

// Person-Houses ilişkisi ekle. Çoka-çok ilişki
// INSERT INTO `houses` (`name`) VALUES ('h1'),('h2') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,1),(1,2) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
db.Model(&jack).Association("Houses").Append([]House{h1, h2})

Tüm kayıtlar mevcut değilse ilişki oluşturma sırasında önce kayıtları oluşturur sonra ilişkiyi oluşturur.

İlişki Bul

Aşağıda ilişkilerin nasıl bulunacağı gösterilmiştir:

go
// Bire-bir ilişki bul
var person Person
var mom Mom

// SELECT * FROM `people` ORDER BY `people`.`id` LIMIT 1
db.First(&person)
// SELECT * FROM `moms` WHERE `moms`.`id` = 1
db.Model(person).Association("Mom").Find(&mom)

// Bire-çok ilişki bul
var school School
var persons []Person

// SELECT * FROM `schools` ORDER BY `schools`.`id` LIMIT 1
db.First(&school)
// SELECT * FROM `people` WHERE `people`.`school_id` = 1
db.Model(&school).Association("Persons").Find(&persons)

// Çoka-çok ilişki bul
var houses []House

// SELECT `houses`.`id`,`houses`.`name` FROM `houses` JOIN `person_house` ON `person_house`.`house_id` = `houses`.`id` AND `person_house`.`person_id` IN (1,2)
db.Model(&persons).Association("Houses").Find(&houses)

İlişki bulma mevcut verilere dayalı olarak referans tabloda koşullarla eşleşen kayıtları bulur. Çoka-çok ilişkiler için gorm tablo join işlemini otomatik olarak tamamlar.

İlişki Güncelle

Aşağıda ilişkilerin nasıl güncelleneceği gösterilmiştir:

go
// Bire-bir ilişki güncelle
var jack Person

lili := Mom{
    Name: "lili",
}

// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)

// INSERT INTO `moms` (`name`) VALUES ('lili')
db.Create(&lili)

// INSERT INTO `moms` (`name`,`id`) VALUES ('lili',2) ON DUPLICATE KEY UPDATE `id`=`id`
// UPDATE `people` SET `mom_id`=2 WHERE `id` = 1
db.Model(&jack).Association("Mom").Replace(&lili)

// Bire-çok ilişki güncelle

var mit School
newPerson := []Person{{Name: "bob"}, {Name: "jojo"}}
// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`) VALUES ('bob','',0,NULL,NULL),('jojo','',0,NULL,NULL)
db.Create(&newPerson)

//  SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)

// INSERT INTO `people` (`name`,`address`,`age`,`mom_id`,`school_id`,`id`) VALUES ('bob','',0,NULL,1,4),('jojo','',0,NULL,1,5) ON DUPLICATE KEY UPDATE `school_id`=VALUES(`school_id`)
//  UPDATE `people` SET `school_id`=NULL WHERE `people`.`id` NOT IN (4,5) AND `people`.`school_id` = 1
db.Model(&mit).Association("Persons").Replace(newPerson)

// Çoka-çok ilişki güncelle

// INSERT INTO `houses` (`name`) VALUES ('h3'),('h4'),('h5') ON DUPLICATE KEY UPDATE `id`=`id`
// INSERT INTO `person_house` (`person_id`,`house_id`) VALUES (1,3),(1,4),(1,5) ON DUPLICATE KEY UPDATE `person_id`=`person_id`
// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` NOT IN (3,4,5)
db.Model(&jack).Association("Houses").Replace([]House{{Name: "h3"}, {Name: "h4"}, {Name: "h5"}})

İlişki güncelleme sırasında hem referans edilen hem de referans eden veriler mevcut değilse gorm bunları oluşturmaya çalışır.

İlişki Sil

Aşağıda ilişkilerin nasıl silineceği gösterilmiştir:

go
// Bire-bir ilişki sil
var (
    jack Person
    lili Mom
)

// SELECT * FROM `people` WHERE name = 'jack' ORDER BY `people`.`id` LIMIT 1
db.Where("name = ?", "jack").First(&jack)

//  SELECT * FROM `moms` WHERE name = 'lili' ORDER BY `moms`.`id` LIMIT 1
db.Where("name = ?", "lili").First(&lili)

// UPDATE `people` SET `mom_id`=NULL WHERE `people`.`id` = 1 AND `people`.`mom_id` = 2
db.Model(&jack).Association("Mom").Delete(&lili)

// Bire-çok ilişki sil

var (
    mit     School
    persons []Person
)

// SELECT * FROM `schools` WHERE name = 'mit' ORDER BY `schools`.`id` LIMIT 1
db.Where("name = ?", "mit").First(&mit)
// SELECT * FROM `people` WHERE name IN ('jack','mike')
db.Where("name IN ?", []string{"jack", "mike"}).Find(&persons)

// UPDATE `people` SET `school_id`=NULL WHERE `people`.`school_id` = 1 AND `people`.`id` IN (1,2)
db.Model(&mit).Association("Persons").Delete(&persons)

// Çoka-çok ilişki sil
var houses []House

// SELECT * FROM `houses` WHERE name IN ('h3','h4')
db.Where("name IN ?", []string{"h3", "h4"}).Find(&houses)

// DELETE FROM `person_house` WHERE `person_house`.`person_id` = 1 AND `person_house`.`house_id` IN (3,4)
db.Model(&jack).Association("Houses").Delete(&houses)

İlişki silme yalnızca aralarındaki referans ilişkisini siler. Varlık kayıtlarını silmez. İlişkiyi doğrudan temizlemek için Clear metodunu da kullanabiliriz:

go
db.Model(&jack).Association("Houses").Clear()

İlgili varlık kayıtlarını silmek istiyorsanız Association işleminden sonra Unscoped işlemini ekleyebilirsiniz (many2many'i etkilemez):

go
db.Model(&jack).Association("Houses").Unscoped().Delete(&houses)

Bire-çok ve çoka-çok için kayıtları silmek için Select işlemini kullanabilirsiniz:

go
var (
    mit     School
)
db.Where("name = ?", "mit").First(&mit)

db.Select("Persons").Delete(&mit)

Preloading

Preloading ilişki verilerini sorgulamak için kullanılır. İlişki ilişkileri olan varlıklar için önce ilişkili referans alınan varlıkları yükler. Daha önce belirtilen ilişki sorgusu ilişki ilişkilerini sorgular. Preloading doğrudan varlık kayıtlarını sorgular. Tüm ilişki ilişkileri dahil. Sözdizimi açısından ilişki sorgusu önce belirtilen []Person'ı sonra []Person'a dayalı olarak ilişkili []Mom'u sorgulamayı gerektirir. Preloading sözdizimi açısından doğrudan []Person'ı sorgular ve tüm ilişki ilişkilerini de yükler. Ancak pratikte yürüttükleri SQL neredeyse aynıdır. Örneğe bakın:

go
var users []Person

// SELECT * FROM `moms` WHERE `moms`.`id` = 1
// SELECT * FROM `people`
db.Preload("Mom").Find(&users)

Bu bire-bir ilişki sorgu örneğidir. Çıktısı:

go
[{Id:1 Name:jack Address:usa Age:18 MomId:{Int64:1 Valid:true} Mom:{Id:1 Name:jenny} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]} {Id:2 Name:mike Address:uk Age:20 MomId:{Int64:0 Valid:false} Mom:{Id:0 Name:} SchoolId:{Int64:1 Valid:true} School:{Id:0 Name: Persons:[]} Houses:[]}]

İlişkili Mom'un da sorgulandığını görebilirsiniz ancak school ilişkisi preload edilmedi. Bu nedenle tüm School struct'ları sıfır değerlerdir. Tüm ilişkileri preload etmek için clause.Associations'u da kullanabilirsiniz. İç içe ilişkiler hariç:

go
db.Preload(clause.Associations).Find(&users)

Aşağıda iç içe preload örneği bulunmaktadır. İşlevi tüm school ilişkilerinin tüm öğrencilerini her öğrenciyle ilişkili anneyi ve her öğrencinin sahip olduğu evleri sorgulamaktır. Ayrıca her evin sahip setini de sorgular. School->Student->House->Student:

go
var schools []School

db.Preload("Persons").
    Preload("Persons.Mom").
    Preload("Persons.Houses").
    Preload("Persons.Houses.Persons").Find(&schools)

// Çıktı kodu. Mantık yoksayılabilir
for _, school := range schools {
    fmt.Println("school", school.Name)
    for _, person := range school.Persons {
        fmt.Println("person", person.Name)
        fmt.Println("mom", person.Mom.Name)
        for _, house := range person.Houses {
            var persons []string
            for _, p := range house.Persons {
                persons = append(persons, p.Name)
            }
            fmt.Println("house", house.Name, "owner", persons)
        }
        fmt.Println()
    }
}

Çıktı:

school MIT
person jack
mom jenny
house h1 owner [jack]
house h2 owner [jack]

person mike
mom

Her okulun her öğrencisinin annesini ve evlerini ve her evin sahiplerini çıktılar.

İşlemler

Gorm varsayılan olarak işlemleri etkinleştirir. Herhangi bir ekleme ve güncelleme işlemi başarısız olduktan sonra geri alınır. Bağlantı Yapılandırması bölümünde devre dışı bırakabilirsiniz. Performans yaklaşık yüzde 30 artar. Gorm'da işlemleri kullanmak için birden fazla metod vardır. Aşağıda basit bir tanıtım bulunmaktadır.

Otomatik

Closure işlemi. Transaction metodu aracılığıyla closure fonksiyonu geçirin. Fonksiyon dönüş değeri nil değilse otomatik olarak geri alınır:

go
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error)

Örneğe bakın. Closure içindeki işlemler harici db yerine parametre tx'i kullanmalıdır:

go
var ps []Person

db.Transaction(func(tx *gorm.DB) error {
    err := tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Create(&ps).Error
    if err != nil {
        return err
    }

    err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
    if err != nil {
        return err
    }

    return nil
})

Manuel

Manuel işlemler daha çok önerilir. Ne zaman geri alınacağını ve ne zaman commit edileceğini kontrol ederiz. Manuel işlemler aşağıdaki üç metodu kullanır:

go
// Begin metodu işlem başlatmak için kullanılır
func (db *DB) Begin(opts ...*sql.TxOptions) *DB

// Rollback metodu işlemi geri almak için kullanılır
func (db *DB) Rollback() *DB

// Commit metodu işlemi commit etmek için kullanılır
func (db *DB) Commit() *DB

Örneğe bakın. İşlem başlattıktan sonra ORM'i çalıştırmak için tx kullanmalısınız:

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.Rollback()
    return
}

tx.Commit()

Savepoint'leri belirtebilirsiniz:

go
var ps []Person

tx := db.Begin()

err := tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

tx.SavePoint("createBatch")

err = tx.Create(&ps).Error
if err != nil {
    tx.Rollback()
    return
}

err = tx.Model(Person{}).Where("id = ?", 1).Update("name", "jack").Error
if err != nil {
    tx.RollbackTo("createBatch")
    return
}

tx.Commit()

Özet

Yukarıdaki içeriğin tamamını okuduysanız ve kodu yazdıysanız veritabanında CRUD işlemleri gerçekleştirmek için gorm'u kullanabilirsiniz. Bu işlemlerin yanı sıra gorm'un birçok başka özelliği vardır. Daha fazla ayrıntı için resmi dokümantasyonu kontrol edebilirsiniz.

Golang by www.golangdev.cn edit