GORM 2.0 is a rewrite from scratch, it introduces some incompatible-API change and many improvements
Highlights
- Performance Improvements
- Modularity
- Context, Batch Insert, Prepared Statement Mode, DryRun Mode, Join Preload, Find To Map, Create From Map, FindInBatches supports
- Nested Transaction/SavePoint/RollbackTo SavePoint supports
- SQL Builder, Named Argument, Group Conditions, Upsert, Locking, Optimizer/Index/Comment Hints supports, SubQuery improvements, CRUD with SQL Expr and Context Valuer
- Full self-reference relationships support, Join Table improvements, Association Mode for batch data
- Multiple fields allowed to track create/update time, UNIX (milli/nano) seconds supports
- Field permissions support: read-only, write-only, create-only, update-only, ignored
- New plugin system, provides official plugins for multiple databases, read/write splitting, prometheus integrations…
- New Hooks API: unified interface with plugins
- New Migrator: allows to create database foreign keys for relationships, smarter AutoMigrate, constraints/checker support, enhanced index support
- New Logger: context support, improved extensibility
- Unified Naming strategy: table name, field name, join table name, foreign key, checker, index name rules
- Better customized data type support (e.g: JSON)
How To Upgrade
- 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 - Database drivers have been split into separate projects, e.g: github.com/go-gorm/sqlite, and its import path also changed to
gorm.io/driver/sqlite
Install
go get gorm.io/gorm |
Quick Start
import ( |
Major Features
The release note only cover major changes introduced in GORM V2 as a quick reference list
Context Support
- Database operations support
context.Context
with theWithContext
method - Logger also accepts context for tracing
db.WithContext(ctx).Find(&users) |
Batch 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 { |