GORM is a fantastic ORM library for Golang.
// TODO: To be updated…
目录
1. 快速开始
1.1 GORM 概述
- 全功能 ORM(几乎)
- 关联(包含一个/包含多个/属于/多对多/多态)
- Callbacks(在创建/保存/更新/删除/查找之前或之后)
- 预加载
- 事务
- 复合主键
- SQL Builder
- 数据库自动迁移
- 自定义日志
- 可扩展性,可基于 GORM 回调编写插件
- 每个功能都有测试
- 开发人员友好
1.2 安装
> go get -u github.com/jinzhu/gorm
1.3 快速体验
以MySQL
为例:
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
// 初始化 MySQL 连接
config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s",
"your_username",
"your_password",
"host:port",
"database",
true,
"Local")
db, err := gorm.Open("mysql", config)
if err != nil {
panic("Failed to connect database")
}
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Product{})
// 创建
db.Create(&Product{
Code: "L1212",
Price: 1000,
})
// 读取
var product Product
db.First(&product, 1) // 查询 ID 为 1 的 Product
db.First(&product, "code = ?", "L1212") // 查询 code 为 L1212 的 Product
// 更新 - 更新 product 的 price 为 2000
db.Model(&product).Update("price", 2000)
// 删除 - 删除 product
db.Delete(&product)
}
2. 数据库
2.1 连接数据库
首先需要导入目标数据库的驱动程序。例如:
import _ "github.com/go-sql-driver/mysql"
为了方便记住导入路径,GORM
包装了一些驱动:
import _ "github.com/jinzhu/gorm/dialects/mysql"
// import _ "github.com/jinzhu/gorm/dialects/postgres"
// import _ "github.com/jinzhu/gorm/dialects/sqlite"
// import _ "github.com/jinzhu/gorm/dialects/mssql"
MySQL
注意:为了处理time.Time
,需要包括parseTime
作为参数。更多支持的参数
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
func main() {
db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
defer db.Close()
}
也可以参照之前的形式来写:
// 初始化 MySQL 连接
config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s",
"your_username",
"your_password",
"host:port",
"database",
true,
"Local")
db, err := gorm.Open("mysql", config)
if err != nil {
panic("Failed to connect database")
}
defer db.Close()
PostgreSQL
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
func main() {
db, err := gorm.Open("postgres", "host=myhost user=gorm dbname=gorm sslmode=disable password=mypassword")
defer db.Close()
}
SQLite3
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
func main() {
db, err := gorm.Open("sqlite3", "/tmp/gorm.db")
defer db.Close()
}
2.2 迁移 Migrate
自动迁移(Auto Migrate)模式将自动保持更新到最新。
注意:自动迁移仅仅会创建表以及缺少的列和索引,并不会改变现有列的类型或删除未使用的列,以保护数据
db.AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 创建表时添加表后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&Product{})
2.3 检查表是否存在
// 检查模型`User`对应的表是否存在
db.HasTable(&User{})
// 检查表`users`是否存在
db.HasTable("users")
2.4 创建表
// 为模型`User`创建表
db.CreateTable(&User{})
// 创建表`users`时将"ENGINE=InnoDB"附加到SQL语句
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})
2.5 删除表
// 删除模型`User`的表
db.DropTable(&User{})
// 删除表`users`
db.DropTable("users")
// 删除模型`User`的表和表`products`
db.DropTableIfExists(&User{}, "products")
2.6 修改列
// 修改模型`User`的description列的数据类型为`text`
db.Model(&User{}).ModifyColumn("description", "text")
2.7 删除列
// 删除模型`User`的description列
db.Model(&User{}).DropColumn("description")
2.8 添加外键
// 添加外键
// 1st param : 外键字段
// 2nd param : 外键表(字段)
// 3rd param : ONDELETE
// 4th param : ONUPDATE
db.Model(&Product{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
2.9 索引
// 为`name`列添加索引`idx_user_name`
db.Model(&User{}).AddIndex("idx_user_name", "name")
// 为`name`, `age`列添加索引`idx_user_name_age`
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")
// 添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")
// 为多列添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")
// 删除索引
db.Model(&User{}).RemoveIndex("idx_user_name")
3. 模型
3.1 模型定义
package model
import (
"database/sql"
"time"
"github.com/jinzhu/gorm"
)
type User struct {
gorm.Model
Birthday time.Time
Age int
Name string `gorm:"size:255"` // string 长度默认为 255
Num int `gorm:"AUTO_INCREMENT"` // 自增
CreditCard CreditCard // One-To-One (拥有一个 - CreditCard 表的 UserID 作外键)
Emails []Email // One-To-Many (拥有多个 - Email 表的 UserID 作外键)
BillingAddress Address // One-To-One (属于 - 本表的 BillingAddressID 作外键)
BillingAddressID sql.NullInt64
ShippingAddress Address // One-To-One (属于 - 本表的 ShippingAddressID 作外键)
ShippingAddressId int
IgnoreMe int `gorm:"-"` // 忽略这个字段
Languages []Language `gorm:"many2many:user_languages;"` // Many-To-Many , `user_languages` 是连接表
}
type Email struct {
ID int
UserID int `gorm:"index"` // 外键 (属于), tag `index` 是为该列创建索引
Email string `gorm:"type:varchar(100);unique_index"` // `type` 设置 sql 类型,`unique_index` 为该列设置唯一索引
Subscribed bool
}
type Address struct {
ID int
Address1 string `gorm:"not null;unique"` // 设置该字段非空且唯一
Address2 string `gorm:"type:varchar(100);unique"`
Post sql.NullString `gorm:"not null"`
}
type Language struct {
ID int
Name string `gorm:"index:idx_name_code"` // 创建索引并命名,如果找到其他相同名称的索引则创建组合索引
Code string `gorm:"index:idx_name_code"` // `unique_index` also works
}
type CreditCard struct {
gorm.Model
UserID uint
Number string
}
3.2 约定
gorm.Model 结构体
基本模型定义gorm.Model
,包括字段ID
、CreatedAt
、UpdatedAt
、DeletedAt
。
gorm.Model
在gorm
目录下的model.go
中定义:
// Model base model definition, including fields `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, which could be embedded in your models
// type User struct {
// gorm.Model
// }
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `sql:"index"`
}
可以将它嵌入你的模型,或者只写需要的字段:
// 添加字段 `ID`,`CreatedAt`,`UpdatedAt`,`DeletedAt`
type User struct {
gorm.Model
Name string
}
// 只需要字段 `ID`,`CreatedAt`
type User struct {
ID uint
CreatedAt time.Time
Name string
}
表名是结构体名的复数
type User struct {} // 默认表名是 `users`
// 设置 User 的表名为 `profiles`
func (User) TableName() string {
return "profiles"
}
func (u User) TableName() string {
if u.Role == "admin" {
return "admin_users"
} else {
return "users"
}
}
// 全局禁用表名负数
db.SingularTable(true) // 如果设置为 true,`User` 的默认表名为 `user`,使用 `TableName` 设置的表名不受影响
更改默认表名
可以通过定义DefaultTableNameHandler
对默认表名应用任何规则:
gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {
return "prefix_" + defaultTableName;
}
列名是字段名的蛇形小写
type User struct {
ID uint // 列名为 `id`
Name string // 列名为 `name`
Birthday time.Time // 列名为 `birthday`
CreatedAt time.Time // 列名为 `created_at`
}
// 重设列名
type Animal struct {
AnimalID int64 `gorm:"column:beast_id"` // 设置列名为 `beast_id`
Birthday time.Time `gorm:"column:day_of_the_beast"` // 设置列名为 `day_of_the_beast`
Age int64 `gorm:"column:age_of_the_beast"` // 设置列名为 `age_of_the_beast`
}
字段 ID 为默认主键
type User struct {
ID uint // 字段 `ID` 为默认主键
Name string
}
// 使用 tag `primary_key` 来设置主键
type Animal struct {
AnimalID int64 `gorm:"primary_key"` // 设置 AnimalID 为主键
Name string
Age int64
}
字段 CreatedAt 存储创建时间
创建具有CreatedAt
字段的记录将被设置为当前时间:
db.Create(&user) // 将会设置 `CreatedAt` 为当前时间
// 使用 `Update` 来更改它的值
db.Model(&user).Update("created_at", time.Now())
字段 UpdatedAt 存储修改时间
保存具有UpdatedAt
字段的记录将被设置为当前时间:
db.Save(&user) // 将会设置 `UpdatedAt` 为当前时间
db.Model(&user).Update("name", "jinzhu") // 将会设置 `UpdatedAt` 为当前时间
字段 DeletedAt 用于存储删除时间
删除具有
DeletedAt
字段的记录时,记录本身不会从数据库中删除,只是将字段DeletedAt
设置为当前时间,并且该记录在查询时无法被找到,即软删除。
3.3 关联
Belongs To
A
belongs to
association sets up a one-to-one connection with another model, such that each instance of the declaring model “belongs to” one instance of the other model.
例如,如果您的应用程序包含用户和配置文件,并且可以将每个配置文件分配给一个用户:
type User struct {
gorm.Model
Name string
}
// `Profile` belongs to `User`, `UserID` is the foreign key
type Profile struct {
gorm.Model
UserID int
User User
Name string
}
4. Code Snippets
// Enable Logger, show detailed log
db.LogMode(true)
// Disable Logger, don't show any log
db.LogMode(false)
// Debug a single operation, show detailed log for this operation
db.Debug().Where("name = ?", "jinzhu").First(&User{})
5. 高级用法
5.1 错误处理
执行任何操作后,如果发生任何错误,GORM 会将其设置为*DB
的Error
字段:
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {
// 错误处理...
}
// 如果有多个错误发生,用`GetErrors`获取所有的错误,它返回`[]error`
db.First(&user).Limit(10).Find(&users).GetErrors()
// 检查是否返回 RecordNotFound 错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()
if db.Model(&user).Related(&credit_card).RecordNotFound() {
// 没有信用卡被发现处理...
}
5.2 事务
要在事务中执行一组操作,一般流程如下:
// 开始事务
tx := db.Begin()
// 在事务中做一些数据库操作(从这一点使用'tx',而不是'db')
tx.Create(...)
// ...
// 发生错误时回滚事务
tx.Rollback()
// 或提交事务
tx.Commit()
一个具体的例子:
func CreateAnimals(db *gorm.DB) err {
tx := db.Begin()
// 注意,一旦你在一个事务中,使用tx作为数据库句柄
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
5.3 SQL 构建
执行原生 SQL
db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})
// Scan
type Result struct {
Name string
Age int
}
var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
sql.Row & sql.Rows
获取查询结果为*sql.Row
或*sql.Rows
:
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
...
rows.Scan(&name, &age, &email)
...
}
// Raw SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
...
rows.Scan(&name, &age, &email)
...
}
迭代中使用 sql.Rows 的 Scan
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
var user User
db.ScanRows(rows, &user)
// do something
}
5.4 通用数据库接口 sql.DB
从*gorm.DB
连接获取通用数据库接口*sql.DB
:
// 获取通用数据库对象`*sql.DB`以使用其函数
db.DB()
// Ping
db.DB().Ping()
设置连接池:
db.DB().SetMaxIdleConns(10)
db.DB().SetMaxOpenConns(100)
5.5 复合主键
将多个字段设置为主键以启用复合主键:
type Product struct {
ID string `gorm:"primary_key"`
LanguageCode string `gorm:"primary_key"`
}
5.6 日志
Gorm 有内置的日志记录器支持,默认情况下,它会打印发生的错误:
// 启用Logger,显示详细日志
db.LogMode(true)
// 禁用日志记录器,不显示任何日志
db.LogMode(false)
// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})
也可以自定义Logger
:
db.SetLogger(gorm.Logger{revel.TRACE})
db.SetLogger(log.New(os.Stdout, "\r\n", 0))
参见:
// TODO: To be updated…