GORM 2.0은 처음부터 다시 만들어졌으며, 일부 호환되지 않는 API 변경 및 많은 개선 사항을 도입했습니다.
하이라이트
- 성능 향상
- 모듈성
- Context, Batch Insert, Prepared Statement Mode, DryRun Mode, Join Preload, Map에서 검색하기, Map으로 생성하기, FindInBatches가 지원됩니다
- Nested Transaction/SavePoint/SavePoint로 Rollback하기가 지원됩니다
- SQL Builder, Named Argument, Group Conditions, Upsert, Locking, Optimizer/Index/Comment Hints 지원, SubQuery 개선, SQL Expr로 CRUD하기, Context Valuer
- 자기참조 관계(self-reference relationship) 를 완전하게 제공, Join Table 개선, Batch 데이터를 위한 Association Mode
- 생성/수정 시간을 추적할 수 있는 다중 필드 지원, UNIX(milli/nano) 초 지원
- 필드 권한 지원: read-only, write-only, create-only, update-only, ignored
- 새로운 플러그인 시스템, 여러 데이터베이스에 대한 공식 플러그인 제공, 읽기/쓰기 분할, 프로메테우스 통합…
- 새로운 Hooks API: 플러그인과 통합 된 인터페이스
- 새로운 Migrator: 관계, 제약 / 체커 지원, 향상된 인덱스 지원을위한 데이터베이스 외래 키 생성 가능
- 새로운 Logger: context 지원, 향상된 확장성
- 이름규칙 통합: 테이블 이름, 필드 이름, join table이름, 외래키, 체커, index 이름 규칙
- 더 나은 데이터타입 커스터마이징(예: JSON)
업그레이드 방법
- GORM’s developments moved to github.com/go-gorm, and its import path changed to
gorm.io/gorm
, for previous projects, you can keep usinggithub.com/jinzhu/gorm
GORM V1 Document - 데이터베이스 드라이버가 각각의 프로젝트로 분리되었습니다 (예: github.com/go-gorm/sqlite) 그리고 import path 또한
gorm.io/driver/sqlite
로 변경되었습니다.
설치하기
go get gorm.io/gorm |
빠른 시작
import ( |
주요 기능
릴리즈 노트는 GORM V2에 도입된 주요 변경 사항만을 빠른 참조 목록으로 다룹니다.
Context 지원
- 데이터베이스 작업은
WithContext
함수를 사용하여context.Context
를 지원합니다 - Logger 또한 tracing을 위한 context를 허용합니다
db.WithContext(ctx).Find(&users) |
일괄 Insert
To efficiently insert large number of records, pass a slice to the Create
method. GORM will generate a single SQL statement to insert all the data and backfill primary key values, hook methods will be invoked too.
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} |
You can specify batch size when creating with CreateInBatches
, e.g:
var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}} |
Prepared Statement Mode
Prepared Statement Mode creates prepared stmt and caches them to speed up future calls
// globally mode, all operations will create prepared stmt and cache to speed up |
DryRun Mode
Generates SQL without executing, can be used to check or test generated SQL
stmt := db.Session(&Session{DryRun: true}).Find(&user, 1).Statement |
Join Preload
Preload associations using INNER JOIN, and will handle null data to avoid failing to scan
db.Joins("Company").Joins("Manager").Joins("Account").Find(&users, "users.id IN ?", []int{1,2}) |
Find To Map
Scan result to map[string]interface{}
or []map[string]interface{}
var result map[string]interface{} |
Create From Map
Create from map map[string]interface{}
or []map[string]interface{}
db.Model(&User{}).Create(map[string]interface{}{"Name": "jinzhu", "Age": 18}) |
FindInBatches
Query and process records in batch
result := db.Where("age>?", 13).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error { |
Nested Transaction
db.Transaction(func(tx *gorm.DB) error { |
SavePoint, RollbackTo
tx := db.Begin() |
Named Argument
GORM supports use sql.NamedArg
, map[string]interface{}
as named arguments
db.Where("name1 = @name OR name2 = @name", sql.Named("name", "jinzhu")).Find(&user) |
Group Conditions
db.Where( |
SubQuery
// Where SubQuery |
Upsert
clause.OnConflict
provides compatible Upsert support for different databases (SQLite, MySQL, PostgreSQL, SQL Server)
import "gorm.io/gorm/clause" |
Locking
db.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&users) |
Optimizer/Index/Comment Hints
import "gorm.io/hints" |
Check out Hints for details
CRUD From SQL Expr/Context Valuer
type Location struct { |
Check out Customize Data Types for details
Field permissions
Field permissions support, permission levels: read-only, write-only, create-only, update-only, ignored
type User struct { |
Track creating/updating time/unix (milli/nano) seconds for multiple fields
type User struct { |
Multiple Databases, Read/Write Splitting
GORM provides multiple databases, read/write splitting support with plugin DB Resolver
, which also supports auto-switching database/table based on current struct/table, and multiple sources、replicas supports with customized load-balancing logic
Check out Database Resolver for details
Prometheus
GORM provides plugin Prometheus
to collect DBStats
and user-defined metrics
Check out Prometheus for details
Naming Strategy
GORM allows users change the default naming conventions by overriding the default NamingStrategy
, which is used to build TableName
, ColumnName
, JoinTableName
, RelationshipFKName
, CheckerName
, IndexName
, Check out GORM Config for details
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ |
Logger
- Context support
- Customize/turn off the colors in the log
- Slow SQL log, default slow SQL time is 200ms
- Optimized the SQL log format so that it can be copied and executed in a database console
Transaction Mode
By default, all GORM write operations run inside a transaction to ensure data consistency, you can disable it during initialization to speed up write operations if it is not required
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{ |
DataTypes (JSON as example)
GORM optimizes support for custom types, so you can define a struct to support all databases
The following takes JSON as an example (which supports SQLite, MySQL, Postgres, refer: https://github.com/go-gorm/datatypes/blob/master/json.go)
import "gorm.io/datatypes" |
Smart Select
GORM allows select specific fields with Select
, and in V2, GORM provides smart select mode if you are querying with a smaller struct
type User struct { |
Associations Batch Mode
Association Mode supports batch data, e.g:
// Find all roles for all users |
Delete Associations when deleting
You are allowed to delete selected has one/has many/many2many relations with Select
when deleting records, for example:
// delete user's account when deleting user |
Breaking Changes
We are trying to list big breaking changes or those changes can’t be caught by the compilers, please create an issue or pull request here if you found any unlisted breaking changes
Tags
- GORM V2 prefer write tag name in
camelCase
, tags insnake_case
won’t works anymore, for example:auto_increment
,unique_index
,polymorphic_value
,embedded_prefix
, check out Model Tags - Tags used to specify foreign keys changed to
foreignKey
,references
, check out Associations Tags - Not support
sql
tag
Table Name
TableName
will not allow dynamic table name anymore, the result of TableName
will be cached for future
func (User) TableName() string { |
Please use Scopes
for dynamic tables, for example:
func UserTable(u *User) func(*gorm.DB) *gorm.DB { |
Creating and Deleting Tables requires the use of the Migrator
Previously tables could be created and dropped as follows:
db.CreateTable(&MyTable{}) |
Now you do the following:
db.Migrator().CreateTable(&MyTable{}) |
Foreign Keys
A way of adding foreign key constraints was;
db.Model(&MyTable{}).AddForeignKey("profile_id", "profiles(id)", "NO ACTION", "NO ACTION") |
Now you add constraints as follows:
db.Migrator().CreateConstraint(&Users{}, "Profiles") |
which translates to the following sql code for postgres:
ALTER TABLE `Profiles` ADD CONSTRAINT `fk_users_profiles` FORIEGN KEY (`useres_id`) REFRENCES `users`(`id`)) |
Method Chain Safety/Goroutine Safety
To reduce GC allocs, GORM V2 will share Statement
when using method chains, and will only create new Statement
instances for new initialized *gorm.DB
or after a New Session Method
, to reuse a *gorm.DB
, you need to make sure it just after a New Session Method
, for example:
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) |
Check out Method Chain for details
Default Value
GORM V2 won’t auto-reload default values created with database function after creating, checkout Default Values for details
Soft Delete
GORM V1 will enable soft delete if the model has a field named DeletedAt
, in V2, you need to use gorm.DeletedAt
for the model wants to enable the feature, e.g:
type User struct { |
NOTE:
gorm.Model
is usinggorm.DeletedAt
, if you are embedding it, nothing needs to change
BlockGlobalUpdate
GORM V2 enabled BlockGlobalUpdate
mode by default, to trigger a global update/delete, you have to use some conditions or use raw SQL or enable AllowGlobalUpdate
mode, for example:
db.Where("1 = 1").Delete(&User{}) |
ErrRecordNotFound
GORM V2 only returns ErrRecordNotFound
when you are querying with methods First
, Last
, Take
which is expected to return some result, and we have also removed method RecordNotFound
in V2, please use errors.Is
to check the error, e.g:
err := db.First(&user).Error |
Hooks Method
Before/After Create/Update/Save/Find/Delete must be defined as a method of type func(tx *gorm.DB) error
in V2, which has unified interfaces like plugin callbacks, if defined as other types, a warning log will be printed and it won’t take effect, check out Hooks for details
func (user *User) BeforeCreate(tx *gorm.DB) error { |
Update Hooks support Changed
to check fields changed or not
When updating with Update
, Updates
, You can use Changed
method in Hooks BeforeUpdate
, BeforeSave
to check a field changed or not
func (user *User) BeforeUpdate(tx *gorm.DB) error { |
Plugins
Plugin callbacks also need be defined as a method of type func(tx *gorm.DB) error
, check out Write Plugins for details
Updating with struct
When updating with struct, GORM V2 allows to use Select
to select zero-value fields to update them, for example:
db.Model(&user).Select("Role", "Age").Update(User{Name: "jinzhu", Role: "", Age: 0}) |
Associations
GORM V1 allows to use some settings to skip create/update associations, in V2, you can use Select
to do the job, for example:
db.Omit(clause.Associations).Create(&user) |
and GORM V2 doesn’t allow preload with Set("gorm:auto_preload", true)
anymore, you can use Preload
with clause.Associations
, e.g:
// preload all associations |
Also, checkout field permissions, which can be used to skip creating/updating associations globally
GORM V2 will use upsert to save associations when creating/updating a record, won’t save full associations data anymore to protect your data from saving uncompleted data, for example:
user := User{ |
Join Table
In GORM V2, a JoinTable
can be a full-featured model, with features like Soft Delete
,Hooks
, and define other fields, e.g:
type Person struct { |
After that, you could use normal GORM methods to operate the join table data, for example:
var results []PersonAddress |
Count
Count only accepts *int64
as the argument
Transactions
some transaction methods like RollbackUnlessCommitted
removed, prefer to use method Transaction
to wrap your transactions
db.Transaction(func(tx *gorm.DB) error { |
Checkout Transactions for details
Migrator
- Migrator will create database foreign keys by default
- Migrator is more independent, many API renamed to provide better support for each database with unified API interfaces
- AutoMigrate will alter column’s type if its size, precision, nullable changed
- Support Checker through tag
check
- Enhanced tag setting for
index
Checkout Migration for details
type UserIndex struct { |